Compare commits

...

54 Commits

Author SHA1 Message Date
wiz
898ff5da23 Merge pull request #1093 from mempool/simon/transifex-pull
Transifex pull
2022-01-06 08:33:58 +00:00
softsimon
d78d2c0eca Transifex pull 2022-01-06 12:32:08 +04:00
wiz
a08e77ff3e Merge pull request #1092 from mempool/simon/transifex-pull 2022-01-05 20:17:54 +00:00
softsimon
1e39eb0fa5 Transifex pull 2022-01-06 00:04:02 +04:00
wiz
5de133ae6a Merge pull request #1087 from mempool/simon/removing-sql-import-references
Remove all references to SQL tables import
2022-01-05 10:03:36 +00:00
softsimon
d27b125848 Merge branch 'master' into simon/removing-sql-import-references
# Conflicts:
#	production/README.md
2022-01-05 13:51:58 +04:00
wiz
ad36d53bb5 Merge pull request #1081 from mempool/wiz/update-production-configuration-for-v2.3
Update production configurations + README for v2.3
2022-01-05 09:45:57 +00:00
softsimon
24f76f2f37 Remove all references to SQL tables import
fixes #1045
2022-01-05 13:26:36 +04:00
wiz
691bdda523 Merge pull request #1086 from mempool/simon/transifex-pull
Transifex pull
2022-01-05 09:19:03 +00:00
wiz
81bb31090e Use upstream hostnames in production nginx configuration 2022-01-05 18:12:05 +09:00
softsimon
cc0a0719b6 Transifex pull 2022-01-05 13:10:58 +04:00
softsimon
7dca8ae1a0 Merge pull request #1085 from mempool/wiz/add-citadel-to-about-page
Add Citadel as Community Integration on About page
2022-01-05 13:04:50 +04:00
softsimon
84027d5568 Merge pull request #1084 from mempool/wiz/tweak-page-titles-descriptions
Tweak html description meta tags / SEO service page titles
2022-01-05 13:04:09 +04:00
wiz
4116186c1a Add Citadel as Community Integration on About page 2022-01-05 17:15:11 +09:00
wiz
358301020f Tweak html description meta tags / SEO service page titles 2022-01-05 16:57:24 +09:00
wiz
642022bfd8 Merge pull request #1083 from mempool/simon/transifex-pull
Transifex pull
2022-01-05 01:52:26 +00:00
softsimon
70f25b6c9c Transifex pull 2022-01-04 22:43:09 +04:00
wiz
c778e84247 Add missing } at end of nginx/server-common.conf 2022-01-04 17:27:37 +09:00
wiz
4de1d017ad Update production configurations + README for v2.3
* Refactor production nginx configuration files
* Update README for new networks, SQL, etc.
2022-01-04 16:38:12 +09:00
wiz
61851be23a Merge pull request #1079 from mempool/simon/liquid-fee-range-dropdown
Liquid support to the Graph fee filter dropdown
2022-01-04 04:51:10 +00:00
wiz
5de949eaed Merge pull request #1067 from mempool/simon/liquid-testnet-navbar-color
Liquid testnet navbar color
2022-01-04 04:32:12 +00:00
wiz
de6434a5ba Merge pull request #1080 from mempool/simon/liquid-testnet-asset-data
Fixing missing assets data for Liquid Testnet native asset
2022-01-04 04:24:19 +00:00
softsimon
c8639ec71d Fixing missing assets data for Liquid Testnet native asset
fixes #1068
2022-01-04 05:26:46 +04:00
softsimon
e1275c62cc Liquid support to the Graph fee filter dropdown
fixes #1072
2022-01-04 04:42:19 +04:00
softsimon
be45e88056 Liquid testnet navbar color 2022-01-01 13:37:20 +04:00
softsimon
990ab3da5f Merge pull request #1066 from mempool/simon/liquid-backend-detection-refactor
Refactoring the Liquid and LiquidTestnet check to a common function.
2022-01-01 12:54:39 +04:00
wiz
d1d74ebf37 Merge pull request #1065 from mempool/simon/block-navigation-routing-fix
Block navigation routing fix
2021-12-30 22:31:05 +00:00
softsimon
6ab79b3c35 Refactoring the Liquid and LiquidTestnet check to a common function. 2021-12-31 02:28:40 +04:00
softsimon
4f21fc0d87 Block navigation routing fix 2021-12-31 02:21:12 +04:00
wiz
10c4e47091 Merge pull request #1064 from mempool/wiz/add-liquidtestnet-to-upgrade-script
Add missing liquidtestnet backend to upgrade script
2021-12-30 21:59:37 +00:00
wiz
dd49ff0084 Merge pull request #1062 from mempool/simon/liquidtestnet-backend-fix
Fix backend support for 'liquidtestnet' network
2021-12-30 21:56:55 +00:00
wiz
853314ba58 Add missing liquidtestnet backend to upgrade script 2021-12-31 06:55:16 +09:00
wiz
784e2470df Merge pull request #1060 from mempool/simon/coinbase-unknown-fix
Fixing misplaced Unknown text after the Coinbase
2021-12-30 21:30:18 +00:00
softsimon
350b4922da Fix backend support for 'liquidtestnet' network 2021-12-31 01:26:45 +04:00
softsimon
40fb1792f4 Fixing misplaces Unknown text after the Coinbase 2021-12-30 16:55:42 +04:00
wiz
7ce1cc5103 Merge pull request #1052 from mempool/simon/liquid-testnet
Adding Liquid Testnet as frontend option
2021-12-29 23:34:19 +00:00
wiz
71a4e24900 Delete duplicate production/mempool-config.liquid-testnet.json file 2021-12-30 08:25:44 +09:00
softsimon
a48c2c07b0 Display the transaction grapg instead of L-BTC peg for Liquid Testnet 2021-12-30 03:12:35 +04:00
softsimon
d89d7efbe6 Fix issue when switching between testnet and liquid mainnet 2021-12-30 03:07:08 +04:00
softsimon
5ea4b043d9 Use relativeUrl when redirecting from the Searchbar 2021-12-30 02:30:46 +04:00
softsimon
dd4710b602 Handle Liquid native asset issuance transaction. 2021-12-30 02:18:16 +04:00
wiz
832c0cb3cc Merge pull request #1057 from mempool/wiz/remove-hover-effect-on-about-page
Tweak hover effect on profile photos on About page
2021-12-29 22:03:23 +00:00
wiz
04216e952a Tweak hover effect on profile photos on About page 2021-12-30 06:48:40 +09:00
softsimon
951d0f0039 Merge pull request #1056 from mempool/wiz/update-specter-logo-on-about-page
Update Specter logo on About page
2021-12-30 01:33:20 +04:00
wiz
706f4bbc55 Update Specter logo on About page 2021-12-30 06:11:12 +09:00
softsimon
3fd96e412b Merge pull request #1053 from mempool/wiz/add-liquid-testnet-backend
Add support for liquidtestnet in production backend and nginx
2021-12-29 01:07:38 +04:00
softsimon
766803ded1 Liquid testnet asset frontend support 2021-12-29 00:42:34 +04:00
softsimon
504f46cad9 Support for Test Liquid Native Asset 2021-12-29 00:40:55 +04:00
softsimon
fd34761a93 Adding Liquid Testnet as frontend option
fixes #976
2021-12-28 11:16:33 +04:00
wiz
96e8f45e5b Add support for liquidtestnet in production backend and nginx 2021-12-28 15:20:11 +09:00
wiz
195fae670b Merge pull request #1044 from nymkappa/feature/increase-resolution-charts
Increase graphs data resolution
2021-12-28 05:19:20 +00:00
wiz
dd767f9468 Merge pull request #1043 from mempool/simon/transifex-pull
Pulled from transifex
2021-12-26 21:09:07 +00:00
nymkappa
bc8104eeb4 Increase graphs data resolution 2021-12-26 17:51:38 +09:00
softsimon
2c61eb6227 Pulled from transifex 2021-12-26 11:15:19 +04:00
128 changed files with 52723 additions and 54710 deletions

View File

@@ -25,8 +25,7 @@ help:
.PHONY: init
init:
@echo ''
mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/db-scripts $(DATA)/mysql/data
install -v mariadb-structure.sql $(DATA)/mysql/db-scripts
mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/data
#REF: https://github.com/mempool/mempool/blob/master/docker/README.md
cat docker/docker-compose.yml > docker-compose.yml
cat backend/mempool-config.sample.json > backend/mempool-config.json

View File

@@ -69,11 +69,6 @@ Create database and grant privileges:
Query OK, 0 rows affected (0.00 sec)
```
From the mempool repo's top-level folder, import the database structure:
```bash
mysql -u mempool -p mempool < mariadb-structure.sql
```
## Mempool Backend
Install mempool dependencies from npm and build the backend:

View File

@@ -1,7 +1,14 @@
import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
import config from '../config';
export class Common {
static nativeAssetId = '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
'144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'
: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
static _isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet';
static isLiquid(): boolean {
return this._isLiquid;
}
static median(numbers: number[]) {
let medianNr = 0;
@@ -107,7 +114,7 @@ export class Common {
totalFees += tx.bestDescendant.fee;
}
tx.effectiveFeePerVsize = Math.max(config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1, totalFees / (totalWeight / 4));
tx.effectiveFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1, totalFees / (totalWeight / 4));
tx.cpfpChecked = true;
return {

View File

@@ -127,7 +127,7 @@ class DatabaseMigration {
const queries: string[] = [];
if (version < 1) {
if (config.MEMPOOL.NETWORK !== 'liquid') {
if (config.MEMPOOL.NETWORK !== 'liquid' && config.MEMPOOL.NETWORK !== 'liquidtestnet') {
queries.push(`UPDATE statistics SET
vsize_1 = vsize_1 + vsize_2, vsize_2 = vsize_3,
vsize_3 = vsize_4, vsize_4 = vsize_5,

View File

