Add support for regtest

This commit is contained in:
Felipe Knorr Kuhn 2026-01-29 15:41:04 -08:00
parent 0e6f9a7621
commit a23850de14
No known key found for this signature in database
GPG key ID: 79619B52BB097C1A
43 changed files with 277 additions and 36 deletions

View file

@ -483,7 +483,7 @@ class BitcoinRoutes {
private async getBlocks(req: Request, res: Response) {
try {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(await blocks.$getBlocks(height, 15));
@ -497,7 +497,7 @@ class BitcoinRoutes {
private async getBlocksByBulk(req: Request, res: Response) {
try {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid - Not implemented
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid - Not implemented
handleError(req, res, 404, `This API is only available for Bitcoin networks`);
return;
}
@ -539,7 +539,7 @@ class BitcoinRoutes {
private async getChainTips(req: Request, res: Response) {
try {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
const tips = await chainTips.getChainTips();
if (tips.length > 0) {
@ -559,7 +559,7 @@ class BitcoinRoutes {
private async getStaleTips(req: Request, res: Response) {
try {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
const tips = await chainTips.getStaleTips();
if (tips.length > 0) {

View file

@ -327,7 +327,7 @@ class Blocks {
extras.totalInputAmt = null;
}
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
let pool: PoolTag;
if (coinbaseTx !== undefined) {
pool = await this.$findBlockMiner(coinbaseTx);
@ -1282,7 +1282,7 @@ class Blocks {
}
// Not Bitcoin network, return the block as it from the bitcoin backend
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false) {
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false) {
return await bitcoinCoreApi.$getBlock(hash);
}
@ -1525,7 +1525,7 @@ class Blocks {
}
public async $getBlockAuditSummary(hash: string): Promise<BlockAudit | null> {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) {
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) {
return BlocksAuditsRepository.$getBlockAudit(hash);
} else {
return null;
@ -1533,7 +1533,7 @@ class Blocks {
}
public async $getBlockTxAuditSummary(hash: string, txid: string): Promise<TransactionAudit | null> {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) {
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) {
return BlocksAuditsRepository.$getBlockTxAudit(hash, txid);
} else {
return null;

View file

@ -376,6 +376,7 @@ export class Common {
'testnet4': 42_000,
'testnet': 2_900_000,
'signet': 211_000,
'regtest': 0,
'': 863_500,
};
static isNonStandardVersion(tx: TransactionExtended, height?: number): boolean {
@ -399,6 +400,7 @@ export class Common {
'testnet4': 42_000,
'testnet': 2_900_000,
'signet': 211_000,
'regtest': 0,
'': 863_500,
};
static isNonStandardAnchor(vin: IEsploraApi.Vin, height?: number): boolean {
@ -419,6 +421,7 @@ export class Common {
'testnet4': 90_500,
'testnet': 4_550_000,
'signet': 260_000,
'regtest': 0,
'': 905_000,
};
static isStandardEphemeralDust(tx: TransactionExtended, height?: number): boolean {
@ -439,6 +442,7 @@ export class Common {
'testnet4': 108_000,
'testnet': 4_750_000,
'signet': 276_500,
'regtest': 0,
'': 921_000,
};
static MAX_DATACARRIER_BYTES = 83;
@ -461,6 +465,7 @@ export class Common {
'testnet4': 108_000,
'testnet': 4_750_000,
'signet': 276_500,
'regtest': 0,
'': 921_000,
};
static isNonStandardLegacySigops(tx: TransactionExtended, height?: number): boolean {
@ -854,7 +859,7 @@ export class Common {
static indexingEnabled(): boolean {
return (
['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) &&
['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) &&
config.DATABASE.ENABLED === true &&
config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== 0
);

View file

@ -104,7 +104,7 @@ class DatabaseMigration {
private async $createMissingTablesAndIndexes(databaseSchemaVersion: number) {
await this.$setStatisticsAddedIndexedFlag(databaseSchemaVersion);
const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK);
const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK);
await this.$executeQuery(this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs'));
await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
@ -1300,7 +1300,7 @@ class DatabaseMigration {
*/
private getMigrationQueriesFromVersion(version: number): string[] {
const queries: string[] = [];
const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK);
const isBitcoin = ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK);
if (version < 1) {
if (config.MEMPOOL.NETWORK !== 'liquid' && config.MEMPOOL.NETWORK !== 'liquidtestnet') {

View file

@ -262,7 +262,7 @@ class LiquidRoutes {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
if (['testnet', 'signet', 'liquidtestnet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (['testnet', 'signet', 'liquidtestnet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
handleError(req, res, 400, 'Prices are not available on testnets.');
return;
}

View file

@ -46,7 +46,7 @@ class MempoolBlocks {
}
public async updatePools$(): Promise<void> {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false) {
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false) {
this.pools = {};
return;
}

View file

@ -53,7 +53,7 @@ class MiningRoutes {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
if (['testnet', 'signet', 'liquidtestnet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (['testnet', 'signet', 'liquidtestnet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
handleError(req, res, 400, 'Prices are not available on testnets.');
return;
}
@ -394,7 +394,7 @@ class MiningRoutes {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
handleError(req, res, 400, 'Acceleration data is not available.');
return;
}
@ -409,7 +409,7 @@ class MiningRoutes {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString());
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
handleError(req, res, 400, 'Acceleration data is not available.');
return;
}
@ -425,7 +425,7 @@ class MiningRoutes {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
handleError(req, res, 400, 'Acceleration data is not available.');
return;
}
@ -440,7 +440,7 @@ class MiningRoutes {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
handleError(req, res, 400, 'Acceleration data is not available.');
return;
}
@ -455,7 +455,7 @@ class MiningRoutes {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
handleError(req, res, 400, 'Acceleration data is not available.');
return;
}

View file

@ -195,7 +195,7 @@ class PoolsParser {
let firstKnownBlockPool = 130635; // https://mempool.space/block/0000000000000a067d94ff753eec72830f1205ad3a4c216a08a80c832e551a52
if (config.MEMPOOL.NETWORK === 'testnet') {
firstKnownBlockPool = 21106; // https://mempool.space/testnet/block/0000000070b701a5b6a1b965f6a38e0472e70b2bb31b973e4638dec400877581
} else if (['signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
} else if (['signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
firstKnownBlockPool = 0;
}

View file

@ -6,7 +6,7 @@ interface IConfig {
MEMPOOL: {
ENABLED: boolean;
OFFICIAL: boolean;
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet' | 'regtest';
BACKEND: 'esplora' | 'electrum' | 'none';
HTTP_PORT: number;
UNIX_SOCKET_PATH: string;

View file

@ -161,7 +161,7 @@ class Server {
this.setUpWebsocketHandling();
await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it
if (config.DATABASE.ENABLED === true && config.MEMPOOL.ENABLED && ['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && !poolsUpdater.currentSha) {
if (config.DATABASE.ENABLED === true && config.MEMPOOL.ENABLED && ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && !poolsUpdater.currentSha) {
logger.err(`Failed to retreive pools-v2.json sha, cannot run block indexing. Please make sure you've set valid urls in your mempool-config.json::MEMPOOL::POOLS_JSON_URL and mempool-config.json::MEMPOOL::POOLS_JSON_TREE_UR, aborting now`);
return process.exit(1);
}

View file

@ -134,7 +134,7 @@ class Indexer {
switch (task) {
case 'blocksPrices': {
if (!['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) && config.FIAT_PRICE.ENABLED) {
if (!['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && config.FIAT_PRICE.ENABLED) {
let lastestPriceId;
try {
lastestPriceId = await PricesRepository.$getLatestPriceId();

View file

@ -102,7 +102,7 @@ class PoolsRepository {
if (parse) {
rows[0].regexes = JSON.parse(rows[0].regexes);
}
if (['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
rows[0].addresses = []; // pools-v2.json only contains mainnet addresses
} else if (parse) {
rows[0].addresses = JSON.parse(rows[0].addresses);
@ -134,7 +134,7 @@ class PoolsRepository {
if (parse) {
rows[0].regexes = JSON.parse(rows[0].regexes);
}
if (['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
rows[0].addresses = []; // pools.json only contains mainnet addresses
} else if (parse) {
rows[0].addresses = JSON.parse(rows[0].addresses);

View file

@ -31,7 +31,7 @@ class PoolsUpdater {
}
public async updatePoolsJson(): Promise<void> {
if (['mainnet', 'testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK) === false ||
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) === false ||
config.MEMPOOL.ENABLED === false
) {
return;

View file

@ -134,7 +134,7 @@ class PriceUpdater {
}
public async $run(): Promise<void> {
if (['testnet', 'signet', 'testnet4'].includes(config.MEMPOOL.NETWORK)) {
if (['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
// Coins have no value on testnet/signet, so we want to always show 0
return;
}

View file

@ -27,6 +27,7 @@ __MAINNET_ENABLED__=${MAINNET_ENABLED:=true}
__TESTNET_ENABLED__=${TESTNET_ENABLED:=false}
__TESTNET4_ENABLED__=${TESTNET_ENABLED:=false}
__SIGNET_ENABLED__=${SIGNET_ENABLED:=false}
__REGTEST_ENABLED__=${REGTEST_ENABLED:=false}
__LIQUID_ENABLED__=${LIQUID_ENABLED:=false}
__LIQUID_TESTNET_ENABLED__=${LIQUID_TESTNET_ENABLED:=false}
__ITEMS_PER_PAGE__=${ITEMS_PER_PAGE:=10}
@ -46,6 +47,7 @@ __AUDIT__=${AUDIT:=false}
__MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
__TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0}
__SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0}
__REGTEST_BLOCK_AUDIT_START_HEIGHT__=${REGTEST_BLOCK_AUDIT_START_HEIGHT:=0}
__ACCELERATOR__=${ACCELERATOR:=false}
__ACCELERATOR_BUTTON__=${ACCELERATOR_BUTTON:=true}
__SERVICES_API__=${SERVICES_API:=https://mempool.space/api/v1/services}
@ -59,6 +61,7 @@ export __MAINNET_ENABLED__
export __TESTNET_ENABLED__
export __TESTNET4_ENABLED__
export __SIGNET_ENABLED__
export __REGTEST_ENABLED__
export __LIQUID_ENABLED__
export __LIQUID_TESTNET_ENABLED__
export __ITEMS_PER_PAGE__
@ -78,6 +81,7 @@ export __AUDIT__
export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
export __REGTEST_BLOCK_AUDIT_START_HEIGHT__
export __ACCELERATOR__
export __ACCELERATOR_BUTTON__
export __SERVICES_API__

View file

@ -2,6 +2,7 @@
"TESTNET_ENABLED": false,
"TESTNET4_ENABLED": false,
"SIGNET_ENABLED": false,
"REGTEST_ENABLED": false,
"LIQUID_ENABLED": false,
"LIQUID_TESTNET_ENABLED": false,
"MAINNET_ENABLED": true,
@ -21,6 +22,7 @@
"MAINNET_BLOCK_AUDIT_START_HEIGHT": 0,
"TESTNET_BLOCK_AUDIT_START_HEIGHT": 0,
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 0,
"REGTEST_BLOCK_AUDIT_START_HEIGHT": 0,
"LIGHTNING": false,
"HISTORICAL_PRICE": true,
"ADDITIONAL_CURRENCIES": false,

View file

@ -24,7 +24,7 @@ PROXY_CONFIG = [
'/api/**', '!/api/v1/ws',
'!/liquid', '!/liquid/**', '!/liquid/',
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
'/testnet/api/**', '/signet/api/**', '/testnet4/api/**'
'/testnet/api/**', '/signet/api/**', '/testnet4/api/**', '/regtest/api/**'
],
target: "https://mempool.space",
ws: true,

View file

@ -78,6 +78,27 @@ PROXY_CONFIG.push(...[
"^/testnet": ""
},
},
{
context: ['/regtest/api/v1/**'],
target: `http://127.0.0.1:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/regtest": ""
},
},
{
context: ['/regtest/api/**'],
target: `http://127.0.0.1:3000`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/regtest/api": ""
},
},
/* Optional proxy to route dev to official acceleration services
{
context: ['/api/v1/services/accelerator/**'],

View file

@ -78,6 +78,27 @@ PROXY_CONFIG.push(...[
"^/testnet": ""
},
},
{
context: ['/regtest/api/v1/**'],
target: `http://localhost:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/regtest": ""
},
},
{
context: ['/regtest/api/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/regtest/api/": "/api/v1/"
},
},
{
context: ['/api/v1/services/**'],
target: `http://localhost:9000`,

View file

@ -62,6 +62,27 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
}
PROXY_CONFIG.push(...[
{
context: ['/regtest/api/v1/**'],
target: `http://localhost:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/regtest": ""
},
},
{
context: ['/regtest/api/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/regtest/api/": "/api/v1/"
},
},
{
context: ['/api/v1/services/**'],
target: `http://localhost:9000`,

View file

@ -133,6 +133,49 @@ let routes: Routes = [
},
]
},
{
path: 'regtest',
children: [
{
path: 'mining/blocks',
redirectTo: 'blocks',
pathMatch: 'full'
},
{
path: '',
pathMatch: 'full',
loadChildren: () => import('@app/bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
data: { preload: true },
},
{
path: '',
loadChildren: () => import('@app/master-page.module').then(m => m.MasterPageModule),
data: { preload: true },
},
{
path: 'widget/wallet',
children: [],
component: AddressGroupComponent,
data: {
networkSpecific: true,
}
},
{
path: 'status',
data: { networks: ['bitcoin', 'liquid'] },
component: StatusViewComponent
},
{
path: '',
loadChildren: () => import('@app/bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
data: { preload: true },
},
{
path: '**',
redirectTo: '/regtest'
},
]
},
{
path: '',
pathMatch: 'full',
@ -177,6 +220,10 @@ let routes: Routes = [
path: 'signet',
loadChildren: () => import('@app/previews.module').then(m => m.PreviewsModule)
},
{
path: 'regtest',
loadChildren: () => import('@app/previews.module').then(m => m.PreviewsModule)
},
],
},
{

View file

@ -73,6 +73,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
testnet4: ['var(--testnet)', 'var(--testnet-alt)'],
signet: ['var(--signet)', 'var(--signet-alt)'],
regtest: ['var(--regtest)', 'var(--regtest-alt)'],
};
constructor(

View file

@ -39,6 +39,7 @@ export class ClockComponent implements OnInit {
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
testnet4: ['var(--testnet)', 'var(--testnet-alt)'],
signet: ['var(--signet)', 'var(--signet-alt)'],
regtest: ['var(--regtest)', 'var(--regtest-alt)'],
};
constructor(

View file

@ -44,7 +44,7 @@
</ng-container>
</a>
<div ngbDropdown (window:resize)="onResize()" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED">
<div ngbDropdown (window:resize)="onResize()" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.REGTEST_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split d-flex justify-content-center align-items-center" aria-haspopup="true">
<app-svg-images class="d-flex justify-content-center align-items-center current-network-svg" [name]="network.val === '' ? 'liquid' : network.val" width="20" height="20" viewBox="0 0 125 125"></app-svg-images>
</button>
@ -53,6 +53,7 @@
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['signet'] || '/signet')" ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['testnet'] || '/testnet')" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet3</a>
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['testnet4'] || '/testnet4')" ngbDropdownItem *ngIf="env.TESTNET4_ENABLED" class="testnet"><app-svg-images name="testnet4" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet4</a>
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['regtest'] || '/regtest')" ngbDropdownItem *ngIf="env.REGTEST_ENABLED" class="regtest"><app-svg-images name="regtest" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Regtest</a>
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<a ngbDropdownItem class="liquid mr-1" [class.active]="network.val === 'liquid'" [routerLink]="networkPaths['liquid'] || '/'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
<a ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquidtestnet'" [routerLink]="networkPaths['liquidtestnet'] || '/testnet'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>

View file

@ -157,6 +157,10 @@ nav {
background-color: var(--signet);
}
.regtest.active {
background-color: var(--regtest);
}
.dropdown-divider {
border-top: 1px solid #121420;
}

View file

@ -66,6 +66,7 @@
<a ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet" [class.active]="network.val === 'signet'" [routerLink]="networkPaths['signet'] || '/signet'"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
<a ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" [routerLink]="networkPaths['testnet'] || '/testnet'"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet3</a>
<a ngbDropdownItem *ngIf="env.TESTNET4_ENABLED" class="testnet4" [class.active]="network.val === 'testnet4'" [routerLink]="networkPaths['testnet4'] || '/testnet4'"><app-svg-images name="testnet4" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet4</a>
<a ngbDropdownItem *ngIf="env.REGTEST_ENABLED" class="regtest" [class.active]="network.val === 'regtest'" [routerLink]="networkPaths['regtest'] || '/regtest'"><app-svg-images name="regtest" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Regtest</a>
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquid'] || '')" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid" [class.active]="network.val === 'liquid'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquidtestnet'] || '/testnet')" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquid'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>
</div>

View file

@ -175,6 +175,10 @@ nav {
background-color: var(--signet);
}
.regtest.active {
background-color: var(--regtest);
}
.dropdown-divider {
border-top: 1px solid #121420;
}

View file

@ -85,6 +85,7 @@ export class MasterPageComponent implements OnInit, OnDestroy {
this.env.TESTNET_ENABLED,
this.env.TESTNET4_ENABLED,
this.env.SIGNET_ENABLED,
this.env.REGTEST_ENABLED,
this.env.LIQUID_ENABLED,
this.env.LIQUID_TESTNET_ENABLED,
this.env.MAINNET_ENABLED,

View file

@ -27,6 +27,7 @@ export class StaleList implements OnInit {
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
testnet4: ['var(--testnet)', 'var(--testnet-alt)'],
signet: ['var(--signet)', 'var(--signet-alt)'],
regtest: ['var(--regtest)', 'var(--regtest-alt)'],
};
constructor(

View file

@ -184,6 +184,9 @@
<ng-container *ngSwitchCase="'testnet4'">
<ng-component *ngTemplateOutlet="bitcoinLogo; context: {$implicit: '#5fd15c', width, height, viewBox}"></ng-component>
</ng-container>
<ng-container *ngSwitchCase="'regtest'">
<ng-component *ngTemplateOutlet="bitcoinLogo; context: {$implicit: '#a5a5a5', width, height, viewBox}"></ng-component>
</ng-container>
<ng-container *ngSwitchCase="'liquid'">
<ng-component *ngTemplateOutlet="liquidLogo; context: {$implicit: '', width, height, viewBox}"></ng-component>
</ng-container>

View file

@ -95,6 +95,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
testnet4: ['#4edf77', '#10a0af', '#4edf7700'],
// signet: ['#6f1d5d', '#471850'],
signet: ['#d24fc8', '#a84fd2', '#d24fc800'],
regtest: ['var(--regtest)', 'var(--regtest-alt)', '#7d7d7d00'],
};
gradient: string[] = ['var(--primary)', 'var(--primary)'];

View file

@ -45,6 +45,7 @@ export class EnterpriseService {
this.stateService.env.LIQUID_ENABLED = false;
this.stateService.env.LIQUID_TESTNET_ENABLED = false;
this.stateService.env.SIGNET_ENABLED = false;
this.stateService.env.REGTEST_ENABLED = false;
}
fetchSubdomainInfo(): void {

View file

@ -17,6 +17,7 @@ export class NavigationService {
{ name: 'testnet', path: this.stateService.env.ROOT_NETWORK === 'testnet' ? '/' : '/testnet' },
{ name: 'testnet4', path: this.stateService.env.ROOT_NETWORK === 'testnet4' ? '/' : '/testnet4' },
{ name: 'signet', path: this.stateService.env.ROOT_NETWORK === 'signet' ? '/' : '/signet' },
{ name: 'regtest', path: this.stateService.env.ROOT_NETWORK === 'regtest' ? '/' : '/regtest' },
],
},
liquid: {
@ -89,7 +90,7 @@ export class NavigationService {
}
if (route.url?.length) {
path = [path, ...route.url.map(segment => segment.path).filter(path => {
return path.length && !['testnet', 'testnet4', 'signet'].includes(path);
return path.length && !['testnet', 'testnet4', 'signet', 'regtest'].includes(path);
})].join('/');
}
route = route.firstChild;

View file

@ -50,6 +50,7 @@ export interface Env {
TESTNET_ENABLED: boolean;
TESTNET4_ENABLED: boolean;
SIGNET_ENABLED: boolean;
REGTEST_ENABLED: boolean;
LIQUID_ENABLED: boolean;
LIQUID_TESTNET_ENABLED: boolean;
ITEMS_PER_PAGE: number;
@ -73,10 +74,12 @@ export interface Env {
TESTNET_BLOCK_AUDIT_START_HEIGHT: number;
TESTNET4_BLOCK_AUDIT_START_HEIGHT: number;
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
REGTEST_BLOCK_AUDIT_START_HEIGHT: number;
MAINNET_TX_FIRST_SEEN_START_HEIGHT: number;
TESTNET_TX_FIRST_SEEN_START_HEIGHT: number;
TESTNET4_TX_FIRST_SEEN_START_HEIGHT: number;
SIGNET_TX_FIRST_SEEN_START_HEIGHT: number;
REGTEST_TX_FIRST_SEEN_START_HEIGHT: number;
HISTORICAL_PRICE: boolean;
ACCELERATOR: boolean;
ACCELERATOR_BUTTON: boolean;
@ -95,6 +98,7 @@ const defaultEnv: Env = {
'TESTNET_ENABLED': false,
'TESTNET4_ENABLED': false,
'SIGNET_ENABLED': false,
'REGTEST_ENABLED': false,
'LIQUID_ENABLED': false,
'LIQUID_TESTNET_ENABLED': false,
'BASE_MODULE': 'mempool',
@ -118,10 +122,12 @@ const defaultEnv: Env = {
'TESTNET_BLOCK_AUDIT_START_HEIGHT': 0,
'TESTNET4_BLOCK_AUDIT_START_HEIGHT': 0,
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
'REGTEST_BLOCK_AUDIT_START_HEIGHT': 0,
'MAINNET_TX_FIRST_SEEN_START_HEIGHT': 0,
'TESTNET_TX_FIRST_SEEN_START_HEIGHT': 0,
'TESTNET4_TX_FIRST_SEEN_START_HEIGHT': 0,
'SIGNET_TX_FIRST_SEEN_START_HEIGHT': 0,
'REGTEST_TX_FIRST_SEEN_START_HEIGHT': 0,
'HISTORICAL_PRICE': true,
'ACCELERATOR': false,
'ACCELERATOR_BUTTON': true,
@ -399,7 +405,7 @@ export class StateService {
// (?:preview\/)? optional "preview" prefix (non-capturing)
// (testnet|signet)/ network string (captured as networkMatches[1])
// ($|\/) network string must end or end with a slash
let networkMatches: object = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet4?|signet)($|\/)/);
let networkMatches: object = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet4?|signet|regtest)($|\/)/);
if (!networkMatches && this.env.ROOT_NETWORK) {
networkMatches = { 1: this.env.ROOT_NETWORK };
@ -429,6 +435,12 @@ export class StateService {
this.networkChanged$.next('testnet4');
}
return;
case 'regtest':
if (this.network !== 'regtest') {
this.network = 'regtest';
this.networkChanged$.next('regtest');
}
return;
default:
if (this.env.BASE_MODULE !== 'mempool') {
if (this.network !== this.env.BASE_MODULE) {

View file

@ -50,6 +50,13 @@ const ADDRESS_PREFIXES = {
},
bech32: 'tb1',
},
regtest: {
base58: {
pubkey: ['m', 'n'],
script: '2',
},
bech32: 'bcrt1',
},
liquid: {
base58: {
pubkey: ['P','Q'],

View file

@ -154,7 +154,7 @@ export function nextRoundNumber(num: number): number {
export function seoDescriptionNetwork(network: string): string {
if( network === 'liquidtestnet' || network === 'testnet' ) {
return ' Testnet';
} else if( network === 'signet' || network === 'testnet' || network === 'testnet4') {
} else if( network === 'signet' || network === 'testnet' || network === 'testnet4' || network === 'regtest') {
return ' ' + network.charAt(0).toUpperCase() + network.slice(1);
}
return '';

View file

@ -91,12 +91,13 @@
<p *ngIf="mempoolSpaceBuild"><a [routerLink]="['/research' | relativeUrl]" i18n="mempool-research">Research</a></p>
</div>
<div class="links" *ngIf="officialMempoolSpace || env.TESTNET_ENABLED || env.TESTNET4_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED else toolBox" >
<div class="links" *ngIf="officialMempoolSpace || env.TESTNET_ENABLED || env.TESTNET4_ENABLED || env.SIGNET_ENABLED || env.REGTEST_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED else toolBox" >
<p class="category" i18n="footer.networks">Networks</p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== '') && (currentNetwork !== 'mainnet')"><a [href]="networkLink('mainnet')" i18n="footer.mainnet-explorer">Mainnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'testnet') && env.TESTNET_ENABLED"><a [href]="networkLink('testnet')" i18n="footer.testnet3-explorer">Testnet3 Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'testnet4') && env.TESTNET4_ENABLED"><a [href]="networkLink('testnet4')" i18n="footer.testnet4-explorer">Testnet4 Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'signet') && env.SIGNET_ENABLED"><a [href]="networkLink('signet')" i18n="footer.signet-explorer">Signet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || (env.BASE_MODULE === 'mempool')) && (currentNetwork !== 'regtest') && env.REGTEST_ENABLED"><a [href]="networkLink('regtest')" i18n="footer.regtest-explorer">Regtest Explorer</a></p>
<p *ngIf="(officialMempoolSpace || env.LIQUID_ENABLED) && (currentNetwork !== 'liquidtestnet')"><a [href]="networkLink('liquidtestnet')" i18n="footer.liquid-testnet-explorer">Liquid Testnet Explorer</a></p>
<p *ngIf="(officialMempoolSpace || env.LIQUID_ENABLED) && (currentNetwork !== 'liquid')"><a [href]="networkLink('liquid')" i18n="footer.liquid-explorer">Liquid Explorer</a></p>
</div>

View file

@ -90,6 +90,20 @@ const ADDRESS_CHARS: {
+ `{6,100}`
+ `)`,
},
regtest: {
base58: `[mn2]` // Same as testnet
+ BASE58_CHARS
+ `{33,34}`,
bech32: `(?:`
+ `bcrt1` // Starts with bcrt1
+ BECH32_CHARS_LW
+ `{6,100}`
+ `|`
+ `BCRT1` // All upper case version
+ BECH32_CHARS_UP
+ `{6,100}`
+ `)`,
},
liquid: {
base58: `[GHPQ]` // PQ is P2PKH, GH is P2SH
+ BASE58_CHARS
@ -142,7 +156,7 @@ const ADDRESS_CHARS: {
type RegexTypeNoAddrNoBlockHash = | `transaction` | `blockheight` | `date` | `timestamp`;
export type RegexType = `address` | `blockhash` | RegexTypeNoAddrNoBlockHash;
export const NETWORKS = [`mainnet`, `testnet4`, `testnet`, `signet`, `liquid`, `liquidtestnet`] as const;
export const NETWORKS = [`mainnet`, `testnet4`, `testnet`, `signet`, `regtest`, `liquid`, `liquidtestnet`] as const;
export type Network = typeof NETWORKS[number]; // Turn const array into union type
export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS
@ -162,6 +176,8 @@ function isNetworkAvailable(network: Network, env: Env): boolean {
return env.TESTNET4_ENABLED === true;
case 'signet':
return env.SIGNET_ENABLED === true;
case 'regtest':
return env.REGTEST_ENABLED === true;
case 'liquid':
return env.LIQUID_ENABLED === true;
case 'liquidtestnet':
@ -176,7 +192,7 @@ function isNetworkAvailable(network: Network, env: Env): boolean {
export function needBaseModuleChange(fromBaseModule: 'mempool' | 'liquid', toNetwork: Network): boolean {
if (!toNetwork) {return false;} // No target network means no change needed
if (fromBaseModule === 'mempool') {
return toNetwork !== 'mainnet' && toNetwork !== 'testnet' && toNetwork !== 'testnet4' && toNetwork !== 'signet';
return toNetwork !== 'mainnet' && toNetwork !== 'testnet' && toNetwork !== 'testnet4' && toNetwork !== 'signet' && toNetwork !== 'regtest';
}
if (fromBaseModule === 'liquid') {
return toNetwork !== 'liquid' && toNetwork !== 'liquidtestnet';
@ -191,7 +207,7 @@ export function getTargetUrl(toNetwork: Network, address: string, env: Env): str
targetUrl += '/address/';
targetUrl += address;
}
if (toNetwork === 'mainnet' || toNetwork === 'testnet' || toNetwork === 'testnet4' || toNetwork === 'signet') {
if (toNetwork === 'mainnet' || toNetwork === 'testnet' || toNetwork === 'testnet4' || toNetwork === 'signet' || toNetwork === 'regtest') {
targetUrl = env.MEMPOOL_WEBSITE_URL;
targetUrl += (toNetwork === 'mainnet' ? '' : `/${toNetwork}`);
targetUrl += '/address/';
@ -231,6 +247,9 @@ export function getRegex(type: RegexType, network?: Network): RegExp {
case `signet`:
leadingZeroes = 5;
break;
case `regtest`:
leadingZeroes = 1; // Regtest has very low difficulty
break;
case `liquid`:
leadingZeroes = 8; // We are not interested in Liquid block hashes
break;
@ -298,6 +317,15 @@ export function getRegex(type: RegexType, network?: Network): RegExp {
regex += `|`; // OR
regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey
break;
case `regtest`:
regex += ADDRESS_CHARS.regtest.base58;
regex += `|`; // OR
regex += ADDRESS_CHARS.regtest.bech32;
regex += `|`; // OR
regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey
regex += `|`; // OR
regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey
break;
case `liquid`:
regex += ADDRESS_CHARS.liquid.base58;
regex += `|`; // OR

View file

@ -121,12 +121,14 @@ $dropdown-link-active-bg: $active-bg;
--testnet: #1d486f;
--signet: #6f1d5d;
--regtest: #7d7d7d;
--liquid: #116761;
--liquidtestnet: #494a4a;
--mainnet-alt: #9339f4;
--testnet-alt: #183550;
--signet-alt: #471850;
--regtest-alt: #4a4a4a;
--liquidtestnet-alt: #272e46;
--taproot: #eba814;

View file

@ -88,12 +88,14 @@ $dropdown-link-active-bg: $active-bg;
--testnet: #1d486f;
--signet: #6f1d5d;
--regtest: #7d7d7d;
--liquid: #116761;
--liquidtestnet: #494a4a;
--mainnet-alt: #9339f4;
--testnet-alt: #183550;
--signet-alt: #471850;
--regtest-alt: #4a4a4a;
--liquidtestnet-alt: #272e46;
--taproot: #eba814;

View file

@ -88,12 +88,14 @@ $dropdown-link-active-bg: $active-bg;
--testnet: #1d486f;
--signet: #6f1d5d;
--regtest: #7d7d7d;
--liquid: #116761;
--liquidtestnet: #494a4a;
--mainnet-alt: #9339f4;
--testnet-alt: #183550;
--signet-alt: #471850;
--regtest-alt: #4a4a4a;
--liquidtestnet-alt: #272e46;
--taproot: #eba814;

View file

@ -88,12 +88,14 @@ $dropdown-link-active-bg: $active-bg;
--testnet: #1d486f;
--signet: #6f1d5d;
--regtest: #7d7d7d;
--liquid: #116761;
--liquidtestnet: #494a4a;
--mainnet-alt: #9339f4;
--testnet-alt: #183550;
--signet-alt: #471850;
--regtest-alt: #4a4a4a;
--liquidtestnet-alt: #272e46;
--taproot: #eba814;

View file

@ -0,0 +1,44 @@
{
"MEMPOOL": {
"OFFICIAL": true,
"NETWORK": "regtest",
"BACKEND": "esplora",
"HTTP_PORT": 8989,
"MINED_BLOCKS_CACHE": 144,
"SPAWN_CLUSTER_PROCS": 0,
"API_URL_PREFIX": "/api/v1/",
"INDEXING_BLOCKS_AMOUNT": -1,
"AUTOMATIC_POOLS_UPDATE": false,
"POOLS_UPDATE_DELAY": 3600,
"AUDIT": true,
"RUST_GBT": true,
"POLL_RATE_MS": 500,
"DISK_CACHE_BLOCK_INTERVAL": 1,
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
"ALLOW_UNREACHABLE": true,
"MAX_TRACKED_ADDRESSES": 10
},
"SYSLOG" : {
"MIN_PRIORITY": "debug"
},
"CORE_RPC": {
"PORT": 18443,
"USERNAME": "__BITCOIN_RPC_USER__",
"PASSWORD": "__BITCOIN_RPC_PASS__"
},
"ESPLORA": {
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-regtest"
},
"DATABASE": {
"ENABLED": true,
"HOST": "127.0.0.1",
"SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_REGTEST_USER__",
"PASSWORD": "__MEMPOOL_REGTEST_PASS__",
"DATABASE": "mempool_regtest"
},
"STATISTICS": {
"ENABLED": true,
"TX_PER_SECOND_SAMPLE_PERIOD": 150
}
}