Merge branch 'master' into nymkappa/prepaid-acceleration

This commit is contained in:
Felipe Knorr Kuhn
2024-04-02 21:53:01 +09:00
committed by GitHub
307 changed files with 13605 additions and 17525 deletions

View File

@@ -29,9 +29,6 @@ export class ApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
if (network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND) {
network = '';
}
this.apiBasePath = network ? '/' + network : '';
});
}
@@ -228,8 +225,9 @@ export class ApiService {
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/emergency-spent/stats');
}
listFeaturedAssets$(): Observable<any[]> {
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured');
listFeaturedAssets$(network: string = 'liquid'): Observable<any[]> {
if (network === 'liquid') return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured');
return of([]);
}
getAssetGroup$(id: string): Observable<any> {

View File

@@ -25,9 +25,6 @@ export class ElectrsApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
if (network === 'bisq') {
network = '';
}
this.apiBasePath = network ? '/' + network : '';
});
}

View File

@@ -29,6 +29,7 @@ export class EnterpriseService {
this.subdomain = subdomain;
this.fetchSubdomainInfo();
this.disableSubnetworks();
this.stateService.env.ACCELERATOR = false;
} else {
this.insertMatomo();
}
@@ -43,7 +44,6 @@ export class EnterpriseService {
this.stateService.env.LIQUID_ENABLED = false;
this.stateService.env.LIQUID_TESTNET_ENABLED = false;
this.stateService.env.SIGNET_ENABLED = false;
this.stateService.env.BISQ_ENABLED = false;
}
fetchSubdomainInfo(): void {
@@ -80,14 +80,6 @@ export class EnterpriseService {
siteId = 10;
statsUrl = '//stats.liquid.network/';
break;
case 'bisq.markets':
siteId = 7;
statsUrl = '//stats.bisq.markets/';
break;
case 'bisq.ninja':
statsUrl = '//stats.bisq.markets/';
siteId = 11;
break;
default:
return;
}

View File

@@ -1,8 +1,8 @@
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http';
import { Inject, Injectable, PLATFORM_ID, makeStateKey, TransferState } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { catchError, tap } from 'rxjs/operators';
import { isPlatformBrowser } from '@angular/common';
@Injectable()
@@ -17,14 +17,18 @@ export class HttpCacheInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.isBrowser && request.method === 'GET') {
const cachedResponse = this.transferState.get<any>(makeStateKey(request.url), null);
if (cachedResponse) {
const { response, headers } = this.transferState.get<any>(makeStateKey(request.url), null) || {};
if (response) {
const httpHeaders = new HttpHeaders();
for (const [k,v] of Object.entries(headers)) {
httpHeaders.set(k,v as string[]);
}
const modifiedResponse = new HttpResponse<any>({
headers: cachedResponse.headers,
body: cachedResponse.body,
status: cachedResponse.status,
statusText: cachedResponse.statusText,
url: cachedResponse.url
headers: httpHeaders,
body: response.body,
status: response.status,
statusText: response.statusText,
url: response.url
});
this.transferState.remove(makeStateKey(request.url));
return of(modifiedResponse);
@@ -32,11 +36,41 @@ export class HttpCacheInterceptor implements HttpInterceptor {
}
return next.handle(request)
.pipe(tap((event: HttpEvent<any>) => {
if (!this.isBrowser && event instanceof HttpResponse) {
let keyId = request.url.split('/').slice(3).join('/');
this.transferState.set<any>(makeStateKey('/' + keyId), event);
}
}));
.pipe(
tap((event: HttpEvent<any>) => {
if (!this.isBrowser && event instanceof HttpResponse) {
let keyId = request.url.split('/').slice(3).join('/');
const headers = {};
for (const k of event.headers.keys()) {
headers[k] = event.headers.getAll(k);
}
this.transferState.set<any>(makeStateKey('/' + keyId), { response: event, headers });
}
}),
catchError((e) => {
if (e instanceof HttpErrorResponse) {
if (e.status === 0) {
throw new HttpErrorResponse({
error: 'Unknown error',
headers: e.headers,
status: 0,
statusText: 'Unknown error',
url: e.url,
});
} else {
throw e;
}
} else {
const msg = e?.['message'] || 'Unknown error';
throw new HttpErrorResponse({
error: msg,
headers: new HttpHeaders(),
status: 0,
statusText: msg,
url: '',
});
}
})
);
}
}

View File

@@ -17,12 +17,7 @@ const networkModules = {
{ name: 'liquid', path: '' },
{ name: 'liquidtestnet', path: '/testnet' },
],
},
bisq: {
subnets: [
{ name: 'bisq', path: '' },
],
},
}
};
const networks = Object.keys(networkModules);
@@ -44,7 +39,7 @@ export class NavigationService {
});
}
// For each network (bitcoin/liquid/bisq), find and save the longest url path compatible with the current route
// For each network (bitcoin/liquid), find and save the longest url path compatible with the current route
updateSubnetPaths(root: ActivatedRouteSnapshot): void {
let path = '';
const networkPaths = {};

View File

@@ -71,8 +71,6 @@ export class SeoService {
let domain = 'mempool.space';
if (this.stateService.env.BASE_MODULE === 'liquid') {
domain = 'liquid.network';
} else if (this.stateService.env.BASE_MODULE === 'bisq') {
domain = 'bisq.markets';
}
this.canonicalLink.setAttribute('href', 'https://' + domain + path);
}
@@ -86,8 +84,6 @@ export class SeoService {
return this.baseTitle + ' - Liquid Network';
if (this.network === 'liquidtestnet')
return this.baseTitle + ' - Liquid Testnet';
if (this.network === 'bisq')
return this.baseTitle + ' - Bisq Markets';
return this.baseTitle + ' - ' + (this.network ? this.ucfirst(this.network) : 'Bitcoin') + ' Explorer';
}
@@ -96,8 +92,6 @@ export class SeoService {
return this.baseDescription + ' See the real-time status of your transactions, browse network stats, and more.';
if ( (this.network === 'liquid') || (this.network === 'liquidtestnet') )
return this.baseDescription + ' See Liquid transactions & assets, get network info, and more.';
if (this.network === 'bisq')
return this.baseDescription + ' See Bisq market prices, trading activity, and more.';
}
ucfirst(str: string) {

View File

@@ -52,9 +52,6 @@ export class ServicesApiServices {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
if (network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND) {
network = '';
}
this.apiBasePath = network ? '/' + network : '';
});
@@ -104,12 +101,6 @@ export class ServicesApiServices {
return this.httpClient.get<any>(`${SERVICES_API_PREFIX}/account`);
}
getNodeOwner$(publicKey: string): Observable<any> {
let params = new HttpParams()
.set('node_public_key', publicKey);
return this.httpClient.get<any>(`${SERVICES_API_PREFIX}/lightning/claim/current`, { params, observe: 'response' });
}
getUserMenuGroups$(): Observable<MenuGroup[]> {
const auth = this.storageService.getAuth();
if (!auth) {

View File

@@ -1,8 +1,8 @@
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
import { Transaction } from '../interfaces/electrs.interface';
import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo } from '../interfaces/websocket.interface';
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '../interfaces/node-api.interface';
import { Router, NavigationStart } from '@angular/router';
import { isPlatformBrowser } from '@angular/common';
import { filter, map, scan, shareReplay } from 'rxjs/operators';
@@ -25,8 +25,6 @@ export interface Env {
SIGNET_ENABLED: boolean;
LIQUID_ENABLED: boolean;
LIQUID_TESTNET_ENABLED: boolean;
BISQ_ENABLED: boolean;
BISQ_SEPARATE_BACKEND: boolean;
ITEMS_PER_PAGE: number;
KEEP_BLOCKS_AMOUNT: number;
OFFICIAL_MEMPOOL_SPACE: boolean;
@@ -40,7 +38,6 @@ export interface Env {
PACKAGE_JSON_VERSION: string;
MEMPOOL_WEBSITE_URL: string;
LIQUID_WEBSITE_URL: string;
BISQ_WEBSITE_URL: string;
MINING_DASHBOARD: boolean;
LIGHTNING: boolean;
AUDIT: boolean;
@@ -60,8 +57,6 @@ const defaultEnv: Env = {
'LIQUID_ENABLED': false,
'LIQUID_TESTNET_ENABLED': false,
'BASE_MODULE': 'mempool',
'BISQ_ENABLED': false,
'BISQ_SEPARATE_BACKEND': false,
'ITEMS_PER_PAGE': 10,
'KEEP_BLOCKS_AMOUNT': 8,
'OFFICIAL_MEMPOOL_SPACE': false,
@@ -74,7 +69,6 @@ const defaultEnv: Env = {
'PACKAGE_JSON_VERSION': '',
'MEMPOOL_WEBSITE_URL': 'https://mempool.space',
'LIQUID_WEBSITE_URL': 'https://liquid.network',
'BISQ_WEBSITE_URL': 'https://bisq.markets',
'MINING_DASHBOARD': true,
'LIGHTNING': false,
'AUDIT': false,
@@ -92,6 +86,7 @@ const defaultEnv: Env = {
export class StateService {
isBrowser: boolean = isPlatformBrowser(this.platformId);
isMempoolSpaceBuild = window['isMempoolSpaceBuild'] ?? false;
backend: 'esplora' | 'electrum' | 'none' = 'esplora';
network = '';
lightning = false;
blockVSize: number;
@@ -99,6 +94,7 @@ export class StateService {
latestBlockHeight = -1;
blocks: BlockExtended[] = [];
backend$ = new BehaviorSubject<'esplora' | 'electrum' | 'none'>('esplora');
networkChanged$ = new ReplaySubject<string>(1);
lightningChanged$ = new ReplaySubject<boolean>(1);
blocksSubject$ = new BehaviorSubject<BlockExtended[]>([]);
@@ -151,6 +147,7 @@ export class StateService {
hideAudit: BehaviorSubject<boolean>;
fiatCurrency$: BehaviorSubject<string>;
rateUnits$: BehaviorSubject<string>;
showMiningInfo$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
searchFocus$: Subject<boolean> = new Subject<boolean>();
menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);
@@ -213,11 +210,6 @@ export class StateService {
}
}, {}));
if (this.env.BASE_MODULE === 'bisq') {
this.network = this.env.BASE_MODULE;
this.networkChanged$.next(this.env.BASE_MODULE);
}
this.networkChanged$.subscribe((network) => {
this.transactions$ = new BehaviorSubject<TransactionStripped[]>(null);
this.blocksSubject$.next([]);
@@ -256,6 +248,10 @@ export class StateService {
const rateUnitPreference = this.storageService.getValue('rate-unit-preference');
this.rateUnits$ = new BehaviorSubject<string>(rateUnitPreference || 'vb');
this.backend$.subscribe(backend => {
this.backend = backend;
});
}
setNetworkBasedonUrl(url: string) {
@@ -266,9 +262,9 @@ export class StateService {
// /^\/ starts with a forward slash...
// (?:[a-z]{2}(?:-[A-Z]{2})?\/)? optional locale prefix (non-capturing)
// (?:preview\/)? optional "preview" prefix (non-capturing)
// (bisq|testnet|liquidtestnet|liquid|signet)/ network string (captured as networkMatches[1])
// (testnet|liquidtestnet|liquid|signet)/ network string (captured as networkMatches[1])
// ($|\/) network string must end or end with a slash
const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(bisq|testnet|liquidtestnet|liquid|signet)($|\/)/);
const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet|liquidtestnet|liquid|signet)($|\/)/);
switch (networkMatches && networkMatches[1]) {
case 'liquid':
if (this.network !== 'liquid') {
@@ -299,12 +295,6 @@ export class StateService {
}
}
return;
case 'bisq':
if (this.network !== 'bisq') {
this.network = 'bisq';
this.networkChanged$.next('bisq');
}
return;
default:
if (this.env.BASE_MODULE !== 'mempool') {
if (this.network !== this.env.BASE_MODULE) {

View File

@@ -3,10 +3,10 @@ import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { WebsocketResponse } from '../interfaces/websocket.interface';
import { StateService } from './state.service';
import { Transaction } from '../interfaces/electrs.interface';
import { Subscription } from 'rxjs';
import { firstValueFrom, Subscription } from 'rxjs';
import { ApiService } from './api.service';
import { take } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { TransferState, makeStateKey } from '@angular/core';
import { CacheService } from './cache.service';
import { uncompressDeltaChange, uncompressTx } from '../shared/common.utils';
@@ -54,11 +54,16 @@ export class WebsocketService {
.pipe(take(1))
.subscribe((response) => this.handleResponse(response));
} else {
this.network = this.stateService.network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND ? '' : this.stateService.network;
this.network = this.stateService.network;
this.websocketSubject = webSocket<WebsocketResponse>(this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : ''));
const theInitData = this.transferState.get<any>(initData, null);
const { response: theInitData } = this.transferState.get<any>(initData, null) || {};
if (theInitData) {
if (theInitData.body.blocks) {
theInitData.body.blocks = theInitData.body.blocks.reverse();
}
this.stateService.backend$.next(theInitData.backend);
this.stateService.isLoadingWebSocket$.next(false);
this.handleResponse(theInitData.body);
this.startSubscription(false, true);
} else {
@@ -66,9 +71,6 @@ export class WebsocketService {
}
this.stateService.networkChanged$.subscribe((network) => {
if (network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND) {
network = '';
}
if (network === this.network) {
return;
}
@@ -223,6 +225,7 @@ export class WebsocketService {
}
startTrackRbfSummary() {
this.initRbfSummary();
this.websocketSubject.next({ 'track-rbf-summary': true });
this.isTrackingRbfSummary = true;
}
@@ -232,14 +235,6 @@ export class WebsocketService {
this.isTrackingRbfSummary = false;
}
startTrackBisqMarket(market: string) {
this.websocketSubject.next({ 'track-bisq-market': market });
}
stopTrackingBisqMarket() {
this.websocketSubject.next({ 'track-bisq-market': 'stop' });
}
fetchStatistics(historicalDate: string) {
this.websocketSubject.next({ historicalDate });
}
@@ -285,6 +280,10 @@ export class WebsocketService {
handleResponse(response: WebsocketResponse) {
let reinitBlocks = false;
if (response.backend) {
this.stateService.backend$.next(response.backend);
}
if (response.blocks && response.blocks.length) {
const blocks = response.blocks;
this.stateService.resetBlocks(blocks);
@@ -445,4 +444,30 @@ export class WebsocketService {
this.websocketSubject.next({'refresh-blocks': true});
}
}
async initRbfSummary(): Promise<void> {
if (!this.stateService.isBrowser) {
const rbfList = await firstValueFrom(this.apiService.getRbfList$(false));
if (rbfList) {
const rbfSummary = rbfList.slice(0, 6).map(rbfTree => {
let oldFee = 0;
let oldVsize = 0;
for (const replaced of rbfTree.replaces) {
oldFee += replaced.tx.fee;
oldVsize += replaced.tx.vsize;
}
return {
txid: rbfTree.tx.txid,
mined: !!rbfTree.tx.mined,
fullRbf: !!rbfTree.tx.fullRbf,
oldFee,
oldVsize,
newFee: rbfTree.tx.fee,
newVsize: rbfTree.tx.vsize,
};
});
this.stateService.rbfLatestSummary$.next(rbfSummary);
}
}
}
}

View File

@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ZoneService {
constructor() { }
wrapObservable<T>(obs: Observable<T>): Observable<T> {
return obs;
}
}

View File

@@ -0,0 +1,60 @@
import { ApplicationRef, Injectable, NgZone } from '@angular/core';
import { Observable, Subscriber } from 'rxjs';
// global Zone object provided by zone.js
declare const Zone: any;
@Injectable({
providedIn: 'root'
})
export class ZoneService {
constructor(
private ngZone: NgZone,
private appRef: ApplicationRef,
) { }
wrapObservable<T>(obs: Observable<T>): Observable<T> {
return new Observable((subscriber: Subscriber<T>) => {
let task: any;
this.ngZone.run(() => {
task = Zone.current.scheduleMacroTask('wrapObservable', () => {}, {}, () => {}, () => {});
});
const subscription = obs.subscribe(
value => {
subscriber.next(value);
if (task) {
this.ngZone.run(() => {
this.appRef.tick();
});
task.invoke();
}
},
err => {
subscriber.error(err);
if (task) {
this.appRef.tick();
task.invoke();
}
},
() => {
subscriber.complete();
if (task) {
this.appRef.tick();
task.invoke();
}
}
);
return () => {
subscription.unsubscribe();
if (task) {
this.appRef.tick();
task.invoke();
}
};
});
}
}