diff --git a/backend/.gitignore b/backend/.gitignore
index c4339712c..5476d633c 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -1,7 +1,10 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
-# production config
-mempool-config.json
+# production config and external assets
+*.json
+!mempool-config.sample.json
+
+icons.json
# compiled output
/dist
diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json
index 6ff103bf1..ea656c1de 100644
--- a/backend/mempool-config.sample.json
+++ b/backend/mempool-config.sample.json
@@ -13,7 +13,8 @@
"INITIAL_BLOCKS_AMOUNT": 8,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"PRICE_FEED_UPDATE_INTERVAL": 3600,
- "USE_SECOND_NODE_FOR_MINFEE": false
+ "USE_SECOND_NODE_FOR_MINFEE": false,
+ "EXTERNAL_ASSETS": []
},
"CORE_RPC": {
"HOST": "127.0.0.1",
diff --git a/backend/src/api/liquid/icons.ts b/backend/src/api/liquid/icons.ts
new file mode 100644
index 000000000..1c7c658af
--- /dev/null
+++ b/backend/src/api/liquid/icons.ts
@@ -0,0 +1,39 @@
+import * as fs from 'fs';
+import config from '../../config';
+import logger from '../../logger';
+
+class Icons {
+ private static FILE_NAME = './icons.json';
+ private iconIds: string[] = [];
+ private icons: { [assetId: string]: string; } = {};
+
+ constructor() {}
+
+ public loadIcons() {
+ if (!fs.existsSync(Icons.FILE_NAME)) {
+ logger.warn(`${Icons.FILE_NAME} does not exist. No Liquid icons loaded.`);
+ return;
+ }
+ const cacheData = fs.readFileSync(Icons.FILE_NAME, 'utf8');
+ this.icons = JSON.parse(cacheData);
+
+ for (const i in this.icons) {
+ this.iconIds.push(i);
+ }
+ logger.debug(`Liquid icons has been loaded.`);
+ }
+
+ public getIconByAssetId(assetId: string): Buffer | undefined {
+ const icon = this.icons[assetId];
+ if (icon) {
+ return Buffer.from(icon, 'base64');
+ }
+ }
+
+ public getAllIconIds() {
+ return this.iconIds;
+ }
+
+}
+
+export default new Icons();
diff --git a/backend/src/config.ts b/backend/src/config.ts
index 2addc176a..9513324d8 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -16,6 +16,7 @@ interface IConfig {
MEMPOOL_BLOCKS_AMOUNT: number;
PRICE_FEED_UPDATE_INTERVAL: number;
USE_SECOND_NODE_FOR_MINFEE: boolean;
+ EXTERNAL_ASSETS: string[];
};
ESPLORA: {
REST_API_URL: string;
@@ -78,6 +79,7 @@ const defaults: IConfig = {
'MEMPOOL_BLOCKS_AMOUNT': 8,
'PRICE_FEED_UPDATE_INTERVAL': 3600,
'USE_SECOND_NODE_FOR_MINFEE': false,
+ 'EXTERNAL_ASSETS': [],
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',
diff --git a/backend/src/index.ts b/backend/src/index.ts
index 3a3bdb197..d1bd86994 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -21,6 +21,8 @@ import backendInfo from './api/backend-info';
import loadingIndicators from './api/loading-indicators';
import mempool from './api/mempool';
import elementsParser from './api/liquid/elements-parser';
+import syncAssets from './sync-assets';
+import icons from './api/liquid/icons';
class Server {
private wss: WebSocket.Server | undefined;
@@ -77,6 +79,7 @@ class Server {
this.setUpWebsocketHandling();
+ await syncAssets.syncAssets();
diskCache.loadMempoolCache();
if (config.DATABASE.ENABLED) {
@@ -87,6 +90,10 @@ class Server {
statistics.startStatistics();
}
+ if (config.MEMPOOL.NETWORK === 'liquid') {
+ icons.loadIcons();
+ }
+
fiatConversion.startService();
this.setUpHttpApiRoutes();
@@ -270,6 +277,13 @@ class Server {
;
}
+ if (config.MEMPOOL.NETWORK === 'liquid') {
+ this.app
+ .get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
+ ;
+ }
+
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
diff --git a/backend/src/routes.ts b/backend/src/routes.ts
index 06cebeae1..53387511f 100644
--- a/backend/src/routes.ts
+++ b/backend/src/routes.ts
@@ -19,6 +19,7 @@ import loadingIndicators from './api/loading-indicators';
import { Common } from './api/common';
import bitcoinClient from './api/bitcoin/bitcoin-client';
import elementsParser from './api/liquid/elements-parser';
+import icons from './api/liquid/icons';
class Routes {
constructor() {}
@@ -807,6 +808,26 @@ class Routes {
: (e.message || 'Error'));
}
}
+
+ public getLiquidIcon(req: Request, res: Response) {
+ const result = icons.getIconByAssetId(req.params.assetId);
+ if (result) {
+ res.setHeader('content-type', 'image/png');
+ res.setHeader('content-length', result.length);
+ res.send(result);
+ } else {
+ res.status(404).send('Asset icon not found');
+ }
+ }
+
+ public getAllLiquidIcon(req: Request, res: Response) {
+ const result = icons.getAllIconIds();
+ if (result) {
+ res.json(result);
+ } else {
+ res.status(404).send('Asset icons not found');
+ }
+ }
}
export default new Routes();
diff --git a/backend/src/sync-assets.ts b/backend/src/sync-assets.ts
new file mode 100644
index 000000000..302196458
--- /dev/null
+++ b/backend/src/sync-assets.ts
@@ -0,0 +1,32 @@
+import axios from 'axios';
+import * as fs from 'fs';
+const fsPromises = fs.promises;
+import config from './config';
+import logger from './logger';
+
+const PATH = './';
+
+class SyncAssets {
+ constructor() { }
+
+ public async syncAssets() {
+ for (const url of config.MEMPOOL.EXTERNAL_ASSETS) {
+ await this.downloadFile(url);
+ }
+ }
+
+ private async downloadFile(url: string) {
+ const fileName = url.split('/').slice(-1)[0];
+ logger.info(`Downloading external asset: ${fileName}...`);
+ try {
+ const response = await axios.get(url, {
+ responseType: 'stream', timeout: 30000
+ });
+ await fsPromises.writeFile(PATH + fileName, response.data);
+ } catch (e: any) {
+ throw new Error(`Failed to download external asset. ` + e);
+ }
+ }
+}
+
+export default new SyncAssets();
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 717134fca..8aa1c675e 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -26,7 +26,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@juggle/resize-observer": "^3.3.1",
- "@mempool/mempool.js": "^2.2.4",
+ "@mempool/mempool.js": "2.3.0-dev1",
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
"@nguniversal/express-engine": "11.2.1",
"@types/qrcode": "1.4.1",
@@ -3230,12 +3230,40 @@
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"node_modules/@mempool/mempool.js": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
- "integrity": "sha512-G9Ga2jHLfAuU/qXikRBtTecYr7BhLJH1WbIahefnGpgP48DCQaj1jizvuRZHhoElUvUT5flRt/O9kLjlbToqhw==",
+ "version": "2.3.0-dev1",
+ "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.3.0-dev1.tgz",
+ "integrity": "sha512-+UYGuG8qqdgrtC4J94hCs7+Dry8OprIixEarIC6rww1Nb5POz8n3NTDH8to1r0XLjPr+Du6/OX8fEN1QW94rNA==",
"dependencies": {
- "axios": "^0.21.1",
- "ws": "^7.4.3"
+ "axios": "0.24.0",
+ "ws": "8.3.0"
+ }
+ },
+ "node_modules/@mempool/mempool.js/node_modules/axios": {
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
+ "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
+ "dependencies": {
+ "follow-redirects": "^1.14.4"
+ }
+ },
+ "node_modules/@mempool/mempool.js/node_modules/ws": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
+ "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
}
},
"node_modules/@ng-bootstrap/ng-bootstrap": {
@@ -4541,6 +4569,7 @@
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+ "devOptional": true,
"dependencies": {
"follow-redirects": "^1.14.0"
}
@@ -7264,7 +7293,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "devOptional": true,
+ "dev": true,
"engines": {
"node": ">=0.3.1"
}
@@ -9122,7 +9151,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
- "devOptional": true,
+ "dev": true,
"engines": {
"node": ">=4"
}
@@ -9131,7 +9160,7 @@
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
@@ -9416,7 +9445,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -10385,7 +10414,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
- "devOptional": true
+ "dev": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
@@ -10453,7 +10482,7 @@
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -17962,6 +17991,7 @@
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+ "devOptional": true,
"engines": {
"node": ">=8.3.0"
}
@@ -20409,12 +20439,28 @@
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"@mempool/mempool.js": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
- "integrity": "sha512-G9Ga2jHLfAuU/qXikRBtTecYr7BhLJH1WbIahefnGpgP48DCQaj1jizvuRZHhoElUvUT5flRt/O9kLjlbToqhw==",
+ "version": "2.3.0-dev1",
+ "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.3.0-dev1.tgz",
+ "integrity": "sha512-+UYGuG8qqdgrtC4J94hCs7+Dry8OprIixEarIC6rww1Nb5POz8n3NTDH8to1r0XLjPr+Du6/OX8fEN1QW94rNA==",
"requires": {
- "axios": "^0.21.1",
- "ws": "^7.4.3"
+ "axios": "0.24.0",
+ "ws": "8.3.0"
+ },
+ "dependencies": {
+ "axios": {
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
+ "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
+ "requires": {
+ "follow-redirects": "^1.14.4"
+ }
+ },
+ "ws": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
+ "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
+ "requires": {}
+ }
}
},
"@ng-bootstrap/ng-bootstrap": {
@@ -21532,6 +21578,7 @@
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+ "devOptional": true,
"requires": {
"follow-redirects": "^1.14.0"
}
@@ -23795,7 +23842,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "devOptional": true
+ "dev": true
},
"diffie-hellman": {
"version": "5.0.3",
@@ -25290,13 +25337,13 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
- "devOptional": true
+ "dev": true
},
"har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
- "devOptional": true,
+ "dev": true,
"requires": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
@@ -25544,7 +25591,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "devOptional": true,
+ "dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -26286,7 +26333,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
- "devOptional": true
+ "dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -26339,7 +26386,7 @@
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
- "devOptional": true,
+ "dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -32158,7 +32205,8 @@
"ws": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
- "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
+ "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+ "devOptional": true
},
"xhr2": {
"version": "0.2.0",
diff --git a/frontend/package.json b/frontend/package.json
index 91f7f3682..f25f8bc32 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -34,7 +34,10 @@
"sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
"sync-assets-dev": "node sync-assets.js dev",
"generate-config": "node generate-config.js",
- "build-mempool.js": "tsc | browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
+ "build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
+ "build-mempool-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
+ "build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
+ "build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
"test": "ng test",
"lint": "ng lint",
"e2e": "npm run generate-config && ng e2e",
@@ -70,7 +73,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@juggle/resize-observer": "^3.3.1",
- "@mempool/mempool.js": "^2.2.4",
+ "@mempool/mempool.js": "2.3.0-dev1",
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
"@nguniversal/express-engine": "11.2.1",
"@types/qrcode": "1.4.1",
diff --git a/frontend/src/app/components/docs/api-docs-nav.component.html b/frontend/src/app/components/docs/api-docs-nav.component.html
index a4da93428..63ed8d4e1 100644
--- a/frontend/src/app/components/docs/api-docs-nav.component.html
+++ b/frontend/src/app/components/docs/api-docs-nav.component.html
@@ -30,8 +30,10 @@
Assets
Blocks
diff --git a/frontend/src/app/components/docs/api-docs-nav.component.ts b/frontend/src/app/components/docs/api-docs-nav.component.ts index 967b355f3..5109e2dc0 100644 --- a/frontend/src/app/components/docs/api-docs-nav.component.ts +++ b/frontend/src/app/components/docs/api-docs-nav.component.ts @@ -8,7 +8,7 @@ import { Component, OnInit, Input } from '@angular/core'; export class ApiDocsNavComponent implements OnInit { @Input() network: any; - @Input() collapseItem: any; + @Input() collapseItem: any = { toggle: () => {} }; constructor() { } diff --git a/frontend/src/app/components/docs/api-docs.component.html b/frontend/src/app/components/docs/api-docs.component.html index 8358129f5..c41bc57c7 100644 --- a/frontend/src/app/components/docs/api-docs.component.html +++ b/frontend/src/app/components/docs/api-docs.component.html @@ -275,6 +275,32 @@