diff --git a/frontend/src/app/components/assets/assets.component.html b/frontend/src/app/components/assets/assets.component.html
index 30c6b7255..4685d7b49 100644
--- a/frontend/src/app/components/assets/assets.component.html
+++ b/frontend/src/app/components/assets/assets.component.html
@@ -18,7 +18,7 @@
-
+
diff --git a/frontend/src/app/components/assets/assets.component.ts b/frontend/src/app/components/assets/assets.component.ts
index f0f081f58..c34463ed7 100644
--- a/frontend/src/app/components/assets/assets.component.ts
+++ b/frontend/src/app/components/assets/assets.component.ts
@@ -1,13 +1,11 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { AssetsService } from '@app/services/assets.service';
-import { environment } from '@environments/environment';
import { UntypedFormGroup } from '@angular/forms';
-import { filter, map, switchMap, take } from 'rxjs/operators';
+import { map, switchMap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
-import { combineLatest, Observable } from 'rxjs';
-import { AssetExtended } from '@interfaces/electrs.interface';
+import { Observable } from 'rxjs';
import { SeoService } from '@app/services/seo.service';
-import { StateService } from '@app/services/state.service';
+import { AssetRegistryItem } from '@interfaces/electrs.interface';
@Component({
selector: 'app-assets',
@@ -17,16 +15,14 @@ import { StateService } from '@app/services/state.service';
standalone: false,
})
export class AssetsComponent implements OnInit {
- nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 4 : 6;
ellipses = window.matchMedia('(max-width: 670px)').matches ? false : true;
- assets: AssetExtended[];
- assetsCache: AssetExtended[];
searchForm: UntypedFormGroup;
- assets$: Observable;
+ assets$: Observable;
page = 1;
+ totalAssets = 0;
error: any;
itemsPerPage: number;
@@ -38,46 +34,23 @@ export class AssetsComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private seoService: SeoService,
- private stateService: StateService,
) { }
ngOnInit() {
this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`);
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
- this.assets$ = combineLatest([
- this.assetsService.getAssetsJson$,
- this.route.queryParams,
- ])
+ this.assets$ = this.route.queryParams
.pipe(
- take(1),
- switchMap(([assets, qp]) => {
- this.assets = assets.array;
-
- return this.route.queryParams
- .pipe(
- filter((queryParams) => {
- const newPage = parseInt(queryParams.page, 10);
- if (newPage !== this.page) {
- return true;
- }
- return false;
- }),
- map((queryParams) => {
- if (queryParams.page) {
- const newPage = parseInt(queryParams.page, 10);
- this.page = newPage;
- } else {
- this.page = 1;
- }
- return '';
- })
- );
- }),
- map(() => {
+ switchMap((queryParams) => {
+ this.page = queryParams.page ? parseInt(queryParams.page, 10) : 1;
const start = (this.page - 1) * this.itemsPerPage;
- return this.assets.slice(start, this.itemsPerPage + start);
- })
+ return this.assetsService.getLiquidAssetsPage$(start, this.itemsPerPage);
+ }),
+ map((result) => {
+ this.totalAssets = result.total;
+ return result.assets;
+ }),
);
}
diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts
index ee4ef08af..a8e6d4483 100644
--- a/frontend/src/app/interfaces/electrs.interface.ts
+++ b/frontend/src/app/interfaces/electrs.interface.ts
@@ -225,6 +225,14 @@ export interface Entity {
domain: string;
}
+export interface AssetRegistryItem {
+ asset_id: string;
+ name: string;
+ ticker?: string;
+ domain?: string;
+ entity?: Entity;
+}
+
interface IssuanceTxin {
txid: string;
vin: number;
diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts
index 3320a52f6..6d51c7f53 100644
--- a/frontend/src/app/services/assets.service.ts
+++ b/frontend/src/app/services/assets.service.ts
@@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
-import { Observable, of } from 'rxjs';
-import { map, shareReplay, switchMap } from 'rxjs/operators';
+import { Observable, of, throwError } from 'rxjs';
+import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
import { StateService } from '@app/services/state.service';
import { environment } from '@environments/environment';
-import { Asset, AssetExtended } from '@interfaces/electrs.interface';
+import { ElectrsApiService } from '@app/services/electrs-api.service';
+import { Asset, AssetExtended, AssetRegistryItem } from '@interfaces/electrs.interface';
@Injectable({
providedIn: 'root'
@@ -15,19 +16,25 @@ export class AssetsService {
getAssetsJson$: Observable<{ array: AssetExtended[]; objects: any}>;
getAssetsMinimalJson$: Observable;
getWorldMapJson$: Observable;
+ registryAvailable = true;
+ private apiBaseUrl = '';
constructor(
private httpClient: HttpClient,
private stateService: StateService,
+ private electrsApiService: ElectrsApiService,
) {
- let apiBaseUrl = '';
- if (!this.stateService.isBrowser) {
- apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
+ 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.stateService.networkChanged$.subscribe(() => {
+ this.registryAvailable = true;
+ });
this.getAssetsJson$ = this.stateService.networkChanged$
.pipe(
- switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)),
+ switchMap(() => this.httpClient.get(`${this.apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)),
map((rawAssets) => {
const assets: AssetExtended[] = Object.values(rawAssets);
@@ -56,7 +63,7 @@ export class AssetsService {
);
this.getAssetsMinimalJson$ = this.stateService.networkChanged$
.pipe(
- switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.minimal.json`)),
+ switchMap(() => this.httpClient.get(`${this.apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.minimal.json`)),
map((assetsMinimal) => {
if (this.stateService.network === 'liquidtestnet') {
// Hard coding the Liquid Testnet native asset
@@ -67,7 +74,7 @@ export class AssetsService {
shareReplay(1),
);
- this.getWorldMapJson$ = this.httpClient.get(apiBaseUrl + '/resources/worldmap.json').pipe(shareReplay());
+ this.getWorldMapJson$ = this.httpClient.get(this.apiBaseUrl + '/resources/worldmap.json').pipe(shareReplay());
}
public enrichLiquidAsset$(asset: Asset): Observable {
@@ -83,4 +90,39 @@ export class AssetsService {
);
}
}
+
+ public getLiquidAssetsPage$(startIndex: number, limit: number): Observable<{ assets: AssetRegistryItem[]; total: number }> {
+ return (this.registryAvailable ? this.electrsApiService.getLiquidAssetsRegistry$(startIndex, limit).pipe(
+ map((response) => {
+ const assets = response.body || [];
+ const total = parseInt(response.headers.get('X-Total-Results') || `${assets.length}`, 10);
+ if (!total && !assets.length) {
+ this.registryAvailable = false;
+ return null;
+ }
+ return { assets, total };
+ }),
+ catchError((error) => {
+ if (![404, 501].includes(error.status)) {
+ return throwError(() => error);
+ }
+ this.registryAvailable = false;
+ return of(null);
+ }),
+ ) : of(null)).pipe(
+ switchMap((registryPage) => registryPage ? of(registryPage) : this.getAssetsJson$.pipe(
+ map((assets) => ({
+ assets: assets.array.slice(startIndex, startIndex + limit),
+ total: assets.array.length,
+ })),
+ )),
+ map((page) => ({
+ ...page,
+ assets: page.assets.map((asset) => ({
+ ...asset,
+ entity: asset.entity || (asset.domain ? { domain: asset.domain } : undefined),
+ })),
+ })),
+ );
+ }
}
diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts
index 831c9f167..687156f44 100644
--- a/frontend/src/app/services/electrs-api.service.ts
+++ b/frontend/src/app/services/electrs-api.service.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
-import { HttpClient, HttpParams } from '@angular/common/http';
+import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { BehaviorSubject, Observable, catchError, filter, from, of, shareReplay, switchMap, take, tap } from 'rxjs';
-import { Transaction, Address, Outspend, Recent, Asset, ScriptHash, AddressTxSummary, Utxo } from '@interfaces/electrs.interface';
+import { Transaction, Address, Outspend, Recent, Asset, ScriptHash, AddressTxSummary, Utxo, AssetRegistryItem } from '@interfaces/electrs.interface';
import { StateService } from '@app/services/state.service';
import { BlockExtended } from '@interfaces/node-api.interface';
import { calcScriptHash$ } from '@app/bitcoin.utils';
@@ -228,6 +228,16 @@ export class ElectrsApiService {
return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId);
}
+ getLiquidAssetsRegistry$(startIndex: number, limit: number): Observable> {
+ const params = new HttpParams()
+ .set('start_index', startIndex)
+ .set('limit', limit)
+ .set('sort_field', 'name')
+ .set('sort_dir', 'asc');
+
+ return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/assets/registry', { params, observe: 'response' });
+ }
+
getAssetTransactions$(assetId: string): Observable {
return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId + '/txs');
}