@@ -1,12 +1,13 @@
import config from '../config';
import { MempoolBlock } from '../mempool.interfaces';
import { Common } from './common';
import mempool from './mempool';
import projectedBlocks from './mempool-blocks';
class FeeApi {
constructor() { }
defaultFee = config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1;
defaultFee = Common.isLiquid() ? 0.1 : 1;
public getRecommendedFee() {
const pBlocks = projectedBlocks.getMempoolBlocks();

View File

@@ -4,6 +4,7 @@ import logger from '../logger';
import { Statistic, TransactionExtended, OptimizedStatistic } from '../mempool.interfaces';
import config from '../config';
import { Common } from './common';
class Statistics {
protected intervalTimer: NodeJS.Timer | undefined;
@@ -90,9 +91,9 @@ class Statistics {
memPoolArray.forEach((transaction) => {
for (let i = 0; i < logFees.length; i++) {
if (
(config.MEMPOOL.NETWORK === 'liquid' && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
(Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
||
(config.MEMPOOL.NETWORK !== 'liquid' && (i === lastItem || transaction.effectiveFeePerVsize < logFees[i + 1]))
(!Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize < logFees[i + 1]))
) {
if (weightVsizeFees[logFees[i]]) {
weightVsizeFees[logFees[i]] += transaction.vsize;
@@ -407,7 +408,7 @@ class Statistics {
public async $list1W(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(600, '1 WEEK'); // 10m interval
const query = this.getQueryForDaysAvg(300, '1 WEEK'); // 5m interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -420,7 +421,7 @@ class Statistics {
public async $list1M(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(3600, '1 MONTH'); // 1h interval
const query = this.getQueryForDaysAvg(1800, '1 MONTH'); // 30m interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -433,7 +434,7 @@ class Statistics {
public async $list3M(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(14400, '3 MONTH'); // 4h interval
const query = this.getQueryForDaysAvg(7200, '3 MONTH'); // 2h interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -446,7 +447,7 @@ class Statistics {
public async $list6M(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(21600, '6 MONTH'); // 6h interval
const query = this.getQueryForDaysAvg(10800, '6 MONTH'); // 3h interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -459,7 +460,7 @@ class Statistics {
public async $list1Y(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDays(43200, '1 YEAR'); // 12h interval
const query = this.getQueryForDays(28800, '1 YEAR'); // 8h interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -472,7 +473,7 @@ class Statistics {
public async $list2Y(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDays(86400, "2 YEAR"); // 1d interval
const query = this.getQueryForDays(28800, "2 YEAR"); // 8h interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -485,7 +486,7 @@ class Statistics {
public async $list3Y(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDays(86400, "3 YEAR"); // 1d interval
const query = this.getQueryForDays(43200, "3 YEAR"); // 12h interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);

View File

@@ -2,6 +2,7 @@ import bitcoinApi from './bitcoin/bitcoin-api-factory';
import { TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
import { IEsploraApi } from './bitcoin/esplora-api.interface';
import config from '../config';
import { Common } from './common';
class TransactionUtils {
constructor() { }
@@ -31,7 +32,8 @@ class TransactionUtils {
// @ts-ignore
return transaction;
}
const feePerVbytes = Math.max(config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1, (transaction.fee || 0) / (transaction.weight / 4));
const feePerVbytes = Math.max(Common.isLiquid() ? 0.1 : 1,
(transaction.fee || 0) / (transaction.weight / 4));
const transactionExtended: TransactionExtended = Object.assign({
vsize: Math.round(transaction.weight / 4),
feePerVsize: feePerVbytes,

View File

@@ -2,7 +2,7 @@ const configFile = require('../mempool-config.json');
interface IConfig {
MEMPOOL: {
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid';
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
BACKEND: 'esplora' | 'electrum' | 'none';
HTTP_PORT: number;
SPAWN_CLUSTER_PROCS: number;

View File

@@ -24,6 +24,7 @@ import elementsParser from './api/liquid/elements-parser';
import databaseMigration from './api/database-migration';
import syncAssets from './sync-assets';
import icons from './api/liquid/icons';
import { Common } from './api/common';
class Server {
private wss: WebSocket.Server | undefined;
@@ -96,7 +97,7 @@ class Server {
statistics.startStatistics();
}
if (config.MEMPOOL.NETWORK === 'liquid') {
if (Common.isLiquid()) {
icons.loadIcons();
}
@@ -156,7 +157,7 @@ class Server {
if (this.wss) {
websocketHandler.setWebsocketServer(this.wss);
}
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
if (Common.isLiquid() && config.DATABASE.ENABLED) {
blocks.setNewBlockCallback(async () => {
try {
await elementsParser.$parse();
@@ -283,14 +284,14 @@ class Server {
;
}
if (config.MEMPOOL.NETWORK === 'liquid') {
if (Common.isLiquid()) {
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) {
if (Common.isLiquid() && config.DATABASE.ENABLED) {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
;

View File

@@ -5,11 +5,9 @@
In an empty dir create 2 sub-dirs
```bash
mkdir -p data mysql/data mysql/db-scripts
mkdir -p data mysql/data
```
In the `mysql/db-scripts` sub-dir add the `mariadb-structure.sql` file from the mempool repo
Your dir should now look like that:
```bash
@@ -23,9 +21,6 @@ data mysql
data db-scripts
./mysql/data:
./mysql/db-scripts:
mariadb-structure.sql
```
In the main dir add the following `docker-compose.yml`

2
frontend/.gitignore vendored
View File

@@ -50,6 +50,8 @@ Thumbs.db
src/resources/assets.json
src/resources/assets.minimal.json
src/resources/assets-testnet.json
src/resources/assets-testnet.minimal.json
src/resources/pools.json
# environment config

View File

@@ -2,6 +2,7 @@
"TESTNET_ENABLED": false,
"SIGNET_ENABLED": false,
"LIQUID_ENABLED": false,
"LIQUID_TESTNET_ENABLED": false,
"BISQ_ENABLED": false,
"BISQ_SEPARATE_BACKEND": false,
"ITEMS_PER_PAGE": 10,

View File

@@ -24,6 +24,7 @@ PROXY_CONFIG = [
'/api/**', '!/api/v1/ws',
'!/bisq', '!/bisq/**', '!/bisq/',
'!/liquid', '!/liquid/**', '!/liquid/',
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
'/testnet/api/**', '/signet/api/**'
],
target: "https://mempool.space",
@@ -57,6 +58,16 @@ PROXY_CONFIG = [
ws: true,
secure: false,
changeOrigin: true
},
{
context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'],
target: "https://liquid.network/testnet",
pathRewrite: {
"^/api/liquidtestnet/": "/liquidtestnet/api"
},
ws: true,
secure: false,
changeOrigin: true
}
];

View File

@@ -105,6 +105,91 @@ let routes: Routes = [
},
],
},
{
path: 'liquidtestnet',
children: [
{
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: DashboardComponent
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'address/:id',
component: AddressComponent
},
{
path: 'asset/:id',
component: AssetComponent
},
{
path: 'assets',
component: AssetsComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
},
],
},
{
path: 'tv',
component: TelevisionComponent
},
{
path: 'status',
component: StatusViewComponent
},
{
path: '**',
redirectTo: ''
},
]
},
{
path: 'liquid',
children: [
@@ -466,6 +551,94 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
},
],
},
{
path: 'testnet',
component: LiquidMasterPageComponent,
children: [
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: DashboardComponent
},
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'address/:id',
component: AddressComponent
},
{
path: 'asset/:id',
component: AssetComponent
},
{
path: 'assets',
component: AssetsComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
},
{
path: 'about',
component: AboutComponent,
},
{
path: 'terms-of-service',
component: TermsOfServiceComponent
},
{
path: 'privacy-policy',
component: PrivacyPolicyComponent
},
{
path: 'trademark-policy',
component: TrademarkPolicyComponent
},
{
path: 'sponsor',
component: SponsorComponent,
},
],
},
{
path: 'tv',
component: TelevisionComponent

View File

@@ -7,6 +7,7 @@ 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',
@@ -15,7 +16,8 @@ import { SeoService } from '../services/seo.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetsComponent implements OnInit {
nativeAssetId = environment.nativeAssetId;
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
assets: AssetExtended[];
assetsCache: AssetExtended[];
searchForm: FormGroup;
@@ -34,6 +36,7 @@ export class AssetsComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private seoService: SeoService,
private stateService: StateService,
) { }
ngOnInit() {
@@ -52,12 +55,22 @@ export class AssetsComponent implements OnInit {
take(1),
mergeMap(([assets, qp]) => {
this.assets = Object.values(assets);
// @ts-ignore
this.assets.push({
name: 'Liquid Bitcoin',
ticker: 'L-BTC',
asset_id: this.nativeAssetId,
});
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();

View File

@@ -33,7 +33,7 @@ export class BisqMainDashboardComponent implements OnInit {
) { }
ngOnInit(): void {
this.seoService.setTitle(`Markets`);
this.seoService.resetTitle();
this.websocketService.want(['blocks']);
this.usdPrice$ = this.stateService.conversions$.asObservable().pipe(

View File

@@ -102,6 +102,10 @@
<img class="image" src="/resources/profile/ronindojo.png" />
<span>RoninDojo</span>
</a>
<a href="https://github.com/runcitadel/dashboard" target="_blank" title="Citadel">
<img class="image" src="/resources/profile/runcitadel.svg" />
<span>Citadel</span>
</a>
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
<img class="image" src="/resources/profile/electrum.jpg" />
<span>Electrum</span>
@@ -142,10 +146,6 @@
<img class="image" src="/resources/profile/satpile.jpg" />
<span>Satpile</span>
</a>
<a href="https://github.com/btcontract/lnwallet" target="_blank" title="Bitcoin Lightning Wallet">
<img class="image" src="/resources/profile/blw.png" />
<span>BLW</span>
</a>
</div>
</div>

View File

@@ -116,7 +116,7 @@
&:hover {
text-decoration: none;
img {
box-shadow: 0px 0px 20px #1bd8f4;
transform: scale(1.1);
}
}
img, span{
@@ -180,4 +180,4 @@
.no-about-margin {
height: 10px;
}
}

View File

@@ -99,7 +99,7 @@ export class AddressComponent implements OnInit, OnDestroy {
.pipe(
filter((address) => !!address),
tap((address: Address) => {
if (this.stateService.network === 'liquid' && /^([m-zA-HJ-NP-Z1-9]{26,35}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[a-km-zA-HJ-NP-Z1-9]{80})$/.test(address.address)) {
if ((this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') && /^([m-zA-HJ-NP-Z1-9]{26,35}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[a-km-zA-HJ-NP-Z1-9]{80})$/.test(address.address)) {
this.apiService.validateAddress$(address.address)
.subscribe((addressInfo) => {
this.addressInfo = addressInfo;

View File

@@ -2,12 +2,13 @@
<span class="fiat">{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
</ng-container>
<ng-template #viewFiatVin>
<ng-template [ngIf]="network === 'liquid' && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
<span i18n="shared.confidential">Confidential</span>
</ng-template>
<ng-template #default>
&lrm;{{ satoshis / 100000000 | number : digitsInfo }}
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
<ng-template [ngIf]="network === 'signet'">s</ng-template>BTC</span>
</ng-template>

View File

@@ -31,7 +31,7 @@
<td i18n="asset.issuer|Liquid Asset issuer">Issuer</td>
<td><a target="_blank" href="{{ 'http://' + assetContract[0] }}">{{ assetContract[0] }}</a></td>
</tr>
<tr *ngIf="!isNativeAsset">
<tr *ngIf="asset.issuance_txin">
<td i18n="asset.issuance-tx|Liquid Asset issuance TX">Issuance TX</td>
<td><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></td>
</tr>
@@ -42,15 +42,15 @@
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr *ngIf="isNativeAsset">
<tr *ngIf="isNativeAsset && asset.chain_stats.peg_in_amount">
<td i18n="asset.pegged-in|Liquid Asset pegged-in amount">Pegged in</td>
<td>{{ formatAmount(asset.chain_stats.peg_in_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="isNativeAsset">
<tr *ngIf="isNativeAsset && asset.chain_stats.peg_out_amount">
<td i18n="asset.pegged-out|Liquid Asset pegged-out amount">Pegged out</td>
<td>{{ formatAmount(asset.chain_stats.peg_out_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="!isNativeAsset">
<tr *ngIf="asset.chain_stats.issued_amount">
<td i18n="asset.issued-amount|Liquid Asset issued amount">Issued amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.issued_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
@@ -58,11 +58,11 @@
<td i18n="asset.burned-amount|Liquid Asset burned amount">Burned amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.burned_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="!isNativeAsset">
<tr *ngIf="asset.chain_stats.issued_amount">
<td i18n="asset.circulating-amount|Liquid Asset circulating amount">Circulating amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.issued_amount - asset.chain_stats.burned_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="isNativeAsset">
<tr *ngIf="isNativeAsset && asset.chain_stats.peg_in_amount">
<td i18n="asset.circulating-amount|Liquid Asset circulating amount">Circulating amount</td>
<td>{{ formatAmount(asset.chain_stats.peg_in_amount - asset.chain_stats.burned_amount - asset.chain_stats.peg_out_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
@@ -78,7 +78,7 @@
<div class="title-tx">
<h2>
<ng-template [ngIf]="transactions?.length" i18n="asset.M_of_N">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }}&nbsp;</ng-template>
<ng-template [ngIf]="isNativeAsset" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
<ng-template [ngIf]="isNativeAsset && network === 'liquid'" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
<ng-template #defaultAsset i18n="Default asset transactions title">Issuance and Burn Transactions</ng-template>
</h2>
</div>

View File

@@ -20,7 +20,7 @@ import { moveDec } from 'src/app/bitcoin.utils';
})
export class AssetComponent implements OnInit, OnDestroy {
network = '';
nativeAssetId = environment.nativeAssetId;
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
asset: Asset;
blindedIssuance: boolean;

View File

@@ -10,7 +10,7 @@
</ng-container>
</a>
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
<img src="./resources/bisq-logo.png" style="width: 25px; height: 25px;" class="mr-1">
</button>
@@ -21,6 +21,7 @@
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<button ngbDropdownItem class="mainnet active" routerLink="/"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</button>
<a href="https://liquid.network" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</a>
<a href="https://liquid.network/testnet" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet"><img src="./resources/liquidtestnet-logo.png" style="width: 30px;" class="mr-1"> Liquid Testnet</a>
</div>
</div>

View File

@@ -102,6 +102,10 @@ nav {
background-color: #116761;
}
.liquidtestnet.active {
background-color: #494a4a;
}
.testnet.active {
background-color: #1d486f;
}

View File

@@ -81,12 +81,12 @@
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
<tr>
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
<td *ngIf="network !== 'liquid'; else liquidTotalFees"><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat></span></td>
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees"><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat></span></td>
<ng-template #liquidTotalFees>
<td>{{ fees * 100000000 | number }} L-sat (<app-fiat [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>)</td>
<td><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount>&nbsp; <app-fiat [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat></td>
</ng-template>
</tr>
<tr *ngIf="network !== 'liquid'">
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td>
<app-amount [satoshis]="(blockSubsidy + fees) * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat></span>
@@ -98,7 +98,7 @@
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
</tr>
<tr *ngIf="network !== 'liquid'">
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td><span class="skeleton-loader"></span></td>
</tr>
@@ -125,7 +125,7 @@
<td class="td-width" i18n="transaction.version">Version</td>
<td>{{ block.version | decimal2hex }} <span *ngIf="displayTaprootStatus() && hasTaproot(block.version)" class="badge badge-success ml-1" >Taproot</span></td>
</tr>
<tr *ngIf="network !== 'liquid'">
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td i18n="block.bits">Bits</td>
<td>{{ block.bits | decimal2hex }}</td>
</tr>
@@ -136,7 +136,7 @@
</tbody>
</table>
</div>
<div class="col-sm" *ngIf="network !== 'liquid'">
<div class="col-sm" *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<table class="table table-borderless table-striped">
<tbody>
<tr>

View File

@@ -8,10 +8,12 @@ import { Observable, of, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { SeoService } from 'src/app/services/seo.service';
import { WebsocketService } from 'src/app/services/websocket.service';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
@Component({
selector: 'app-block',
templateUrl: './block.component.html',
providers: [RelativeUrlPipe],
styleUrls: ['./block.component.scss']
})
export class BlockComponent implements OnInit, OnDestroy {
@@ -51,6 +53,7 @@ export class BlockComponent implements OnInit, OnDestroy {
private stateService: StateService,
private seoService: SeoService,
private websocketService: WebsocketService,
private relativeUrlPipe: RelativeUrlPipe,
) { }
ngOnInit() {
@@ -194,7 +197,7 @@ export class BlockComponent implements OnInit, OnDestroy {
if (this.showNextBlocklink) {
this.navigateToNextBlock();
} else {
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/mempool-block/', '0']);
this.router.navigate([this.relativeUrlPipe.transform('/mempool-block'), '0']);
}
}
});
@@ -210,7 +213,7 @@ export class BlockComponent implements OnInit, OnDestroy {
}
setBlockSubsidy() {
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
this.blockSubsidy = 0;
return;
}
@@ -277,13 +280,13 @@ export class BlockComponent implements OnInit, OnDestroy {
return;
}
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight - 2);
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/',
this.router.navigate([this.relativeUrlPipe.transform('/block/'),
block ? block.id : this.block.previousblockhash], { state: { data: { block, blockHeight: this.nextBlockHeight - 2 } } });
}
navigateToNextBlock() {
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight);
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/',
this.router.navigate([this.relativeUrlPipe.transform('/block/'),
block ? block.id : this.nextBlockHeight], { state: { data: { block, blockHeight: this.nextBlockHeight } } });
}

View File

@@ -36,6 +36,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
'': ['#9339f4', '#105fb0'],
bisq: ['#9339f4', '#105fb0'],
liquid: ['#116761', '#183550'],
'liquidtestnet': ['#494a4a', '#272e46'],
testnet: ['#1d486f', '#183550'],
signet: ['#6f1d5d', '#471850'],
};
@@ -47,7 +48,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
) { }
ngOnInit() {
if (this.stateService.network === 'liquid') {
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
this.feeRounding = '1.0-1';
}
this.emptyBlocks.forEach((b) => this.emptyBlockStyles.push(this.getStyleForEmptyBlock(b)));

View File

@@ -31,7 +31,7 @@
top: 75px;
}
.position-container.liquid {
.position-container.liquid, .position-container.liquidtestnet {
left: 420px;
}
@@ -39,7 +39,7 @@
.position-container {
left: 95%;
}
.position-container.liquid {
.position-container.liquid, .position-container.liquidtestnet {
left: 50%;
}
.position-container.loading {

View File

@@ -27,7 +27,7 @@
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions-mempool" (click)="collapseItem.toggle()">GET Address Transactions Mempool</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-utxo" (click)="collapseItem.toggle()">GET Address UTXO</a>
<ng-template [ngIf]="network.val === 'liquid'">
<ng-template [ngIf]="network.val === 'liquid' || network.val === 'liquidtestnet'">
<p>Assets</p>
<a [routerLink]="['./']" fragment="get-assets" (click)="collapseItem.toggle()">GET Assets</a>
<a [routerLink]="['./']" fragment="get-assets-icons" (click)="collapseItem.toggle()">GET Assets Icons</a>
@@ -66,7 +66,7 @@
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-cpfp" (click)="collapseItem.toggle()">GET Children Pay for Parent</a>
<a [routerLink]="['./']" fragment="get-transaction" (click)="collapseItem.toggle()">GET Transaction</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-hex" (click)="collapseItem.toggle()">GET Transaction Hex</a>
<a *ngIf="network.val !== 'bisq' && network.val !== 'liquid'" [routerLink]="['./']" fragment="get-transaction-merkleblock-proof" (click)="collapseItem.toggle()">GET Transaction Merkleblock Proof</a>
<a *ngIf="network.val !== 'bisq' && network.val !== 'liquid' && network.val !== 'liquidtestnet'" [routerLink]="['./']" fragment="get-transaction-merkleblock-proof" (click)="collapseItem.toggle()">GET Transaction Merkleblock Proof</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-merkle-proof" (click)="collapseItem.toggle()">GET Transaction Merkle Proof</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspend" (click)="collapseItem.toggle()">GET Transaction Outspend</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspends" (click)="collapseItem.toggle()">GET Transaction Outspends</a>

View File

@@ -24,7 +24,7 @@
<div id="mobile-top-doc-nav" #mobileFixedApiNav class="hide-on-desktop"><app-api-docs-nav [network]="{ val: network$ | async }"></app-api-docs-nav></div>
<div class="api-category" *ngIf="network.val !== 'bisq' && network.val !== 'liquid'">
<div class="api-category" *ngIf="network.val !== 'bisq' && network.val !== 'liquid' && network.val !== 'liquidtestnet'">
<div class="endpoint-container" id="get-difficulty-adjustment">
<a class="section-header" [routerLink]="['./']" fragment="get-difficulty-adjustment">GET Difficulty Adjustment <span>General</span></a>
@@ -228,14 +228,14 @@
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: <code>txid</code>, <code>vout</code>, <code>value</code>, and <code>status</code> (with the status of the funding tx).<ng-container *ngIf="network.val === 'liquid'">There is also a <code>valuecommitment</code> field that may appear in place of <code>value</code>, plus the following additional fields: <code>asset</code>/<code>assetcommitment</code>, <code>nonce</code>/<code>noncecommitment</code>, <code>surjection_proof</code>, and <code>range_proof</code>.</ng-container></div>
<div i18n>Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: <code>txid</code>, <code>vout</code>, <code>value</code>, and <code>status</code> (with the status of the funding tx).<ng-container *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'">There is also a <code>valuecommitment</code> field that may appear in place of <code>value</code>, plus the following additional fields: <code>asset</code>/<code>assetcommitment</code>, <code>nonce</code>/<code>noncecommitment</code>, <code>surjection_proof</code>, and <code>range_proof</code>.</ng-container></div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressUTXO" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category" *ngIf="network.val === 'liquid'">
<div class="api-category" *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'">
<div class="endpoint-container" id="get-assets">
<a class="section-header" [routerLink]="['./']" fragment="get-assets">GET Assets <span>Assets</span></a>
@@ -313,7 +313,7 @@
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about a block. Available fields: <code>id</code>, <code>height</code>, <code>version</code>, <code>timestamp</code>, <code>bits</code>, <code>nonce</code>, <code>merkle_root</code>, <code>tx_count</code>, <code>size</code>, <code>weight</code>,<ng-container *ngIf="network.val === 'liquid'"> <code>proof</code>,</ng-container> and <code>previousblockhash</code>.</div>
<div i18n>Returns details about a block. Available fields: <code>id</code>, <code>height</code>, <code>version</code>, <code>timestamp</code>, <code>bits</code>, <code>nonce</code>, <code>merkle_root</code>, <code>tx_count</code>, <code>size</code>, <code>weight</code>,<ng-container *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'"> <code>proof</code>,</ng-container> and <code>previousblockhash</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.block" [network]="network.val" ></app-code-template>
</div>
@@ -577,7 +577,7 @@
<app-code-template [hostname]="hostname" [code]="code.transactionHex" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq' && network.val !== 'liquid'" id="get-transaction-merkleblock-proof">
<div class="endpoint-container" *ngIf="network.val !== 'bisq' && network.val !== 'liquid' && network.val !== 'liquidtestnet'" id="get-transaction-merkleblock-proof">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-merkleblock-proof">GET Transaction Merkleblock Proof <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>

View File

@@ -3663,7 +3663,7 @@ export class ApiDocsComponent implements OnInit {
};
this.network$.subscribe((network) => {
this.active = (network === 'liquid') ? 2 : 0;
this.active = (network === 'liquid' || network === 'liquidtestnet') ? 2 : 0;
});
}
@@ -3679,7 +3679,7 @@ export class ApiDocsComponent implements OnInit {
if (network === 'signet') {
curlResponse = code.codeSampleSignet.curl;
}
if (network === 'liquid') {
if (network === 'liquid' || network === 'liquidtestnet') {
curlResponse = code.codeSampleLiquid.curl;
}
if (network === 'bisq') {

View File

@@ -26,7 +26,7 @@ export class CodeTemplateComponent implements OnInit {
if (this.network === 'bisq') {
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-bisq-js`;
}
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-liquid-js`;
}
return npmLink;
@@ -37,7 +37,7 @@ export class CodeTemplateComponent implements OnInit {
if (this.network === 'bisq') {
npmLink = `https://www.npmjs.org/package/@mempool/bisq.js`;
}
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
npmLink = `https://www.npmjs.org/package/@mempool/liquid.js`;
}
return npmLink;
@@ -50,7 +50,7 @@ export class CodeTemplateComponent implements OnInit {
} else {
codeText = codeText.replace('%{0}', 'bitcoin');
}
if(['', 'main', 'liquid', 'bisq'].includes(this.network)) {
if(['', 'main', 'liquid', 'bisq', 'liquidtestnet'].includes(this.network)) {
codeText = codeText.replace('mempoolJS();', `mempoolJS({
hostname: '${document.location.hostname}'
});`);
@@ -119,7 +119,7 @@ export class CodeTemplateComponent implements OnInit {
if (this.network === 'signet') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleSignet.esModule);
}
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleLiquid.esModule);
}
if (this.network === 'bisq') {
@@ -157,7 +157,7 @@ init();`;
if (this.network === 'signet') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleSignet.esModule);
}
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleLiquid.esModule);
}
if (this.network === 'bisq') {
@@ -237,7 +237,7 @@ yarn add @mempool/liquid.js`;
if (this.network === 'signet') {
return this.replaceCurlPlaceholder(code.codeTemplate.curl, code.codeSampleSignet);
}
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
return this.replaceCurlPlaceholder(code.codeTemplate.curl, code.codeSampleLiquid);
}
if (this.network === 'bisq') {
@@ -259,7 +259,7 @@ yarn add @mempool/liquid.js`;
if (this.network === 'signet') {
return code.codeSampleSignet.response;
}
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
return code.codeSampleLiquid.response;
}
if (this.network === 'bisq') {

View File

@@ -26,7 +26,7 @@ export class FeesBoxComponent implements OnInit {
) { }
ngOnInit(): void {
this.defaultFee = this.stateService.network === 'liquid' ? 0.1 : 1;
this.defaultFee = this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ? 0.1 : 1;
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
this.feeEstimations$ = this.stateService.mempoolBlocks$

View File

@@ -1,3 +1,4 @@
<ng-container *ngIf="{ val: network$ | async } as network">
<header>
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" [routerLink]="['/' | relativeUrl]" style="position: relative;">
@@ -10,9 +11,9 @@
</ng-container>
</a>
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
<img src="./resources/liquid-logo.png" style="width: 25px; height: 25px;" class="mr-1">
<img src="./resources/{{ network.val === '' ? 'liquid' : network.val }}-logo.png" style="width: 25px; height: 25px;" class="mr-1">
</button>
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
<a href="https://mempool.space" ngbDropdownItem class="mainnet"><img src="./resources/bitcoin-logo.png" style="width: 30px;" class="mr-1"> Mainnet</a>
@@ -20,12 +21,13 @@
<a href="https://mempool.space/testnet" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><img src="./resources/testnet-logo.png" style="width: 30px;" class="mr-1"> Testnet</a>
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<a href="https://bisq.markets" ngbDropdownItem class="mainnet"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</a>
<button ngbDropdownItem class="liquid active" routerLink="/"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</button>
<button ngbDropdownItem class="liquid" [class.active]="network.val === 'liquid'" routerLink="/"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</button>
<button ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquidtestnet'" routerLink="/testnet"><img src="./resources/liquidtestnet-logo.png" style="width: 30px;" class="mr-1"> Liquid Testnet</button>
</div>
</div>
<div class="navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav liquid">
<ul class="navbar-nav {{ network.val }}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
@@ -41,7 +43,7 @@
</li>
-->
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/assets']" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
</li>
<li [hidden]="isMobile" class="nav-item mr-2" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/docs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
@@ -60,3 +62,4 @@
<router-outlet></router-outlet>
<br>
</ng-container>

View File

@@ -102,6 +102,10 @@ nav {
background-color: #116761;
}
.liquidtestnet.active {
background-color: #494a4a;
}
.testnet.active {
background-color: #1d486f;
}

View File

@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Env, StateService } from '../../services/state.service';
import { Observable} from 'rxjs';
import { merge, Observable, of} from 'rxjs';
@Component({
selector: 'app-liquid-master-page',
@@ -13,6 +13,7 @@ export class LiquidMasterPageComponent implements OnInit {
navCollapsed = false;
isMobile = window.innerWidth <= 767.98;
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
network$: Observable<string>;
constructor(
private stateService: StateService,
@@ -21,6 +22,7 @@ export class LiquidMasterPageComponent implements OnInit {
ngOnInit() {
this.env = this.stateService.env;
this.connectionState$ = this.stateService.connectionState$;
this.network$ = merge(of(''), this.stateService.networkChanged$);
}
collapse(): void {

View File

@@ -11,7 +11,7 @@
</ng-container>
</a>
<div (window:resize)="onResize($event)" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<div (window:resize)="onResize($event)" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
<img src="./resources/{{ network.val === '' ? 'bitcoin' : network.val }}-logo.png" style="width: 25px; height: 25px;" class="mr-1">
</button>
@@ -24,6 +24,8 @@
<button ngbDropdownItem *ngIf="env.BISQ_ENABLED && !env.OFFICIAL_MEMPOOL_SPACE" class="bisq" [class.active]="network.val === 'bisq'" routerLink="/bisq"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</button>
<a href="https://liquid.network" ngbDropdownItem *ngIf="env.LIQUID_ENABLED && env.OFFICIAL_MEMPOOL_SPACE" class="liquid" [class.active]="network.val === 'liquid'"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</a>
<button ngbDropdownItem *ngIf="env.LIQUID_ENABLED && !env.OFFICIAL_MEMPOOL_SPACE" class="liquid" [class.active]="network.val === 'liquid'" routerLink="/liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</button>
<a href="https://liquid.network/testnet" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED && env.OFFICIAL_MEMPOOL_SPACE" class="liquidtestnet" [class.active]="network.val === 'liquid'"><img src="./resources/liquidtestnet-logo.png" style="width: 30px;" class="mr-1"> Liquid Testnet</a>
<button ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED && !env.OFFICIAL_MEMPOOL_SPACE" class="liquidtestnet" [class.active]="network.val === 'liquidtestnet'" routerLink="/liquidtestnet"><img src="./resources/liquidtestnet-logo.png" style="width: 30px;" class="mr-1"> Liquid Testnet</button>
</div>
</div>
@@ -54,8 +56,8 @@
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon></a>
</li>
</ng-template>
<li *ngIf="network.val === 'liquid'" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/liquid/assets']" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
<li *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/docs' | relativeUrl ]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="documentation.title" title="Documentation"></fa-icon></a>

View File

@@ -110,6 +110,10 @@ nav {
background-color: #116761;
}
.liquidtestnet.active {
background-color: #494a4a;
}
.testnet.active {
background-color: #1d486f;
}

View File

@@ -19,7 +19,7 @@
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
</div>
<div class="time-difference" *ngIf="projectedBlock.blockVSize <= stateService.blockVSize; else mergedBlock">
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="timeDiffMainnet">
<ng-template [ngIf]="network === 'liquid' || network === 'liquidtestnet'" [ngIfElse]="timeDiffMainnet">
<app-time-until [time]="(1 * i) + now + 61000" [fastRender]="false" [fixedRender]="true"></app-time-until>
</ng-template>
<ng-template #timeDiffMainnet>

View File

@@ -6,11 +6,13 @@ import { Router } from '@angular/router';
import { take, map, switchMap } from 'rxjs/operators';
import { feeLevels, mempoolFeeColors } from 'src/app/app.constants';
import { specialBlocks } from 'src/app/app.constants';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
@Component({
selector: 'app-mempool-blocks',
templateUrl: './mempool-blocks.component.html',
styleUrls: ['./mempool-blocks.component.scss'],
providers: [RelativeUrlPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MempoolBlocksComponent implements OnInit, OnDestroy {
@@ -52,10 +54,11 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
private router: Router,
public stateService: StateService,
private cd: ChangeDetectorRef,
private relativeUrlPipe: RelativeUrlPipe,
) { }
ngOnInit() {
if (this.stateService.network === 'liquid') {
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
this.feeRounding = '1.0-1';
}
this.mempoolEmptyBlocks.forEach((b) => {
@@ -166,19 +169,19 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
if (event.key === 'ArrowRight') {
if (this.mempoolBlocks[this.markIndex - 1]) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/mempool-block/', this.markIndex - 1]);
this.router.navigate([this.relativeUrlPipe.transform('mempool-block/'), this.markIndex - 1]);
} else {
this.stateService.blocks$
.pipe(take(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT))
.subscribe(([block]) => {
if (this.stateService.latestBlockHeight === block.height) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', block.id], { state: { data: { block } }});
this.router.navigate([this.relativeUrlPipe.transform('/block/'), block.id], { state: { data: { block } }});
}
});
}
} else if (event.key === 'ArrowLeft') {
if (this.mempoolBlocks[this.markIndex + 1]) {
this.router.navigate([(this.network ? '/' + this.network : '') + '/mempool-block/', this.markIndex + 1]);
this.router.navigate([this.relativeUrlPipe.transform('/mempool-block/'), this.markIndex + 1]);
}
}
});

View File

@@ -371,7 +371,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
this.feeLimitIndex = i;
}
if (feeLevels[i] <= this.limitFee) {
if (this.stateService.network === 'liquid') {
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
} else {
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);

View File

@@ -7,11 +7,13 @@ import { Observable, of, Subject, merge } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap, filter, catchError, map } from 'rxjs/operators';
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
@Component({
selector: 'app-search-form',
templateUrl: './search-form.component.html',
styleUrls: ['./search-form.component.scss'],
providers: [RelativeUrlPipe],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchFormComponent implements OnInit {
@@ -38,6 +40,7 @@ export class SearchFormComponent implements OnInit {
private assetsService: AssetsService,
private stateService: StateService,
private electrsApiService: ElectrsApiService,
private relativeUrlPipe: RelativeUrlPipe,
) { }
ngOnInit() {
@@ -48,7 +51,7 @@ export class SearchFormComponent implements OnInit {
searchText: ['', Validators.required],
});
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
this.assetsService.getAssetsMinimalJson$
.subscribe((assets) => {
this.assets = assets;
@@ -101,7 +104,7 @@ export class SearchFormComponent implements OnInit {
this.navigate('/block/', searchText);
} else if (this.regexTransaction.test(searchText)) {
const matches = this.regexTransaction.exec(searchText);
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.assets[matches[1]]) {
this.navigate('/asset/', matches[1]);
}
@@ -125,7 +128,7 @@ export class SearchFormComponent implements OnInit {
}
navigate(url: string, searchText: string, extras?: any) {
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + url, searchText], extras);
this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras);
this.searchTriggered.emit();
this.searchForm.setValue({
searchText: '',

View File

@@ -117,7 +117,7 @@
</ng-template>
<ng-template [ngIf]="paymentForm.get('method').value === 'lbtc'">
<ng-template [ngIf]="paymentForm.get('method').value === 'lbtc' || paymentForm.get('method').value === 'tlbtc'">
<div class="qr-wrapper">
<a [href]="bypassSecurityTrustUrl('liquidnetwork:' + donationObj.addresses.LBTC + '?amount=' + donationObj.amount + '&assetid=6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d')" target="_blank">

View File

@@ -41,17 +41,11 @@
</button>
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
<ul>
<ng-template ngFor let-fee let-i="index" [ngForOf]="feeLevels">
<ng-template [ngIf]="fee <= 400">
<li (click)="filterFees(fee)" [class]="filterFeeIndex > fee ? 'inactive' : ''">
<ng-template [ngIf]="inverted">
<span class="square" [ngStyle]="{'backgroundColor': chartColors[i]}"></span>
<span class="fee-text" >{{feeLevels[i]}} - {{ feeLevels[i + 1] }}</span>
</ng-template>
<ng-template [ngIf]="!inverted">
<span class="square" [ngStyle]="{'backgroundColor': chartColors[i - 1]}"></span>
<span class="fee-text" >{{feeLevels[i]}} - {{ feeLevels[i - 1] }}</span>
</ng-template>
<ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
<ng-template [ngIf]="feeData.fee <= 400">
<li (click)="filterFeeIndex = feeData.fee" [class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
<span class="square" [ngStyle]="{'backgroundColor': feeData.color}"></span>
<span class="fee-text">{{ feeData.range }}</span>
</li>
</ng-template>
</ng-template>

View File

@@ -37,6 +37,7 @@ export class StatisticsComponent implements OnInit {
radioGroupForm: FormGroup;
graphWindowPreference: string;
inverted: boolean;
feeLevelDropdownData = [];
constructor(
@Inject(LOCALE_ID) private locale: string,
@@ -51,19 +52,10 @@ export class StatisticsComponent implements OnInit {
ngOnInit() {
this.inverted = this.storageService.getValue('inverted-graph') === 'true';
if (!this.inverted) {
this.feeLevels = [...feeLevels].reverse();
this.chartColors = [...chartColors].reverse();
}
this.setFeeLevelDropdownData();
this.seoService.setTitle($localize`:@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs`);
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h';
const isMobile = window.innerWidth <= 767.98;
let labelHops = isMobile ? 48 : 24;
if (isMobile) {
labelHops = 96;
}
this.radioGroupForm = this.formBuilder.group({
dateSpan: this.graphWindowPreference
@@ -147,11 +139,27 @@ export class StatisticsComponent implements OnInit {
document.location.reload();
}
filterFees(index: number) {
this.filterFeeIndex = index;
}
filterClick() {
this.dropDownOpen = !this.dropDownOpen;
setFeeLevelDropdownData() {
let _feeLevels = feeLevels
let _chartColors = chartColors;
if (!this.inverted) {
_feeLevels = [...feeLevels].reverse();
_chartColors = [...chartColors].reverse();
}
_feeLevels.forEach((fee, i) => {
if (this.inverted) {
this.feeLevelDropdownData.push({
fee: fee,
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i + 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i + 1]}`,
color: _chartColors[i],
});
} else {
this.feeLevelDropdownData.push({
fee: fee,
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i - 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i - 1]}`,
color: _chartColors[i - 1],
});
}
})
}
}

View File

@@ -66,7 +66,7 @@
<td><app-time-span [time]="tx.status.block_time - transactionTime" [fastRender]="true"></app-time-span></td>
</tr>
</ng-template>
<tr *ngIf="network !== 'liquid'">
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td class="td-width" i18n="transaction.features|Transaction features">Features</td>
<td>
<app-tx-features [tx]="tx"></app-tx-features>
@@ -114,7 +114,7 @@
<span i18n="transaction.eta.in-several-hours|Transaction ETA in several hours or more">In several hours (or more)</span>
</ng-template>
<ng-template #belowBlockLimit>
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="timeEstimateDefault">
<ng-template [ngIf]="network === 'liquid' || network === 'liquidtestnet'" [ngIfElse]="timeEstimateDefault">
<app-time-until [time]="(60 * 1000 * txInBlockIndex) + now" [fastRender]="false" [fixedRender]="true"></app-time-until>
</ng-template>
<ng-template #timeEstimateDefault>
@@ -124,7 +124,7 @@
</ng-template>
</td>
</tr>
<tr *ngIf="network !== 'liquid'">
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<td class="td-width" i18n="transaction.features|Transaction Features">Features</td>
<td>
<app-tx-features [tx]="tx"></app-tx-features>

View File

@@ -159,7 +159,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
);
}),
switchMap((tx) => {
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
return from(this.liquidUnblinding.checkUnblindedTx(tx))
.pipe(
catchError((error) => {

View File

@@ -48,7 +48,7 @@
</td>
<td>
<div [ngSwitch]="true">
<ng-container *ngSwitchCase="vin.is_coinbase"><span i18n="transactions-list.coinbase">Coinbase</span><ng-template [ngIf]="network !== 'liquid'">&nbsp;<span i18n="transactions-list.newly-generated-coins">(Newly Generated Coins)</span></ng-template><br /><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii"><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></a></ng-container>
<ng-container *ngSwitchCase="vin.is_coinbase"><span i18n="transactions-list.coinbase">Coinbase</span><ng-template [ngIf]="network !== 'liquid' && network !== 'liquidtestnet'">&nbsp;<span i18n="transactions-list.newly-generated-coins">(Newly Generated Coins)</span></ng-template><br /><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii"><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></a></ng-container>
<ng-container *ngSwitchCase="vin.is_pegin">
<span i18n="transactions-list.peg-in">Peg-in</span>
</ng-container>
@@ -56,13 +56,18 @@
<span>P2PK</span>
</ng-container>
<ng-container *ngSwitchDefault>
<a [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vin.prevout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vin.prevout.scriptpubkey_address | shortenString : 35 }}</span>
</a>
<div>
<app-address-labels [vin]="vin"></app-address-labels>
</div>
<ng-template [ngIf]="!vin.prevout" [ngIfElse]="defaultAddress">
<span>{{ vin.issuance ? 'Issuance' : 'UNKNOWN' }}</span>
</ng-template>
<ng-template #defaultAddress>
<a [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vin.prevout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vin.prevout.scriptpubkey_address | shortenString : 35 }}</span>
</a>
<div>
<app-address-labels [vin]="vin"></app-address-labels>
</div>
</ng-template>
</ng-container>
</div>
</td>
@@ -244,7 +249,7 @@
&nbsp;
</span>
<button type="button" class="btn btn-sm btn-primary mt-2" (click)="switchCurrency()">
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
<ng-template [ngIf]="network === 'liquid' || network === 'liquidtestnet'" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
<ng-template #defaultAmount>
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
</ng-template>

View File

@@ -15,7 +15,7 @@ import { map } from 'rxjs/operators';
})
export class TransactionsListComponent implements OnInit, OnChanges {
network = '';
nativeAssetId = environment.nativeAssetId;
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
displayDetails = false;
@Input() transactions: Transaction[];
@@ -41,7 +41,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
this.latestBlock$ = this.stateService.blocks$.pipe(map(([block]) => block));
this.stateService.networkChanged$.subscribe((network) => this.network = network);
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
this.assetsService.getAssetsMinimalJson$.subscribe((assets) => {
this.assetsMinimal = assets;
});
@@ -99,7 +99,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
}
switchCurrency() {
if (this.network === 'liquid') {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
return;
}
const oldvalue = !this.stateService.viewFiat$.value;

View File

@@ -2,7 +2,7 @@
<div class="container-xl dashboard-container">
<div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData">
<ng-template [ngIf]="collapseLevel === 'three'" [ngIfElse]="expanded">
<div class="col card-wrapper" *ngIf="(network$ | async) !== 'liquid'">
<div class="col card-wrapper" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
<div class="card">
<div class="card-body">
@@ -10,7 +10,7 @@
</div>
</div>
</div>
<div class="col" *ngIf="(network$ | async) !== 'liquid'">
<div class="col" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
<ng-container *ngTemplateOutlet="difficultyEpoch"></ng-container>
</div>
<div class="col">
@@ -29,7 +29,7 @@
</div>
</ng-template>
<ng-template #expanded>
<div class="col card-wrapper" *ngIf="(network$ | async) !== 'liquid'">
<div class="col card-wrapper" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
<div class="card">
<div class="card-body">
@@ -37,7 +37,7 @@
</div>
</div>
</div>
<div class="col" *ngIf="(network$ | async) !== 'liquid'">
<div class="col" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
<ng-container *ngTemplateOutlet="difficultyEpoch"></ng-container>
</div>
<div class="col">
@@ -125,7 +125,7 @@
<tbody>
<tr *ngFor="let transaction of transactions$ | async; let i = index;">
<td class="table-cell-txid"><a [routerLink]="['/tx' | relativeUrl, transaction.txid]">{{ transaction.txid | shortenString : 10 }}</a></td>
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquid'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
<td class="table-cell-fiat" *ngIf="(network$ | async) === ''" ><app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat></td>
<td class="table-cell-fees">{{ transaction.fee / transaction.vsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
</tr>

View File

@@ -262,7 +262,7 @@ export class DashboardComponent implements OnInit {
share(),
);
if (this.stateService.network === 'liquid') {
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
this.liquidPegsMonth$ = this.apiService.listLiquidPegsMonth$()
.pipe(
map((pegs) => {

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { map, shareReplay, switchMap } from 'rxjs/operators';
import { StateService } from './state.service';
@Injectable({
@@ -21,8 +21,23 @@ export class AssetsService {
apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
}
this.getAssetsJson$ = this.httpClient.get(apiBaseUrl + '/resources/assets.json').pipe(shareReplay());
this.getAssetsMinimalJson$ = this.httpClient.get(apiBaseUrl + '/resources/assets.minimal.json').pipe(shareReplay());
this.getMiningPools$ = this.httpClient.get(apiBaseUrl + '/resources/pools.json').pipe(shareReplay());
this.getAssetsJson$ = this.stateService.networkChanged$
.pipe(
switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)),
shareReplay(1),
);
this.getAssetsMinimalJson$ = this.stateService.networkChanged$
.pipe(
switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.minimal.json`)),
map((assetsMinimal) => {
if (this.stateService.network === 'liquidtestnet') {
// Hard coding the Liquid Testnet native asset
assetsMinimal['144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'] = [null, "tL-BTC", "Test Liquid Bitcoin", 8];
}
return assetsMinimal;
}),
shareReplay(1),
);
this.getMiningPools$ = this.httpClient.get(apiBaseUrl + '/resources/pools.json').pipe(shareReplay(1));
}
}

View File

@@ -27,8 +27,14 @@ export class SeoService {
}
getTitle(): string {
if (this.network === 'testnet')
return 'mempool - Bitcoin Testnet';
if (this.network === 'signet')
return 'mempool - Bitcoin Signet';
if (this.network === 'liquid')
return 'mempool - Liquid Network';
if (this.network === 'liquidtestnet')
return 'mempool - Liquid Testnet';
if (this.network === 'bisq')
return 'mempool - Bisq Markets';
return 'mempool - ' + (this.network ? this.ucfirst(this.network) : 'Bitcoin') + ' Explorer';

View File

@@ -19,6 +19,7 @@ export interface Env {
TESTNET_ENABLED: boolean;
SIGNET_ENABLED: boolean;
LIQUID_ENABLED: boolean;
LIQUID_TESTNET_ENABLED: boolean;
BISQ_ENABLED: boolean;
BISQ_SEPARATE_BACKEND: boolean;
ITEMS_PER_PAGE: number;
@@ -38,6 +39,7 @@ const defaultEnv: Env = {
'TESTNET_ENABLED': false,
'SIGNET_ENABLED': false,
'LIQUID_ENABLED': false,
'LIQUID_TESTNET_ENABLED': false,
'BASE_MODULE': 'mempool',
'BISQ_ENABLED': false,
'BISQ_SEPARATE_BACKEND': false,
@@ -116,7 +118,7 @@ export class StateService {
this.blocks$ = new ReplaySubject<[Block, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
if (this.env.BASE_MODULE !== 'mempool') {
if (this.env.BASE_MODULE === 'bisq') {
this.network = this.env.BASE_MODULE;
this.networkChanged$.next(this.env.BASE_MODULE);
}
@@ -125,10 +127,10 @@ export class StateService {
}
setNetworkBasedonUrl(url: string) {
if (this.env.BASE_MODULE !== 'mempool') {
if (this.env.BASE_MODULE !== 'mempool' && this.env.BASE_MODULE !== 'liquid') {
return;
}
const networkMatches = url.match(/\/(bisq|testnet|liquid|signet)/);
const networkMatches = url.match(/\/(bisq|testnet|liquidtestnet|liquid|signet)/);
switch (networkMatches && networkMatches[1]) {
case 'liquid':
if (this.network !== 'liquid') {
@@ -136,6 +138,12 @@ export class StateService {
this.networkChanged$.next('liquid');
}
return;
case 'liquidtestnet':
if (this.network !== 'liquidtestnet') {
this.network = 'liquidtestnet';
this.networkChanged$.next('liquidtestnet');
}
return;
case 'signet':
if (this.network !== 'signet') {
this.network = 'signet';
@@ -144,8 +152,13 @@ export class StateService {
return;
case 'testnet':
if (this.network !== 'testnet') {
this.network = 'testnet';
this.networkChanged$.next('testnet');
if (this.env.BASE_MODULE === 'liquid') {
this.network = 'liquidtestnet';
this.networkChanged$.next('liquidtestnet');
} else {
this.network = 'testnet';
this.networkChanged$.next('testnet');
}
}
return;
case 'bisq':
@@ -155,7 +168,12 @@ export class StateService {
}
return;
default:
if (this.network !== '') {
if (this.env.BASE_MODULE !== 'mempool') {
if (this.network !== this.env.BASE_MODULE) {
this.network = this.env.BASE_MODULE;
this.networkChanged$.next(this.env.BASE_MODULE);
}
} else if (this.network !== '') {
this.network = '';
this.networkChanged$.next('');
}
@@ -182,4 +200,8 @@ export class StateService {
setBlockScrollingInProgress(value: boolean) {
this.blockScrolling$.next(value);
}
isLiquid() {
return this.network === 'liquid' || this.network === 'liquidtestnet';
}
}

View File

@@ -11,10 +11,13 @@ export class RelativeUrlPipe implements PipeTransform {
) { }
transform(value: string): string {
if (this.stateService.env.BASE_MODULE !== 'mempool') {
return '/' + value;
let network = this.stateService.network;
if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') {
network = 'testnet';
} else if (this.stateService.env.BASE_MODULE !== 'mempool') {
network = '';
}
return (this.stateService.network ? '/' + this.stateService.network : '') + value;
return (network ? '/' + network : '') + value;
}
}

View File

@@ -1,4 +1,5 @@
export const environment = {
production: true,
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
nativeTestAssetId: '144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49',
};

View File

@@ -5,6 +5,7 @@
export const environment = {
production: false,
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
nativeTestAssetId: '144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49',
};
/*

View File

@@ -5,7 +5,7 @@
<title>mempool - Bisq Markets</title>
<base href="/">
<meta name="description" content="An open-source explorer developed for the Bisq community.">
<meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the Bisq Network.">
<meta property="og:image" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
<meta property="og:image:type" content="image/jpeg" />
@@ -13,8 +13,8 @@
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:site" content="https://bisq.markets/">
<meta property="twitter:creator" content="@bisq_network">
<meta property="twitter:title" content="mempool - Bisq Markets">
<meta property="twitter:description" content="An open-source explorer developed for the Bisq community.">
<meta property="twitter:title" content="The Mempool Open Source Project™">
<meta property="twitter:description" content="Our self-hosted markets explorer for the Bisq community.">
<meta property="twitter:image:src" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
<meta property="twitter:domain" content="bisq.markets">

View File

@@ -5,7 +5,7 @@
<title>mempool - Liquid Network</title>
<base href="/">
<meta name="description" content="An open-source explorer developed for the Liquid Network.">
<meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the Liquid Network.">
<meta property="og:image" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1000" />
@@ -13,8 +13,8 @@
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:site" content="@mempool">
<meta property="twitter:creator" content="@mempool">
<meta property="twitter:title" content="mempool - Liquid Network">
<meta property="twitter:description" content="An open-source explorer developed for the Liquid Network.">
<meta property="twitter:title" content="The Mempool Open Source Project™">
<meta property="twitter:description" content="Our self-hosted network explorer for the Liquid community.">
<meta property="twitter:image:src" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
<meta property="twitter:domain" content="liquid.network">

View File

@@ -5,7 +5,7 @@
<title>mempool - Bitcoin Explorer</title>
<base href="/">
<meta name="description" content="An open-source explorer developed for the Bitcoin community, focusing on the emerging transaction fee market to help our transition into a multi-layer ecosystem." />
<meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the Bitcoin community." />
<meta property="og:image" content="https://mempool.space/resources/mempool-space-preview.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1000" />
@@ -13,8 +13,8 @@
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:site" content="@mempool">
<meta property="twitter:creator" content="@mempool">
<meta property="twitter:title" content="mempool">
<meta property="twitter:description" content="An open-source explorer developed for the Bitcoin community, focusing on the transition into a multi-layer ecosystem." />
<meta property="twitter:title" content="The Mempool Open Source Project™">
<meta property="twitter:description" content="Our self-hosted mempool explorer for the Bitcoin community." />
<meta property="twitter:image:src" content="https://mempool.space/resources/mempool-space-preview.png" />
<meta property="twitter:domain" content="mempool.space">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,17 @@
<svg width="897" height="1072" viewBox="0 0 897 1072" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M304.742 112.004L448.742 29.7188V157.719L304.742 240.004V112.004Z" fill="#40607D"/>
<path d="M616.742 224L592.742 240V112L616.742 95.9999V224Z" fill="#40607D"/>
<path d="M52.6641 256.045L192.744 176V304L164.728 320.009L52.6641 256.045Z" fill="#40607D"/>
<path d="M168.742 480L192.742 464V411.789L280.742 462.736V416L304.742 400L448.742 482.4V208L192.742 356.21V336L168.742 352V480Z" fill="#40607D"/>
<path d="M728.742 352L896.741 256V699.935C896.741 771.706 858.284 837.975 795.969 873.583L448.742 1072V511.999L616.742 416V543.999L728.742 480V352Z" fill="#40607D"/>
<path d="M448.742 157.719L592.742 240.004V112.004L448.742 29.7188V157.719Z" fill="#6891AA"/>
<path d="M304.742 240L280.742 224V95.9999L304.742 112V240Z" fill="#6891AA"/>
<path d="M168.742 352L0.742188 256V699.935C0.742188 771.706 39.1994 837.975 101.514 873.583L448.742 1072V511.999L280.742 416V543.999L168.742 480V352Z" fill="#6891AA"/>
<path d="M704.741 464L728.741 480V352L704.741 336V356.21L448.742 208V482.4L592.742 400L616.742 416V462.736L704.741 411.789V464Z" fill="#6891AA"/>
<path d="M732.757 320.009L704.741 304V176L844.821 256.045L732.757 320.009Z" fill="#6891AA"/>
<path d="M448.742 29.6L592.742 112L616.742 95.9999L448.742 0L280.742 95.9999L304.742 112L448.742 29.6Z" fill="#A2BECE"/>
<path d="M280.742 224L304.742 240L448.742 157.719L592.742 240.004L616.742 224L704.741 274.285V304L732.757 320.009L844.901 256L704.741 176L728.741 160L896.741 256L728.741 352L704.741 336V356.21L448.742 208L192.742 356.21V336L168.742 352L0.742188 256L168.742 160L192.742 176L52.5821 256L164.728 320.009L192.744 304V274.284L280.742 224Z" fill="#A2BECE"/>
<path d="M592.742 400L616.742 416L448.742 511.999L280.742 416L304.742 400L448.742 482.4L592.742 400Z" fill="#A2BECE"/>
<path d="M192.742 411.789L280.742 462.736V543.999L168.742 480L192.742 464V411.789Z" fill="#A2BECE"/>
<path d="M728.741 480L616.742 543.999V462.736L704.741 411.789V464L728.741 480Z" fill="#A2BECE"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -100,6 +100,10 @@ body {
background-color: #6f1d5d !important;
}
.navbar-nav.liquidtestnet > .active {
background-color: #494a4a !important;
}
.form-control {
color: #495057;
}

View File

@@ -42,9 +42,17 @@ if (configContent.BASE_MODULE && configContent.BASE_MODULE === 'liquid') {
assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.minimal.json';
}
const testnetAssetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.json';
const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json';
console.log('Downloading assets');
download(PATH + 'assets.json', assetsJsonUrl);
console.log('Downloading assets minimal');
download(PATH + 'assets.minimal.json', assetsMinimalJsonUrl);
console.log('Downloading mining pools info');
download(PATH + 'pools.json', poolsJsonUrl);
console.log('Downloading testnet assets');
download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl);
console.log('Downloading testnet assets minimal');
download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl);

View File

@@ -1,80 +1,88 @@
# mempool.space v2 production website hosting
# mempool enterprise production instance
These instructions are for setting up a serious production mempool website for Mainnet, Testnet, and Liquid. For home users, follow the main instructions instead.
These instructions are for setting up a serious production mempool website for Bitcoin mainnet, testnet, signet, Liquid mainnet and testnet, and Bisq. For home users, you should use one-click installation methods instead, and for advanced manual deployments of mainnet only see the top-level installation instructions.
### Server Hardware
Mempool V2 is powered by electrs, which is a beast. I recommend a beefy server:
Mempool V2 is powered by blockstream/electrs, which is a beast. I recommend a beefy server:
* 16C CPU (more is better)
* 20C CPU (more is better)
* 64G RAM (more is better)
* 2TB SSD (NVMe is better)
* 4TB SSD (NVMe is better)
### HDD vs SSD vs NVMe
If you don't have a fast SSD or NVMe backed disk, that's fine. What you do is, go online and buy some fast new NVMe drives and wait for them to arrive. After you install them, throw away your old HDDs and then proceed with the rest of this guide.
## FreeBSD 12
## FreeBSD 13
The mempool.space site is powered by FreeBSD with ZFS root and ARC cache for maximum performance. Linux probably works fine too, but why settle?
### Filesystem
For maximum performance, I use 2x 1TB NVMe SSDs in a RAID 0 using ZFS with lots of RAM for the ARC L2 cache.
For maximum performance, I use 2x 2TB NVMe SSDs in a RAID 0 using ZFS with lots of RAM for the ARC L2 cache.
```
# zpool list -v nvmraid
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
nvmraid 1.81T 1.04T 787G - - 0% 57% 1.00x ONLINE -
nvd0 928G 535G 393G - - 0% 57%
nvd1 928G 534G 394G - - 0% 57%
% zpool list -v
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
nvm 3.62T 1.25T 2.38T - - 2% 34% 1.00x ONLINE -
nvd0p3 1.81T 629G 1.20T - - 2% 33.9% - ONLINE
nvd1p3 1.81T 646G 1.18T - - 2% 34.8% - ONLINE
```
For maximum flexibility of configuration, I configure the partitions separately for each data folder:
For maximum flexibility of configuration, I recommend partitions separately for each data folder:
```
Filesystem Size Used Avail Capacity Mounted on
nvmraid/mempool 732G 3.0G 729G 0% /mempool
nvmraid/mysql 730G 618M 729G 0% /mysql
nvmraid/bisq 729G 88K 729G 0% /bisq
nvmraid/elements 731G 1.8G 729G 0% /elements
nvmraid/elements/liquidv1 737G 7.2G 729G 1% /elements/liquidv1
nvmraid/elements/electrs 730G 434M 729G 0% /elements/electrs
nvmraid/bitcoin 730G 694M 729G 0% /bitcoin
nvmraid/bitcoin/chainstate 733G 3.9G 729G 1% /bitcoin/chainstate
nvmraid/bitcoin/indexes 757G 27G 729G 4% /bitcoin/indexes
nvmraid/bitcoin/electrs 730G 853M 729G 0% /bitcoin/electrs
nvmraid/bitcoin/blocks 1.0T 306G 729G 30% /bitcoin/blocks
nvmraid/bitcoin/testnet3 729G 13M 729G 0% /bitcoin/testnet3
nvmraid/bitcoin/testnet3/blocks 756G 26G 729G 3% /bitcoin/testnet3/blocks
nvmraid/bitcoin/testnet3/chainstate 731G 1.3G 729G 0% /bitcoin/testnet3/chainstate
nvmraid/bitcoin/testnet3/indexes 733G 3.8G 729G 1% /bitcoin/testnet3/indexes
nvmraid/electrs/liquid/cache 729G 39M 729G 0% /electrs/liquid/newindex/cache
nvmraid/electrs/liquid/history 730G 737M 729G 0% /electrs/liquid/newindex/history
nvmraid/electrs/liquid/txstore 736G 6.2G 729G 1% /electrs/liquid/newindex/txstore
nvmraid/electrs/mainnet/cache 729G 44M 729G 0% /electrs/mainnet/newindex/cache
nvmraid/electrs/mainnet/history 964G 234G 729G 24% /electrs/mainnet/newindex/history
nvmraid/electrs/mainnet/txstore 1.1T 392G 729G 35% /electrs/mainnet/newindex/txstore
nvmraid/electrs/testnet/cache 729G 40M 729G 0% /electrs/testnet/newindex/cache
nvmraid/electrs/testnet/history 747G 18G 729G 2% /electrs/testnet/newindex/history
nvmraid/electrs/testnet/txstore 764G 34G 729G 4% /electrs/testnet/newindex/txstore
nvm/bisq 766G 1.1G 765G 0% /bisq
nvm/bitcoin 766G 648M 765G 0% /bitcoin
nvm/bitcoin/blocks 1.1T 375G 765G 33% /bitcoin/blocks
nvm/bitcoin/chainstate 770G 4.5G 765G 1% /bitcoin/chainstate
nvm/bitcoin/electrs 772G 7.3G 765G 1% /bitcoin/electrs
nvm/bitcoin/indexes 799G 34G 765G 4% /bitcoin/indexes
nvm/bitcoin/testnet3 765G 5.0M 765G 0% /bitcoin/testnet3
nvm/bitcoin/testnet3/blocks 786G 21G 765G 3% /bitcoin/testnet3/blocks
nvm/bitcoin/testnet3/chainstate 766G 1.1G 765G 0% /bitcoin/testnet3/chainstate
nvm/bitcoin/testnet3/indexes 768G 2.9G 765G 0% /bitcoin/testnet3/indexes
nvm/electrs 765G 128K 765G 0% /electrs
nvm/electrs/liquid 765G 104K 765G 0% /electrs/liquid
nvm/electrs/liquid/cache 765G 7.8M 765G 0% /electrs/liquid/newindex/cache
nvm/electrs/liquid/history 766G 886M 765G 0% /electrs/liquid/newindex/history
nvm/electrs/liquid/txstore 775G 10G 765G 1% /electrs/liquid/newindex/txstore
nvm/electrs/liquidtestnet 765G 112K 765G 0% /electrs/liquidtestnet
nvm/electrs/liquidtestnet/cache 765G 96K 765G 0% /electrs/liquidtestnet/newindex/cache
nvm/electrs/liquidtestnet/history 765G 96K 765G 0% /electrs/liquidtestnet/newindex/history
nvm/electrs/liquidtestnet/txstore 765G 96K 765G 0% /electrs/liquidtestnet/newindex/txstore
nvm/electrs/mainnet 765G 112K 765G 0% /electrs/mainnet
nvm/electrs/mainnet/cache 765G 4.4M 765G 0% /electrs/mainnet/newindex/cache
nvm/electrs/mainnet/history 1.0T 300G 765G 28% /electrs/mainnet/newindex/history
nvm/electrs/mainnet/txstore 1.3T 530G 765G 41% /electrs/mainnet/newindex/txstore
nvm/electrs/signet 766G 522M 765G 0% /electrs/signet
nvm/electrs/testnet 765G 104K 765G 0% /electrs/testnet
nvm/electrs/testnet/cache 765G 1.6M 765G 0% /electrs/testnet/newindex/cache
nvm/electrs/testnet/history 784G 19G 765G 2% /electrs/testnet/newindex/history
nvm/electrs/testnet/txstore 803G 38G 765G 5% /electrs/testnet/newindex/txstore
nvm/elements 766G 927M 765G 0% /elements
nvm/elements/electrs 766G 716M 765G 0% /elements/electrs
nvm/elements/liquidv1 777G 11G 765G 1% /elements/liquidv1
nvm/mempool 789G 24G 765G 3% /mempool
nvm/mysql 766G 648M 765G 0% /mysql
tmpfs 1.0G 1.3M 1.0G 0% /var/cache/nginx
tmpfs 3.0G 1.9G 1.1G 63% /bisq/statsnode-data/btc_mainnet/db/json
```
### Build Dependencies
You'll probably need these:
```
pkg install -y zsh sudo git screen vim-console curl wget neovim rsync
pkg install -y openssl openssh-portable open-vm-tools-nox11 py37-pip
pkg install -y boost-libs autoconf automake gmake gcc libevent libtool pkgconf
pkg install -y mariadb55-server mariadb55-client nginx py37-certbot-nginx npm
pkg install -y zsh sudo git screen curl wget neovim rsync nginx openssl openssh-portable py38-pip py38-certbot-nginx boost-libs autoconf automake gmake gcc libevent libtool pkgconf mariadb105-server mariadb105-client
```
### NodeJS / npm
I recommend to build nodejs / npm from source using nvm:
```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | zsh
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh
source $HOME/.zshrc
nvm install node
nvm install v16.10.0
nvm alias default node
```
@@ -107,11 +115,18 @@ DataDirectory /var/db/tor
DataDirectoryGroupReadable 1
HiddenServiceDir /var/db/tor/mempool
HiddenServicePort 80 127.0.0.1:80
HiddenServicePort 80 127.0.0.1:81
HiddenServiceVersion 3
HiddenServiceDir /var/db/tor/bisq
HiddenServicePort 80 127.0.0.1:82
HiddenServiceVersion 3
HiddenServiceDir /var/db/tor/liquid
HiddenServicePort 80 127.0.0.1:83
HiddenServiceVersion 3
```
### Bitcoin
Build [Bitcoin Core](https://github.com/bitcoin/bitcoin) from source. Alternatively, install the OS packages:
@@ -121,25 +136,35 @@ pkg install -y bitcoin-daemon bitcoin-utils
Configure your bitcoin.conf like this:
```
datadir=/bitcoin
server=1
daemon=1
txindex=1
listen=1
discover=1
txindex=1
dbcache=3700
maxconnections=1337
par=16
dbcache=4096
maxmempool=1337
mempoolexpiry=999999
maxconnections=42
onion=127.0.0.1:9050
rpcallowip=127.0.0.1
rpcuser=0cd862dce678b830bd2aa36f10b9b6b2
rpcpassword=2d89d36cac4a13c87b5d19ef8f577e37
rpcuser=foo
rpcpassword=bar
[main]
bind=127.0.0.1:8333
rpcbind=127.0.0.1:8332
whitelist=bloomfilter@127.0.0.1
[test]
daemon=1
bind=127.0.0.1:18333
rpcbind=127.0.0.1:18332
[signet]
daemon=1
bind=127.0.0.1:38333
rpcbind=127.0.0.1:38332
```
### Elements
@@ -158,15 +183,39 @@ Configure your elements.conf like this:
server=1
daemon=1
listen=1
chain=liquidv1
rpcuser=liquiduser
rpcpassword=liquidpass
validatepegin=1
rpcuser=foo
rpcpassword=bar
mainchainrpchost=127.0.0.1
mainchainrpcport=8332
mainchainrpcuser=user
mainchainrpcpassword=pass
mainchainrpcuser=foo
mainchainrpcpassword=bar
txindex=1
[liquidv1]
validatepegin=1
mainchainrpcport=8332
[liquidtestnet]
validatepegin=0
anyonecanspendaremine=0
initialfreecoins=2100000000000000
con_dyna_deploy_start=0
con_max_block_sig_size=150
checkblockindex=0
fallbackfee=0.00000100
con_has_parent_chain=0
parentgenesisblockhash=NULL
pubkeyprefix=36
scriptprefix=19
blindedprefix=23
bech32_hrp=tex
blech32_hrp=tlq
pchmessagestart=410edd62
dynamic_epoch_length=1000
signblockscript=51210217e403ddb181872c32a0cd468c710040b2f53d8cac69f18dad07985ee37e9a7151ae
evbparams=dynafed:0:::
addnode=liquid-testnet.blockstream.com:18892
addnode=liquidtestnet.com:18891
addnode=liquid.network:18444
```
Start elementsd and wait for it to sync the Liquid blockchain.
@@ -180,33 +229,42 @@ cd electrs
git checkout new-index
```
You'll need 3 instances, one for each network. Build one at a time:
You'll need one instance per network. Build and run them one at a time:
```
./electrs-start-mainnet
./electrs-start-testnet
./electrs-start-signet
./electrs-start-liquid
./electrs-start-liquidtestnet
```
### MariaDB
Import historical mempool fee database snapshot, or the blank mariadb structure if none:
Import historical mempool fee database snapshot:
```
mysql -u root
create database mempool;
grant all on mempool.* to 'mempool'@'localhost' identified by 'mempool';
create database tmempool;
grant all on tmempool.* to 'tmempool'@'localhost' identified by 'tmempool';
create database lmempool;
grant all on lmempool.* to 'lmempool'@'localhost' identified by 'lmempool';
create database mempool_testnet;
grant all on mempool_testnet.* to 'mempool_testnet'@'localhost' identified by 'mempool_testnet';
create database mempool_signet;
grant all on mempool_signet.* to 'mempool_signet'@'localhost' identified by 'mempool_signet';
create database mempool_liquid;
grant all on mempool_liquid.* to 'mempool_liquid'@'localhost' identified by 'mempool_liquid';
create database mempool_liquidtestnet;
grant all on mempool_liquidtestnet.* to 'mempool_liquidtestnet'@'localhost' identified by 'mempool_liquidtestnet';
```
Then import
### Bisq
Build bisq-statsnode normally and run using options like this:
```
mysql -u mempool -p mempool < /mempool/mempool/mariadb-structure.sql
mysql -u tmempool -p tmempool < /mempool/mempool/mariadb-structure.sql
mysql -u lmempool -p lmempool < /mempool/mempool/mariadb-structure.sql
./bisq-statsnode --dumpBlockchainData=true --dumpStatistics=true
```
If bisq is happy, it should dump JSON files for Bisq Markets and BSQ data into /bisq that the mempool backend will use.
### Mempool
After all 3 electrs instances are fully indexed, install your 3 mempool nodes:
@@ -224,13 +282,15 @@ Finally, start your 3 mempool backends:
Get SSL certificate using certbot:
```
certbot --nginx -d mempool.space
certbot --nginx -d mempool.ninja
```
Install nginx.conf from this repo, edit as necessary:
Make a symlink from /usr/local/etc/nginx/mempool to /mempool/mempool, and copy the nginx.conf and edit as necessary. You probably only need to edit the top-level nginx.conf file.
```
cp nginx.conf /usr/local/etc/nginx/nginx.conf
vi /usr/local/etc/nginx/nginx.conf
cd /usr/local/etc/nginx
ln -s /mempool/mempool
cp /mempool/mempool/nginx.conf .
vi nginx.conf
```
Restart nginx
@@ -241,4 +301,3 @@ service nginx restart
### Done
Your site should look like https://mempool.space/
If it doesn't ask wiz on Keybase DM or Twitter for help.

View File

@@ -0,0 +1,30 @@
#!/usr/local/bin/zsh
cd "$HOME/electrs"
#source $HOME/.cargo/env
#export PATH=$HOME/.cargo/bin:$PATH
until false
do
# patch code for FreeBSD
if grep XBS5 "$HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/sysconf-0.3.4/src/raw.rs" ; then
grep -v XBS5 $HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/sysconf-0.3.4/src/raw.rs > /tmp/foo && \
mv /tmp/foo $HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/sysconf-0.3.4/src/raw.rs
fi
cargo run \
--release \
--features liquid \
--bin electrs \
-- \
-vv \
--asset-db-path "$HOME/asset_registry_testnet_db" \
--address-search \
--cors '*' \
--db-dir /electrs \
--network liquidtestnet \
--daemon-dir $HOME \
--http-addr '[::]:3004' \
--cookie 'foo:bar' \
--precache-scripts $HOME/electrs/contrib/popular-scripts.txt
sleep 1
done

View File

@@ -0,0 +1,39 @@
{
"MEMPOOL": {
"NETWORK": "liquid",
"BACKEND": "esplora",
"HTTP_PORT": 8994,
"MINED_BLOCKS_CACHE": 144,
"SPAWN_CLUSTER_PROCS": 0,
"API_URL_PREFIX": "/api/v1/",
"WEBSOCKET_REFRESH_RATE_MS": 2000
},
"SYSLOG" : {
"MIN_PRIORITY": "debug"
},
"CORE_RPC": {
"PORT": 7040,
"USERNAME": "foo",
"PASSWORD": "bar"
},
"SECOND_CORE_RPC": {
"PORT": 8332,
"USERNAME": "foo",
"PASSWORD": "bar"
},
"ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4004"
},
"DATABASE": {
"ENABLED": true,
"HOST": "127.0.0.1",
"PORT": 3306,
"USERNAME": "mempool_liquidtestnet",
"PASSWORD": "mempool_liquidtestnet",
"DATABASE": "mempool_liquidtestnet"
},
"STATISTICS": {
"ENABLED": true,
"TX_PER_SECOND_SAMPLE_PERIOD": 150
}
}

View File

@@ -1,6 +1,7 @@
{
"TESTNET_ENABLED": true,
"LIQUID_ENABLED": true,
"LIQUID_TESTNET_ENABLED": true,
"BISQ_ENABLED": true,
"BISQ_SEPARATE_BACKEND": true,
"SIGNET_ENABLED": true,

Some files were not shown because too many files have changed in this diff Show More