Liquid: add BTC reserves to L-BTC widget and make it dynamic
This commit is contained in:
		
							parent
							
								
									de2842b62a
								
							
						
					
					
						commit
						752eba767a
					
				| @ -41,20 +41,24 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnChanges() { |   ngOnChanges() { | ||||||
|     if (!this.data) { |     if (!this.data?.liquidPegs) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this.pegsChartOptions = this.createChartOptions(this.data.series, this.data.labels); |     if (!this.data.liquidReserves || this.data.liquidReserves?.series.length !== this.data.liquidPegs.series.length) { | ||||||
|  |       this.pegsChartOptions = this.createChartOptions(this.data.liquidPegs.series, this.data.liquidPegs.labels); | ||||||
|  |     } else { | ||||||
|  |       this.pegsChartOptions = this.createChartOptions(this.data.liquidPegs.series, this.data.liquidPegs.labels, this.data.liquidReserves.series); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   rendered() { |   rendered() { | ||||||
|     if (!this.data) { |     if (!this.data.liquidPegs) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this.isLoading = false; |     this.isLoading = false; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   createChartOptions(series: number[], labels: string[]): EChartsOption { |   createChartOptions(pegSeries: number[], labels: string[], reservesSeries?: number[],): EChartsOption { | ||||||
|     return { |     return { | ||||||
|       grid: { |       grid: { | ||||||
|         height: this.height, |         height: this.height, | ||||||
| @ -99,17 +103,18 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges { | |||||||
|           type: 'line', |           type: 'line', | ||||||
|         }, |         }, | ||||||
|         formatter: (params: any) => { |         formatter: (params: any) => { | ||||||
|           const colorSpan = (color: string) => `<span class="indicator" style="background-color: #116761;"></span>`; |           const colorSpan = (color: string) => `<span class="indicator" style="background-color: ${color};"></span>`; | ||||||
|           let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>'; |           let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>'; | ||||||
|           params.map((item: any, index: number) => { |           for (let index = params.length - 1; index >= 0; index--) { | ||||||
|  |             const item = params[index]; | ||||||
|             if (index < 26) { |             if (index < 26) { | ||||||
|               itemFormatted += `<div class="item">
 |               itemFormatted += `<div class="item">
 | ||||||
|                 <div class="indicator-container">${colorSpan(item.color)}</div> |                 <div class="indicator-container">${colorSpan(item.color)}</div> | ||||||
|                 <div class="grow"></div> |                 <div style="margin-right: 5px"></div> | ||||||
|                 <div class="value">${formatNumber(item.value, this.locale, '1.2-2')} <span class="symbol">L-BTC</span></div> |                 <div class="value">${formatNumber(item.value, this.locale, '1.2-2')} <span class="symbol">${item.seriesName}</span></div> | ||||||
|               </div>`;
 |               </div>`;
 | ||||||
|             } |             } | ||||||
|           }); |           }; | ||||||
|           return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`; |           return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`; | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
| @ -138,20 +143,34 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges { | |||||||
|       }, |       }, | ||||||
|       series: [ |       series: [ | ||||||
|         { |         { | ||||||
|           data: series, |           data: pegSeries, | ||||||
|  |           name: 'L-BTC', | ||||||
|  |           color: '#116761', | ||||||
|           type: 'line', |           type: 'line', | ||||||
|           stack: 'total', |           stack: 'total', | ||||||
|           smooth: false, |           smooth: true, | ||||||
|           showSymbol: false, |           showSymbol: false, | ||||||
|           areaStyle: { |           areaStyle: { | ||||||
|             opacity: 0.2, |             opacity: 0.2, | ||||||
|             color: '#116761', |             color: '#116761', | ||||||
|           }, |           }, | ||||||
|           lineStyle: { |           lineStyle: { | ||||||
|             width: 3, |             width: 2, | ||||||
|             color: '#116761', |             color: '#116761', | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           data: reservesSeries, | ||||||
|  |           name: 'BTC', | ||||||
|  |           color: '#EA983B', | ||||||
|  |           type: 'line', | ||||||
|  |           smooth: true, | ||||||
|  |           showSymbol: false, | ||||||
|  |           lineStyle: { | ||||||
|  |             width: 2, | ||||||
|  |             color: '#EA983B', | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|       ], |       ], | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ | |||||||
|             </ng-container> |             </ng-container> | ||||||
|           </ng-template> |           </ng-template> | ||||||
|           <ng-template #liquidPegs> |           <ng-template #liquidPegs> | ||||||
|             <app-lbtc-pegs-graph [data]="liquidPegsMonth$ | async"></app-lbtc-pegs-graph> |             <app-lbtc-pegs-graph [data]="fullHistory$ | async"></app-lbtc-pegs-graph> | ||||||
|           </ng-template> |           </ng-template> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @ -270,8 +270,16 @@ | |||||||
|   <div class="mempool-info-data"> |   <div class="mempool-info-data"> | ||||||
|     <div class="item"> |     <div class="item"> | ||||||
|       <h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5> |       <h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5> | ||||||
|       <ng-container *ngIf="(liquidPegsMonth$ | async) as liquidPegsMonth; else loadingTransactions"> |       <ng-container *ngIf="(currentPeg$ | async) as currentPeg; else loadingTransactions"> | ||||||
|         <p class="card-text">{{ liquidPegsMonth.series.slice(-1)[0] | number: '1.2-2' }} <span>L-BTC</span></p> |         <p i18n-ngbTooltip="liquid.last-elements-audit-block" [ngbTooltip]="'L-BTC supply last updated at Liquid block ' + (currentPeg.lastBlockUpdate)" placement="top" class="card-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span class="liquid-color">L-BTC</span></p> | ||||||
|  |       </ng-container> | ||||||
|  |     </div> | ||||||
|  |     <div class="item"> | ||||||
|  |       <a class="title-link" [routerLink]="['/audit' | relativeUrl]"> | ||||||
|  |         <h5 class="card-title" i18n="dashboard.btc-reserves">BTC Reserves <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5> | ||||||
|  |       </a> | ||||||
|  |       <ng-container *ngIf="(currentReserves$ | async) as currentReserves; else loadingTransactions"> | ||||||
|  |         <p i18n-ngbTooltip="liquid.last-bitcoin-audit-block" [ngbTooltip]="'BTC reserves last updated at Bitcoin block ' + (currentReserves.lastBlockUpdate)" placement="top" class="card-text">{{ +(currentReserves.amount) / 100000000 | number: '1.2-2' }} <span class="bitcoin-color">BTC</span></p> | ||||||
|       </ng-container> |       </ng-container> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | |||||||
| @ -97,6 +97,12 @@ | |||||||
|         color: #ffffff66; |         color: #ffffff66; | ||||||
|         font-size: 12px; |         font-size: 12px; | ||||||
|       } |       } | ||||||
|  |       .liquid-color { | ||||||
|  |         color: #116761; | ||||||
|  |       } | ||||||
|  |       .bitcoin-color { | ||||||
|  |         color: #b86d12; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     .progress { |     .progress { | ||||||
|       width: 90%; |       width: 90%; | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; | import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; | ||||||
| import { combineLatest, merge, Observable, of, Subscription } from 'rxjs'; | import { combineLatest, concat, EMPTY, interval, merge, Observable, of, Subscription } from 'rxjs'; | ||||||
| import { catchError, filter, map, scan, share, switchMap, tap } from 'rxjs/operators'; | import { catchError, delay, filter, map, mergeMap, scan, share, skip, startWith, switchMap, tap } from 'rxjs/operators'; | ||||||
| import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface'; | import { AuditStatus, BlockExtended, CurrentPegs, OptimizedMempoolStats } from '../interfaces/node-api.interface'; | ||||||
| import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface'; | import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface'; | ||||||
| import { ApiService } from '../services/api.service'; | import { ApiService } from '../services/api.service'; | ||||||
| import { StateService } from '../services/state.service'; | import { StateService } from '../services/state.service'; | ||||||
| @ -47,8 +47,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|   transactionsWeightPerSecondOptions: any; |   transactionsWeightPerSecondOptions: any; | ||||||
|   isLoadingWebSocket$: Observable<boolean>; |   isLoadingWebSocket$: Observable<boolean>; | ||||||
|   liquidPegsMonth$: Observable<any>; |   liquidPegsMonth$: Observable<any>; | ||||||
|  |   currentPeg$: Observable<CurrentPegs>; | ||||||
|  |   auditStatus$: Observable<AuditStatus>; | ||||||
|  |   liquidReservesMonth$: Observable<any>; | ||||||
|  |   currentReserves$: Observable<CurrentPegs>; | ||||||
|  |   fullHistory$: Observable<any>; | ||||||
|   currencySubscription: Subscription; |   currencySubscription: Subscription; | ||||||
|   currency: string; |   currency: string; | ||||||
|  |   private lastPegBlockUpdate: number = 0; | ||||||
|  |   private lastReservesBlockUpdate: number = 0; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     public stateService: StateService, |     public stateService: StateService, | ||||||
| @ -204,8 +211,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|     if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') { |     if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') { | ||||||
|       this.liquidPegsMonth$ = this.apiService.listLiquidPegsMonth$() |       ////////// Pegs historical data //////////
 | ||||||
|  |       this.liquidPegsMonth$ = interval(60 * 60 * 1000) | ||||||
|         .pipe( |         .pipe( | ||||||
|  |           startWith(0), | ||||||
|  |           switchMap(() => this.apiService.listLiquidPegsMonth$()), | ||||||
|           map((pegs) => { |           map((pegs) => { | ||||||
|             const labels = pegs.map(stats => stats.date); |             const labels = pegs.map(stats => stats.date); | ||||||
|             const series = pegs.map(stats => parseFloat(stats.amount) / 100000000); |             const series = pegs.map(stats => parseFloat(stats.amount) / 100000000); | ||||||
| @ -217,6 +227,89 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { | |||||||
|           }), |           }), | ||||||
|           share(), |           share(), | ||||||
|         ); |         ); | ||||||
|  | 
 | ||||||
|  |       this.currentPeg$ = concat( | ||||||
|  |         // We fetch the current peg when the page load and
 | ||||||
|  |         // wait for the API response before listening to websocket blocks
 | ||||||
|  |         this.apiService.liquidPegs$() | ||||||
|  |           .pipe( | ||||||
|  |             tap((currentPeg) => this.lastPegBlockUpdate = currentPeg.lastBlockUpdate) | ||||||
|  |           ), | ||||||
|  |         // Or when we receive a newer block, we wait 2 seconds so that the backend updates and we fetch the current peg
 | ||||||
|  |         this.stateService.blocks$ | ||||||
|  |           .pipe( | ||||||
|  |             delay(2000), | ||||||
|  |             switchMap((_) => this.apiService.liquidPegs$()), | ||||||
|  |             filter((currentPeg) => currentPeg.lastBlockUpdate > this.lastPegBlockUpdate), | ||||||
|  |             tap((currentPeg) => this.lastPegBlockUpdate = currentPeg.lastBlockUpdate) | ||||||
|  |           ) | ||||||
|  |       ).pipe( | ||||||
|  |         share() | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       ////////// BTC Reserves historical data //////////
 | ||||||
|  |       this.auditStatus$ = concat( | ||||||
|  |         this.apiService.federationAuditSynced$().pipe(share()), | ||||||
|  |         this.stateService.blocks$.pipe( | ||||||
|  |           skip(1), | ||||||
|  |           delay(2000), | ||||||
|  |           switchMap(() => this.apiService.federationAuditSynced$()), | ||||||
|  |           share() | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|  |        | ||||||
|  |       this.liquidReservesMonth$ = interval(60 * 60 * 1000).pipe( | ||||||
|  |         startWith(0), | ||||||
|  |         mergeMap(() => this.apiService.federationAuditSynced$()), | ||||||
|  |         switchMap((auditStatus) => { | ||||||
|  |           return auditStatus.isAuditSynced ? this.apiService.listLiquidReservesMonth$() : EMPTY; | ||||||
|  |         }), | ||||||
|  |         map(reserves => { | ||||||
|  |           const labels = reserves.map(stats => stats.date); | ||||||
|  |           const series = reserves.map(stats => parseFloat(stats.amount) / 100000000); | ||||||
|  |           return { | ||||||
|  |             series, | ||||||
|  |             labels | ||||||
|  |           }; | ||||||
|  |         }), | ||||||
|  |         share() | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       this.currentReserves$ = this.auditStatus$.pipe( | ||||||
|  |         filter(auditStatus => auditStatus.isAuditSynced === true), | ||||||
|  |         switchMap(_ => | ||||||
|  |           this.apiService.liquidReserves$().pipe( | ||||||
|  |             filter((currentReserves) => currentReserves.lastBlockUpdate > this.lastReservesBlockUpdate), | ||||||
|  |             tap((currentReserves) => { | ||||||
|  |               this.lastReservesBlockUpdate = currentReserves.lastBlockUpdate; | ||||||
|  |             }) | ||||||
|  |           ) | ||||||
|  |         ), | ||||||
|  |         share() | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       this.fullHistory$ = combineLatest([this.liquidPegsMonth$, this.currentPeg$, this.liquidReservesMonth$.pipe(startWith(null)), this.currentReserves$.pipe(startWith(null))]) | ||||||
|  |         .pipe( | ||||||
|  |           map(([liquidPegs, currentPeg, liquidReserves, currentReserves]) => { | ||||||
|  |             liquidPegs.series[liquidPegs.series.length - 1] = parseFloat(currentPeg.amount) / 100000000; | ||||||
|  | 
 | ||||||
|  |             if (liquidPegs.series.length === liquidReserves?.series.length) { | ||||||
|  |               liquidReserves.series[liquidReserves.series.length - 1] = parseFloat(currentReserves?.amount) / 100000000; | ||||||
|  |             } else if (liquidPegs.series.length === liquidReserves?.series.length + 1) { | ||||||
|  |               liquidReserves.series.push(parseFloat(currentReserves?.amount) / 100000000); | ||||||
|  |             } else { | ||||||
|  |               liquidReserves = { | ||||||
|  |                 series: [], | ||||||
|  |                 labels: [] | ||||||
|  |               }; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return { | ||||||
|  |               liquidPegs, | ||||||
|  |               liquidReserves | ||||||
|  |             }; | ||||||
|  |           }) | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => { |     this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => { | ||||||
|  | |||||||
| @ -76,6 +76,35 @@ export interface LiquidPegs { | |||||||
|   date: string; |   date: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export interface CurrentPegs { | ||||||
|  |   amount: string; | ||||||
|  |   lastBlockUpdate: number; | ||||||
|  |   hash: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface FederationAddress {  | ||||||
|  |   address: string; | ||||||
|  |   balance: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface FederationUtxo { | ||||||
|  |   txid: string; | ||||||
|  |   txindex: number; | ||||||
|  |   bitcoinaddress: string; | ||||||
|  |   amount: number; | ||||||
|  |   blocknumber: number; | ||||||
|  |   blocktime: number; | ||||||
|  |   pegtxid: string; | ||||||
|  |   pegindex: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface AuditStatus { | ||||||
|  |   bitcoinBlocks: number; | ||||||
|  |   bitcoinHeaders: number; | ||||||
|  |   lastBlockAudit: number; | ||||||
|  |   isAuditSynced: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export interface ITranslators { [language: string]: string; } | export interface ITranslators { [language: string]: string; } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; | import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; | ||||||
| import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, | import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, | ||||||
|   PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface'; |   PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo } from '../interfaces/node-api.interface'; | ||||||
| import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs'; | import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs'; | ||||||
| import { StateService } from './state.service'; | import { StateService } from './state.service'; | ||||||
| import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface'; | import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface'; | ||||||
| @ -178,10 +178,42 @@ export class ApiService { | |||||||
|     return this.httpClient.get<RbfTree[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/' + (fullRbf ? 'fullrbf/' : '') + 'replacements/' + (after || '')); |     return this.httpClient.get<RbfTree[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/' + (fullRbf ? 'fullrbf/' : '') + 'replacements/' + (after || '')); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   liquidPegs$(): Observable<CurrentPegs> { | ||||||
|  |     return this.httpClient.get<CurrentPegs>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   listLiquidPegsMonth$(): Observable<LiquidPegs[]> { |   listLiquidPegsMonth$(): Observable<LiquidPegs[]> { | ||||||
|     return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month'); |     return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   liquidReserves$(): Observable<CurrentPegs> { | ||||||
|  |     return this.httpClient.get<CurrentPegs>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   listLiquidReservesMonth$(): Observable<LiquidPegs[]> { | ||||||
|  |     return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/month'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   federationAuditSynced$(): Observable<AuditStatus> { | ||||||
|  |     return this.httpClient.get<AuditStatus>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/status'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   federationAddresses$(): Observable<FederationAddress[]> { | ||||||
|  |     return this.httpClient.get<FederationAddress[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/addresses'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   federationUtxos$(): Observable<FederationUtxo[]> { | ||||||
|  |     return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   federationAddressesOneMonthAgo$(): Observable<FederationAddress[]> { | ||||||
|  |     return this.httpClient.get<FederationAddress[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/addresses/previous-month'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   federationUtxosOneMonthAgo$(): Observable<FederationUtxo[]> { | ||||||
|  |     return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/previous-month'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   listFeaturedAssets$(): Observable<any[]> { |   listFeaturedAssets$(): Observable<any[]> { | ||||||
|     return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured'); |     return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured'); | ||||||
|   } |   } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user