import { Injectable } from '@angular/core'; import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit } from '../interfaces/node-api.interface'; import { Observable, of } from 'rxjs'; import { StateService } from './state.service'; import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface'; import { Outspend, Transaction } from '../interfaces/electrs.interface'; import { Conversion } from './price.service'; import { MenuGroup } from '../interfaces/services.interface'; import { StorageService } from './storage.service'; // Todo - move to config.json const SERVICES_API_PREFIX = `/api/v1/services`; @Injectable({ providedIn: 'root' }) export class ApiService { private apiBaseUrl: string; // base URL is protocol, hostname, and port private apiBasePath: string; // network path is /testnet, etc. or '' for mainnet constructor( private httpClient: HttpClient, private stateService: StateService, private storageService: StorageService ) { this.apiBaseUrl = ''; // use relative URL by default if (!stateService.isBrowser) { // except when inside AU SSR process this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT; } 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 : ''; }); if (this.stateService.env.GIT_COMMIT_HASH_MEMPOOL_SPACE) { this.getServicesBackendInfo$().subscribe(version => { this.stateService.servicesBackendInfo$.next(version); }) } } list2HStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/2h'); } list24HStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/24h'); } list1WStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/1w'); } list1MStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/1m'); } list3MStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/3m'); } list6MStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/6m'); } list1YStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/1y'); } list2YStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/2y'); } list3YStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/3y'); } list4YStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/4y'); } listAllTimeStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/all'); } getTransactionTimes$(txIds: string[]): Observable { let params = new HttpParams(); txIds.forEach((txId: string) => { params = params.append('txId[]', txId); }); return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/transaction-times', { params }); } getOutspendsBatched$(txIds: string[]): Observable { let params = new HttpParams(); txIds.forEach((txId: string) => { params = params.append('txId[]', txId); }); return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/outspends', { params }); } getAboutPageProfiles$(): Observable { return this.httpClient.get(this.apiBaseUrl + '/api/v1/services/sponsors'); } getOgs$(): Observable { return this.httpClient.get(this.apiBaseUrl + '/api/v1/donations'); } getTranslators$(): Observable { return this.httpClient.get(this.apiBaseUrl + '/api/v1/translators'); } getContributor$(): Observable { return this.httpClient.get(this.apiBaseUrl + '/api/v1/contributors'); } getInitData$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/init-data'); } getCpfpinfo$(txid: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/cpfp/' + txid); } validateAddress$(address: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address); } getRbfHistory$(txid: string): Observable<{ replacements: RbfTree, replaces: string[] }> { return this.httpClient.get<{ replacements: RbfTree, replaces: string[] }>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/rbf'); } getRbfCachedTx$(txid: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/cached'); } getRbfList$(fullRbf: boolean, after?: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/' + (fullRbf ? 'fullrbf/' : '') + 'replacements/' + (after || '')); } listLiquidPegsMonth$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month'); } listFeaturedAssets$(): Observable { return this.httpClient.get(this.apiBaseUrl + '/api/v1/assets/featured'); } getAssetGroup$(id: string): Observable { return this.httpClient.get(this.apiBaseUrl + '/api/v1/assets/group/' + id); } postTransaction$(hexPayload: string): Observable { return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'}); } listPools$(interval: string | undefined) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getPoolStats$(slug: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}`); } getPoolHashrate$(slug: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/hashrate`); } getPoolBlocks$(slug: string, fromHeight: number): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/blocks` + (fromHeight !== undefined ? `/${fromHeight}` : '') ); } getBlocks$(from: number): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks` + (from !== undefined ? `/${from}` : ``) ); } getBlock$(hash: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash); } getStrippedBlockTransactions$(hash: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash + '/summary'); } getDifficultyAdjustments$(interval: string | undefined): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty-adjustments` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getHistoricalHashrate$(interval: string | undefined): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getHistoricalPoolsHashrate$(interval: string | undefined): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate/pools` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getHistoricalBlockFees$(interval: string | undefined) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/fees` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getHistoricalBlockRewards$(interval: string | undefined) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/rewards` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getHistoricalBlockFeeRates$(interval: string | undefined) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/fee-rates` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable> { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/sizes-weights` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getHistoricalBlocksHealth$(interval: string | undefined) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/predictions` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); } getBlockAudit$(hash: string) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/block/${hash}/audit-summary` ); } getBlockAuditScores$(from: number): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/audit/scores` + (from !== undefined ? `/${from}` : ``) ); } getBlockAuditScore$(hash: string) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/audit/score/` + hash ); } getRewardStats$(blockCount: number = 144): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/reward-stats/${blockCount}`); } getEnterpriseInfo$(name: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/enterprise/info/` + name); } getChannelByTxIds$(txIds: string[]): Observable { let params = new HttpParams(); txIds.forEach((txId: string) => { params = params.append('txId[]', txId); }); return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels/txids/', { params }); } lightningSearch$(searchText: string): Observable { let params = new HttpParams().set('searchText', searchText); return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/search', { params }); } getNodesPerIsp(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp-ranking'); } getNodeForCountry$(country: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/country/' + country); } getNodeForISP$(isp: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp/' + isp); } getNodesPerCountry$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/countries'); } getWorldNodes$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/world'); } getChannelsGeo$(publicKey?: string, style?: 'graph' | 'nodepage' | 'widget' | 'channelpage'): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels-geo' + (publicKey !== undefined ? `/${publicKey}` : '') + (style !== undefined ? `?style=${style}` : '') ); } getHistoricalPrice$(timestamp: number | undefined): Observable { if (this.stateService.isAnyTestnet()) { return of({ prices: [], exchangeRates: { USDEUR: 0, USDGBP: 0, USDCAD: 0, USDCHF: 0, USDAUD: 0, USDJPY: 0, } }); } return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + '/api/v1/historical-price' + (timestamp ? `?timestamp=${timestamp}` : '') ); } /** * Services */ getNodeOwner$(publicKey: string): Observable { let params = new HttpParams() .set('node_public_key', publicKey); return this.httpClient.get(`${SERVICES_API_PREFIX}/lightning/claim/current`, { params, observe: 'response' }); } getUserMenuGroups$(): Observable { const auth = this.storageService.getAuth(); if (!auth) { return of(null); } return this.httpClient.get(`${SERVICES_API_PREFIX}/account/menu`, { headers: { 'Authorization': auth.token } }); } getUserInfo$(): Observable { const auth = this.storageService.getAuth(); if (!auth) { return of(null); } return this.httpClient.get(`${SERVICES_API_PREFIX}/account`, { headers: { 'Authorization': auth.token } }); } logout$(): Observable { const auth = this.storageService.getAuth(); if (!auth) { return of(null); } localStorage.removeItem('auth'); return this.httpClient.post(`${SERVICES_API_PREFIX}/auth/logout`, { headers: { 'Authorization': auth.token } }); } getServicesBackendInfo$(): Observable { return this.httpClient.get(`${SERVICES_API_PREFIX}/version`); } estimate$(txInput: string) { return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/estimate`, { txInput: txInput }, { observe: 'response' }); } }