diff --git a/README.md b/README.md
index 75146d4b1..85cadb729 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,8 @@ JSON:
"MEMPOOL_BLOCKS_AMOUNT": 8,
"PRICE_FEED_UPDATE_INTERVAL": 600,
"USE_SECOND_NODE_FOR_MINFEE": false,
- "EXTERNAL_ASSETS": []
+ "EXTERNAL_ASSETS": ["https://mempool.space/resources/pools.json"],
+ "STDOUT_LOG_MIN_PRIORITY": "debug"
},
```
@@ -111,6 +112,7 @@ docker-compose overrides::
MEMPOOL_PRICE_FEED_UPDATE_INTERVAL: ""
MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: ""
MEMPOOL_EXTERNAL_ASSETS: ""
+ MEMPOOL_STDOUT_LOG_MIN_PRIORITY: ""
```
JSON:
diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json
index dc0f1a69e..5c45838ee 100644
--- a/backend/mempool-config.sample.json
+++ b/backend/mempool-config.sample.json
@@ -17,7 +17,8 @@
"USE_SECOND_NODE_FOR_MINFEE": false,
"EXTERNAL_ASSETS": [
"https://mempool.space/resources/pools.json"
- ]
+ ],
+ "STDOUT_LOG_MIN_PRIORITY": "debug"
},
"CORE_RPC": {
"HOST": "127.0.0.1",
diff --git a/backend/src/config.ts b/backend/src/config.ts
index 040d94bf1..97c3bb32a 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -18,6 +18,7 @@ interface IConfig {
PRICE_FEED_UPDATE_INTERVAL: number;
USE_SECOND_NODE_FOR_MINFEE: boolean;
EXTERNAL_ASSETS: string[];
+ STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
};
ESPLORA: {
REST_API_URL: string;
@@ -94,7 +95,8 @@ const defaults: IConfig = {
'USE_SECOND_NODE_FOR_MINFEE': false,
'EXTERNAL_ASSETS': [
'https://mempool.space/resources/pools.json'
- ]
+ ],
+ 'STDOUT_LOG_MIN_PRIORITY': 'debug',
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',
diff --git a/backend/src/index.ts b/backend/src/index.ts
index f78c5922b..557c269dd 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -319,7 +319,9 @@ class Server {
if (Common.isLiquid()) {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'assets/featured', routes.$getAllFeaturedLiquidAssets)
.get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', routes.$getAssetGroup)
;
}
diff --git a/backend/src/logger.ts b/backend/src/logger.ts
index 4e8c5ea11..43373e043 100644
--- a/backend/src/logger.ts
+++ b/backend/src/logger.ts
@@ -97,6 +97,9 @@ class Logger {
syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${msg}`;
this.syslog(syslogmsg);
}
+ if (Logger.priorities[priority] > Logger.priorities[config.MEMPOOL.STDOUT_LOG_MIN_PRIORITY]) {
+ return;
+ }
if (priority === 'warning') {
priority = 'warn';
}
diff --git a/backend/src/routes.ts b/backend/src/routes.ts
index 044f9a3ac..8ae2f9609 100644
--- a/backend/src/routes.ts
+++ b/backend/src/routes.ts
@@ -21,6 +21,7 @@ import bitcoinClient from './api/bitcoin/bitcoin-client';
import elementsParser from './api/liquid/elements-parser';
import icons from './api/liquid/icons';
import miningStats from './api/mining';
+import axios from 'axios';
class Routes {
constructor() {}
@@ -855,6 +856,25 @@ class Routes {
res.status(404).send('Asset icons not found');
}
}
+
+ public async $getAllFeaturedLiquidAssets(req: Request, res: Response) {
+ try {
+ const response = await axios.get('https://liquid.network/api/v1/assets/featured', { responseType: 'stream', timeout: 10000 });
+ response.data.pipe(res);
+ } catch (e) {
+ res.status(500).end();
+ }
+ }
+
+ public async $getAssetGroup(req: Request, res: Response) {
+ try {
+ const response = await axios.get('https://liquid.network/api/v1/assets/group/' + parseInt(req.params.id, 10),
+ { responseType: 'stream', timeout: 10000 });
+ response.data.pipe(res);
+ } catch (e) {
+ res.status(500).end();
+ }
+ }
}
export default new Routes();
diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json
index b97c9e571..3a0a0ec0b 100644
--- a/docker/backend/mempool-config.json
+++ b/docker/backend/mempool-config.json
@@ -14,7 +14,8 @@
"MEMPOOL_BLOCKS_AMOUNT": __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__,
"PRICE_FEED_UPDATE_INTERVAL": __MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__,
"USE_SECOND_NODE_FOR_MINFEE": __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__,
- "EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__
+ "EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__,
+ "STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__"
},
"CORE_RPC": {
"HOST": "__CORE_RPC_HOST__",
diff --git a/docker/backend/start.sh b/docker/backend/start.sh
index d12978970..019f16217 100644
--- a/docker/backend/start.sh
+++ b/docker/backend/start.sh
@@ -16,7 +16,8 @@ __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=1100}
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=600}
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
-__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
+__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[\"https://mempool.space/resources/pools.json\"]}
+__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=debug}
# CORE_RPC
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
@@ -89,7 +90,8 @@ sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}
sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__/${__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__}/g" mempool-config.json
sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json
-sed -i "s/__MEMPOOL_EXTERNAL_ASSETS__/${__MEMPOOL_EXTERNAL_ASSETS__}/g" mempool-config.json
+sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json
+sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json
sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json
diff --git a/frontend/cypress/integration/liquid/liquid.spec.ts b/frontend/cypress/integration/liquid/liquid.spec.ts
index af76314a1..d8d1c366d 100644
--- a/frontend/cypress/integration/liquid/liquid.spec.ts
+++ b/frontend/cypress/integration/liquid/liquid.spec.ts
@@ -115,17 +115,16 @@ describe('Liquid', () => {
describe('assets', () => {
it('shows the assets screen', () => {
- cy.visit(`${basePath}`);
- cy.get('#btn-assets');
+ cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
- cy.get('table tr').should('have.length.at.least', 5);
+ cy.get('.featuredBox .card').should('have.length.at.least', 5);
});
it('allows searching assets', () => {
cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
- cy.get('table tr').should('have.length', 1);
+ cy.get('ngb-typeahead-window').should('have.length', 1);
});
});
@@ -133,7 +132,7 @@ describe('Liquid', () => {
cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid AUD').then(() => {
- cy.get('table tr td:nth-of-type(1) a').click();
+ cy.get('ngb-typeahead-window:nth-of-type(1) button').click();
});
});
});
@@ -197,7 +196,7 @@ describe('Liquid', () => {
});
it('shows asset peg in/out and burn transactions', () => {
- cy.visit(`${basePath}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
+ cy.visit(`${basePath}/assets/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').not('.assetBox');
cy.get('#table-tx-vin tr').not('.assetBox');
diff --git a/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts b/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts
index eb75be773..90741539b 100644
--- a/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts
+++ b/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts
@@ -73,17 +73,11 @@ describe('Liquid Testnet', () => {
});
describe('assets', () => {
- it('shows the assets screen', () => {
- cy.visit(`${basePath}/assets`);
- cy.waitForSkeletonGone();
- cy.get('table tr').should('have.length.at.least', 5);
- });
-
it('allows searching assets', () => {
cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
- cy.get('table tr').should('have.length', 1);
+ cy.get('ngb-typeahead-window').should('have.length', 1);
});
});
@@ -91,7 +85,7 @@ describe('Liquid Testnet', () => {
cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
- cy.get('table tr td:nth-of-type(1) a').click();
+ cy.get('ngb-typeahead-window:nth-of-type(1) button').click();
});
});
});
@@ -150,7 +144,7 @@ describe('Liquid Testnet', () => {
cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`);
cy.get('#table-tx-vout tr:nth-child(2) .amount a').click().then(() => {
cy.waitForSkeletonGone();
- cy.url().should('contain', '/asset/38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5');
+ cy.url().should('contain', '/assets/asset/38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5');
});
});
@@ -162,7 +156,7 @@ describe('Liquid Testnet', () => {
});
it('shows asset peg in/out and burn transactions', () => {
- cy.visit(`${basePath}/asset/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`);
+ cy.visit(`${basePath}/assets/asset/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').not('.assetBox');
cy.get('#table-tx-vin tr').not('.assetBox');
diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index 36a53781f..aaf545206 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -10,7 +10,7 @@ import { TelevisionComponent } from './components/television/television.componen
import { StatisticsComponent } from './components/statistics/statistics.component';
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
import { AssetComponent } from './components/asset/asset.component';
-import { AssetsComponent } from './assets/assets.component';
+import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component';
import { StatusViewComponent } from './components/status-view/status-view.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
@@ -23,6 +23,9 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
+import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
+import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
+import { AssetsComponent } from './components/assets/assets.component';
let routes: Routes = [
{
@@ -343,13 +346,31 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
path: 'address/:id',
component: AddressComponent
},
- {
- path: 'asset/:id',
- component: AssetComponent
- },
{
path: 'assets',
- component: AssetsComponent,
+ component: AssetsNavComponent,
+ children: [
+ {
+ path: 'featured',
+ component: AssetsFeaturedComponent,
+ },
+ {
+ path: 'all',
+ component: AssetsComponent,
+ },
+ {
+ path: 'asset/:id',
+ component: AssetComponent
+ },
+ {
+ path: 'group/:id',
+ component: AssetGroupComponent
+ },
+ {
+ path: '**',
+ redirectTo: 'featured'
+ }
+ ]
},
{
path: 'docs/api/:type',
@@ -434,13 +455,27 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
path: 'address/:id',
component: AddressComponent
},
- {
- path: 'asset/:id',
- component: AssetComponent
- },
{
path: 'assets',
- component: AssetsComponent,
+ component: AssetsNavComponent,
+ children: [
+ {
+ path: 'all',
+ component: AssetsComponent,
+ },
+ {
+ path: 'asset/:id',
+ component: AssetComponent
+ },
+ {
+ path: 'group/:id',
+ component: AssetGroupComponent
+ },
+ {
+ path: '**',
+ redirectTo: 'all'
+ }
+ ]
},
{
path: 'docs/api/:type',
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index f9eae0666..97fc16204 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -40,7 +40,8 @@ import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component';
import { AssetComponent } from './components/asset/asset.component';
-import { AssetsComponent } from './assets/assets.component';
+import { AssetsComponent } from './components/assets/assets.component';
+import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component';
import { StatusViewComponent } from './components/status-view/status-view.component';
import { MinerComponent } from './components/miner/miner.component';
import { SharedModule } from './shared/shared.module';
@@ -64,6 +65,8 @@ import { LanguageService } from './services/language.service';
import { SponsorComponent } from './components/sponsor/sponsor.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
+import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
@NgModule({
declarations: [
@@ -110,6 +113,9 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
PushTransactionComponent,
DocsComponent,
ApiDocsNavComponent,
+ AssetsNavComponent,
+ AssetsFeaturedComponent,
+ AssetGroupComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
diff --git a/frontend/src/app/assets/assets.component.html b/frontend/src/app/assets/assets.component.html
deleted file mode 100644
index c8962cd15..000000000
--- a/frontend/src/app/assets/assets.component.html
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
Registered assets
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Name |
- Ticker |
- Issuer domain |
- Asset ID |
-
-
-
- |
- |
- |
- |
-
-
-
-
-
-
-
-
- Error loading assets data.
-
- {{ error.error }}
-
-
-
-
-
-
diff --git a/frontend/src/app/assets/assets.component.spec.ts b/frontend/src/app/assets/assets.component.spec.ts
deleted file mode 100644
index ed39b7122..000000000
--- a/frontend/src/app/assets/assets.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { AssetsComponent } from './assets.component';
-
-describe('AssetsComponent', () => {
- let component: AssetsComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ AssetsComponent ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AssetsComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/frontend/src/app/assets/assets.component.ts b/frontend/src/app/assets/assets.component.ts
deleted file mode 100644
index 49c42d76e..000000000
--- a/frontend/src/app/assets/assets.component.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
-import { AssetsService } from '../services/assets.service';
-import { environment } from 'src/environments/environment';
-import { FormGroup, FormBuilder, Validators } from '@angular/forms';
-import { distinctUntilChanged, map, filter, mergeMap, tap, take } from 'rxjs/operators';
-import { ActivatedRoute, Router } from '@angular/router';
-import { merge, combineLatest, Observable } from 'rxjs';
-import { AssetExtended } from '../interfaces/electrs.interface';
-import { SeoService } from '../services/seo.service';
-import { StateService } from '../services/state.service';
-
-@Component({
- selector: 'app-assets',
- templateUrl: './assets.component.html',
- styleUrls: ['./assets.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class AssetsComponent implements OnInit {
- nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
-
- assets: AssetExtended[];
- assetsCache: AssetExtended[];
- searchForm: FormGroup;
- assets$: Observable;
-
- error: any;
-
- page = 1;
- itemsPerPage: number;
- contentSpace = window.innerHeight - (250 + 200);
- fiveItemsPxSize = 250;
-
- constructor(
- private assetsService: AssetsService,
- private formBuilder: FormBuilder,
- 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.searchForm = this.formBuilder.group({
- searchText: [{ value: '', disabled: true }, Validators.required]
- });
-
- this.assets$ = combineLatest([
- this.assetsService.getAssetsJson$,
- this.route.queryParams
- ])
- .pipe(
- take(1),
- mergeMap(([assets, qp]) => {
- this.assets = Object.values(assets);
- if (this.stateService.network === 'liquid') {
- // @ts-ignore
- this.assets.push({
- name: 'Liquid Bitcoin',
- ticker: 'L-BTC',
- asset_id: this.nativeAssetId,
- });
- } else if (this.stateService.network === 'liquidtestnet') {
- // @ts-ignore
- this.assets.push({
- name: 'Test Liquid Bitcoin',
- ticker: 'tL-BTC',
- asset_id: this.nativeAssetId,
- });
- }
-
- this.assets = this.assets.sort((a: any, b: any) => a.name.localeCompare(b.name));
- this.assetsCache = this.assets;
- this.searchForm.get('searchText').enable();
-
- if (qp.search) {
- this.searchForm.get('searchText').setValue(qp.search, { emitEvent: false });
- }
-
- return merge(
- this.searchForm.get('searchText').valueChanges
- .pipe(
- distinctUntilChanged(),
- tap((text) => {
- this.page = 1;
- this.searchTextChanged(text);
- })
- ),
- this.route.queryParams
- .pipe(
- filter((queryParams) => {
- const newPage = parseInt(queryParams.page, 10);
- if (newPage !== this.page || queryParams.search !== this.searchForm.get('searchText').value) {
- return true;
- }
- return false;
- }),
- map((queryParams) => {
- if (queryParams.page) {
- const newPage = parseInt(queryParams.page, 10);
- this.page = newPage;
- } else {
- this.page = 1;
- }
- if (this.searchForm.get('searchText').value !== (queryParams.search || '')) {
- this.searchTextChanged(queryParams.search);
- }
- if (queryParams.search) {
- this.searchForm.get('searchText').setValue(queryParams.search, { emitEvent: false });
- return queryParams.search;
- }
- return '';
- })
- ),
- );
- }),
- map((searchText) => {
- const start = (this.page - 1) * this.itemsPerPage;
- if (searchText.length ) {
- const filteredAssets = this.assetsCache.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
- || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1);
- this.assets = filteredAssets;
- return filteredAssets.slice(start, this.itemsPerPage + start);
- } else {
- this.assets = this.assetsCache;
- return this.assets.slice(start, this.itemsPerPage + start);
- }
- })
- );
- }
-
- pageChange(page: number) {
- const queryParams = { page: page, search: this.searchForm.get('searchText').value };
- if (queryParams.search === '') {
- queryParams.search = null;
- }
- if (queryParams.page === 1) {
- queryParams.page = null;
- }
- this.page = -1;
- this.router.navigate([], {
- relativeTo: this.route,
- queryParams: queryParams,
- queryParamsHandling: 'merge',
- });
- }
-
- searchTextChanged(text: string) {
- const queryParams = { search: text, page: 1 };
- if (queryParams.search === '') {
- queryParams.search = null;
- }
- if (queryParams.page === 1) {
- queryParams.page = null;
- }
- this.router.navigate([], {
- relativeTo: this.route,
- queryParams: queryParams,
- queryParamsHandling: 'merge',
- });
- }
-
- trackByAsset(index: number, asset: any) {
- return asset.asset_id;
- }
-}
diff --git a/frontend/src/app/components/asset/asset.component.html b/frontend/src/app/components/asset/asset.component.html
index 9723a45e5..b1728a0ff 100644
--- a/frontend/src/app/components/asset/asset.component.html
+++ b/frontend/src/app/components/asset/asset.component.html
@@ -2,7 +2,7 @@
Asset
-
+
{{ assetString | shortenString : 24 }}
{{ assetString }}
@@ -20,7 +20,7 @@
- Name |
+ Name |
{{ assetContract[2] }} ({{ assetContract[1] }}) |
diff --git a/frontend/src/app/components/asset/asset.component.ts b/frontend/src/app/components/asset/asset.component.ts
index ecb216052..e57bbee7a 100644
--- a/frontend/src/app/components/asset/asset.component.ts
+++ b/frontend/src/app/components/asset/asset.component.ts
@@ -63,6 +63,7 @@ export class AssetComponent implements OnInit, OnDestroy {
.pipe(
switchMap((params: ParamMap) => {
this.error = undefined;
+ this.imageError = false;
this.isLoadingAsset = true;
this.loadedConfirmedTxCount = 0;
this.asset = null;
diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.html b/frontend/src/app/components/assets/asset-group/asset-group.component.html
new file mode 100644
index 000000000..ac0ed4327
--- /dev/null
+++ b/frontend/src/app/components/assets/asset-group/asset-group.component.html
@@ -0,0 +1,35 @@
+
+
+
+
{{ group.group.name }}
+
+
Group of {{ group.group.assets.length | number }} assets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ asset.ticker }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.scss b/frontend/src/app/components/assets/asset-group/asset-group.component.scss
new file mode 100644
index 000000000..c0b31f273
--- /dev/null
+++ b/frontend/src/app/components/assets/asset-group/asset-group.component.scss
@@ -0,0 +1,60 @@
+.image {
+ width: 150px;
+ float: left;
+}
+
+.main-title {
+ float: left
+}
+
+.sub-title {
+ color: grey;
+}
+
+.featuredBox {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: center;
+ gap: 27px;
+}
+
+.card {
+ background-color: #1d1f31;
+ width: 200px;
+ height: 200px;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ @media (max-width: 767.98px) {
+ width: 150px;
+ height: 150px;
+ }
+}
+
+.title {
+ font-size: 14px;
+ font-weight: bold;
+ margin-top: 10px;
+ text-align: center;
+}
+
+.sub-title {
+ color: grey;
+}
+
+.assetIcon {
+ width: 100px;
+ height: 100px;
+ @media (max-width: 767.98px) {
+ width: 50px;
+ height: 50px;
+ }
+}
+
+.view-link {
+ margin-top: 30px;
+}
+
+.ticker {
+ color: grey;
+}
diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.ts b/frontend/src/app/components/assets/asset-group/asset-group.component.ts
new file mode 100644
index 000000000..29cb10dc7
--- /dev/null
+++ b/frontend/src/app/components/assets/asset-group/asset-group.component.ts
@@ -0,0 +1,44 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, ParamMap } from '@angular/router';
+import { combineLatest, Observable } from 'rxjs';
+import { map, switchMap } from 'rxjs/operators';
+import { ApiService } from 'src/app/services/api.service';
+import { AssetsService } from 'src/app/services/assets.service';
+
+@Component({
+ selector: 'app-asset-group',
+ templateUrl: './asset-group.component.html',
+ styleUrls: ['./asset-group.component.scss']
+})
+export class AssetGroupComponent implements OnInit {
+ group$: Observable;
+
+ constructor(
+ private route: ActivatedRoute,
+ private apiService: ApiService,
+ private assetsService: AssetsService,
+ ) { }
+
+ ngOnInit(): void {
+ this.group$ = this.route.paramMap
+ .pipe(
+ switchMap((params: ParamMap) => {
+ return combineLatest([
+ this.assetsService.getAssetsJson$,
+ this.apiService.getAssetGroup$(params.get('id')),
+ ]);
+ }),
+ map(([assets, group]) => {
+ const items = [];
+ // @ts-ignore
+ for (const item of group.assets) {
+ items.push(assets.objects[item]);
+ }
+ return {
+ group: group,
+ assets: items
+ };
+ })
+ );
+ }
+}
diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.html b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html
new file mode 100644
index 000000000..b87713ceb
--- /dev/null
+++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+ Group of {{ group.assets.length | number }} assets
+
+
+
+
+
+
+ {{ group.ticker }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.scss b/frontend/src/app/components/assets/assets-featured/assets-featured.component.scss
new file mode 100644
index 000000000..0ff049d16
--- /dev/null
+++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.scss
@@ -0,0 +1,49 @@
+
+.featuredBox {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: center;
+ gap: 27px;
+}
+
+.card {
+ background-color: #1d1f31;
+ width: 200px;
+ height: 200px;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ @media (max-width: 767.98px) {
+ width: 150px;
+ height: 150px;
+ }
+}
+
+.title {
+ font-size: 14px;
+ font-weight: bold;
+ margin-top: 10px;
+ text-align: center;
+}
+
+.sub-title {
+ color: grey;
+ font-size: 12px;
+}
+
+.assetIcon {
+ width: 100px;
+ height: 100px;
+ @media (max-width: 767.98px) {
+ width: 50px;
+ height: 50px;
+ }
+}
+
+.view-link {
+ margin-top: 30px;
+}
+
+.ticker {
+ color: grey;
+}
diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts b/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts
new file mode 100644
index 000000000..db16a8f2b
--- /dev/null
+++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts
@@ -0,0 +1,21 @@
+import { Component, OnInit } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiService } from 'src/app/services/api.service';
+
+@Component({
+ selector: 'app-assets-featured',
+ templateUrl: './assets-featured.component.html',
+ styleUrls: ['./assets-featured.component.scss']
+})
+export class AssetsFeaturedComponent implements OnInit {
+ featuredAssets$: Observable;
+
+ constructor(
+ private apiService: ApiService,
+ ) { }
+
+ ngOnInit(): void {
+ this.featuredAssets$ = this.apiService.listFeaturedAssets$();
+ }
+
+}
diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.html b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html
new file mode 100644
index 000000000..420fff818
--- /dev/null
+++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html
@@ -0,0 +1,33 @@
+
+
+
Assets
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.scss b/frontend/src/app/components/assets/assets-nav/assets-nav.component.scss
new file mode 100644
index 000000000..21ab756d9
--- /dev/null
+++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.scss
@@ -0,0 +1,24 @@
+ul {
+ margin-bottom: 20px;
+ float: left;
+
+}
+
+form {
+ float: right;
+ width: 300px;
+ @media (max-width: 767.98px) {
+ width: 90%;
+ margin-bottom: 15px;
+ }
+}
+
+@media (max-width: 767.98px) {
+ .nav-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin: auto;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts
new file mode 100644
index 000000000..ac8dded67
--- /dev/null
+++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts
@@ -0,0 +1,95 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { merge, Observable, of, Subject } from 'rxjs';
+import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
+import { AssetExtended } from 'src/app/interfaces/electrs.interface';
+import { AssetsService } from 'src/app/services/assets.service';
+import { SeoService } from 'src/app/services/seo.service';
+import { StateService } from 'src/app/services/state.service';
+import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
+import { environment } from 'src/environments/environment';
+
+@Component({
+ selector: 'app-assets-nav',
+ templateUrl: './assets-nav.component.html',
+ styleUrls: ['./assets-nav.component.scss']
+})
+export class AssetsNavComponent implements OnInit {
+ @ViewChild('instance', {static: true}) instance: NgbTypeahead;
+ nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
+ searchForm: FormGroup;
+ assetsCache: AssetExtended[];
+
+ typeaheadSearchFn: ((text: Observable) => Observable);
+ formatterFn = (asset: AssetExtended) => asset.name + ' (' + asset.ticker + ')';
+ focus$ = new Subject();
+ click$ = new Subject();
+
+ itemsPerPage = 15;
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private seoService: SeoService,
+ private router: Router,
+ private assetsService: AssetsService,
+ private stateService: StateService,
+ private relativeUrlPipe: RelativeUrlPipe,
+ ) { }
+
+ ngOnInit(): void {
+ this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`);
+ this.typeaheadSearchFn = this.typeaheadSearch;
+
+ this.searchForm = this.formBuilder.group({
+ searchText: [{ value: '', disabled: false }, Validators.required]
+ });
+ }
+
+ typeaheadSearch = (text$: Observable) => {
+ const debouncedText$ = text$.pipe(
+ distinctUntilChanged()
+ );
+ const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
+ const inputFocus$ = this.focus$;
+
+ return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$)
+ .pipe(
+ switchMap((searchText) => {
+ if (!searchText.length) {
+ return of([]);
+ }
+ return this.assetsService.getAssetsJson$.pipe(
+ map((assets) => {
+ if (searchText.length ) {
+ const filteredAssets = assets.array.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
+ || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1
+ || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1);
+ return filteredAssets.slice(0, this.itemsPerPage);
+ } else {
+ return assets.array.slice(0, this.itemsPerPage);
+ }
+ })
+ )
+ }),
+ );
+ }
+
+ itemSelected() {
+ setTimeout(() => this.search());
+ }
+
+ search() {
+ const searchText = this.searchForm.value.searchText;
+ this.navigate('/assets/asset/', searchText.asset_id);
+ }
+
+ navigate(url: string, searchText: string, extras?: any) {
+ this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras);
+ this.searchForm.setValue({
+ searchText: '',
+ });
+ }
+
+}
diff --git a/frontend/src/app/components/assets/assets.component.html b/frontend/src/app/components/assets/assets.component.html
new file mode 100644
index 000000000..d5accc9d7
--- /dev/null
+++ b/frontend/src/app/components/assets/assets.component.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name |
+ Ticker |
+ Issuer domain |
+ Asset ID |
+
+
+
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+ Error loading assets data.
+
+ {{ error.error }}
+
+
diff --git a/frontend/src/app/assets/assets.component.scss b/frontend/src/app/components/assets/assets.component.scss
similarity index 100%
rename from frontend/src/app/assets/assets.component.scss
rename to frontend/src/app/components/assets/assets.component.scss
diff --git a/frontend/src/app/components/assets/assets.component.ts b/frontend/src/app/components/assets/assets.component.ts
new file mode 100644
index 000000000..e85248dd6
--- /dev/null
+++ b/frontend/src/app/components/assets/assets.component.ts
@@ -0,0 +1,99 @@
+import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
+import { AssetsService } from 'src/app/services/assets.service';
+import { environment } from 'src/environments/environment';
+import { FormGroup } from '@angular/forms';
+import { filter, map, switchMap, take } from 'rxjs/operators';
+import { ActivatedRoute, Router } from '@angular/router';
+import { combineLatest, Observable } from 'rxjs';
+import { AssetExtended } from 'src/app/interfaces/electrs.interface';
+import { SeoService } from 'src/app/services/seo.service';
+import { StateService } from 'src/app/services/state.service';
+
+@Component({
+ selector: 'app-assets',
+ templateUrl: './assets.component.html',
+ styleUrls: ['./assets.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+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: FormGroup;
+ assets$: Observable;
+
+ page = 1;
+ error: any;
+
+ itemsPerPage: number;
+ contentSpace = window.innerHeight - (250 + 200);
+ fiveItemsPxSize = 250;
+
+ constructor(
+ private assetsService: AssetsService,
+ 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,
+ ])
+ .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(() => {
+ const start = (this.page - 1) * this.itemsPerPage;
+ return this.assets.slice(start, this.itemsPerPage + start);
+ })
+ );
+ }
+
+ pageChange(page: number) {
+ const queryParams = { page: page };
+ if (queryParams.page === 1) {
+ queryParams.page = null;
+ }
+ this.page = -1;
+ this.router.navigate([], {
+ relativeTo: this.route,
+ queryParams: queryParams,
+ queryParamsHandling: 'merge',
+ });
+ }
+
+ trackByAsset(index: number, asset: any) {
+ return asset.asset_id;
+ }
+}
diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index 40f2c1e88..24e8a5b23 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -105,11 +105,11 @@ export class SearchFormComponent implements OnInit {
const matches = this.regexTransaction.exec(searchText);
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.assets[matches[1]]) {
- this.navigate('/asset/', matches[1]);
+ this.navigate('/assets/asset/', matches[1]);
}
this.electrsApiService.getAsset$(matches[1])
.subscribe(
- () => { this.navigate('/asset/', matches[1]); },
+ () => { this.navigate('/assets/asset/', matches[1]); },
() => {
this.electrsApiService.getBlock$(matches[1])
.subscribe(
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html
index 91d9feffe..8e81cc3e7 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.html
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.html
@@ -274,5 +274,5 @@
{{ assetsMinimal[item.asset][0] }}
- {{ item.asset | shortenString : 13 }}
+ {{ item.asset | shortenString : 13 }}
diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts
index 03200c64c..c19bf5a41 100644
--- a/frontend/src/app/services/api.service.ts
+++ b/frontend/src/app/services/api.service.ts
@@ -117,6 +117,14 @@ export class ApiService {
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'});
}
diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts
index 260a48b7b..9454ef7e2 100644
--- a/frontend/src/app/services/assets.service.ts
+++ b/frontend/src/app/services/assets.service.ts
@@ -3,12 +3,16 @@ import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, shareReplay, switchMap } from 'rxjs/operators';
import { StateService } from './state.service';
+import { environment } from 'src/environments/environment';
+import { AssetExtended } from '../interfaces/electrs.interface';
@Injectable({
providedIn: 'root'
})
export class AssetsService {
- getAssetsJson$: Observable;
+ nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
+
+ getAssetsJson$: Observable<{ array: AssetExtended[]; objects: any}>;
getAssetsMinimalJson$: Observable;
getMiningPools$: Observable;
@@ -24,6 +28,30 @@ export class AssetsService {
this.getAssetsJson$ = this.stateService.networkChanged$
.pipe(
switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)),
+ map((rawAssets) => {
+ const assets: AssetExtended[] = Object.values(rawAssets);
+
+ if (this.stateService.network === 'liquid') {
+ // @ts-ignore
+ assets.push({
+ name: 'Liquid Bitcoin',
+ ticker: 'L-BTC',
+ asset_id: this.nativeAssetId,
+ });
+ } else if (this.stateService.network === 'liquidtestnet') {
+ // @ts-ignore
+ assets.push({
+ name: 'Test Liquid Bitcoin',
+ ticker: 'tL-BTC',
+ asset_id: this.nativeAssetId,
+ });
+ }
+
+ return {
+ objects: rawAssets,
+ array: assets.sort((a: any, b: any) => a.name.localeCompare(b.name)),
+ };
+ }),
shareReplay(1),
);
this.getAssetsMinimalJson$ = this.stateService.networkChanged$
diff --git a/production/nginx/location-api-v1-services.conf b/production/nginx/location-api-v1-services.conf
index 7079dd4ac..aae49727e 100644
--- a/production/nginx/location-api-v1-services.conf
+++ b/production/nginx/location-api-v1-services.conf
@@ -70,3 +70,15 @@ location /api/v1/translators {
proxy_hide_header content-security-policy;
proxy_hide_header x-frame-options;
}
+location /api/v1/assets {
+ proxy_pass $mempoolSpaceServices;
+ proxy_cache services;
+ proxy_cache_background_update on;
+ proxy_cache_use_stale updating;
+ proxy_cache_valid 200 10m;
+ expires 10m;
+ proxy_hide_header onion-location;
+ proxy_hide_header strict-transport-security;
+ proxy_hide_header content-security-policy;
+ proxy_hide_header x-frame-options;
+}