-
Mainnet
+
Mainnet
Signet
Testnet3
Testnet4 beta
diff --git a/frontend/src/app/components/master-page/master-page.component.ts b/frontend/src/app/components/master-page/master-page.component.ts
index f3472f204..e351e9196 100644
--- a/frontend/src/app/components/master-page/master-page.component.ts
+++ b/frontend/src/app/components/master-page/master-page.component.ts
@@ -31,6 +31,7 @@ export class MasterPageComponent implements OnInit, OnDestroy {
user: any = undefined;
servicesEnabled = false;
menuOpen = false;
+ isDropdownVisible: boolean;
enterpriseInfo: any;
enterpriseInfo$: Subscription;
@@ -74,19 +75,27 @@ export class MasterPageComponent implements OnInit, OnDestroy {
const isServicesPage = this.router.url.includes('/services/');
this.menuOpen = isServicesPage && !this.isSmallScreen();
+ this.setDropdownVisibility();
}
- ngOnDestroy() {
- if (this.enterpriseInfo$) {
- this.enterpriseInfo$.unsubscribe();
- }
+ setDropdownVisibility(): void {
+ const networks = [
+ this.env.TESTNET_ENABLED,
+ this.env.TESTNET4_ENABLED,
+ this.env.SIGNET_ENABLED,
+ this.env.LIQUID_ENABLED,
+ this.env.LIQUID_TESTNET_ENABLED,
+ this.env.MAINNET_ENABLED,
+ ];
+ const enabledNetworksCount = networks.filter((networkEnabled) => networkEnabled).length;
+ this.isDropdownVisible = enabledNetworksCount > 1;
}
collapse(): void {
this.navCollapsed = !this.navCollapsed;
}
- isSmallScreen() {
+ isSmallScreen(): boolean {
return window.innerWidth <= 767.98;
}
@@ -117,4 +126,11 @@ export class MasterPageComponent implements OnInit, OnDestroy {
menuToggled(isOpen: boolean): void {
this.menuOpen = isOpen;
}
+
+ ngOnDestroy(): void {
+ if (this.enterpriseInfo$) {
+ this.enterpriseInfo$.unsubscribe();
+ }
+ }
+
}
diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts
index 736c12c13..74f048d68 100644
--- a/frontend/src/app/lightning/lightning-api.service.ts
+++ b/frontend/src/app/lightning/lightning-api.service.ts
@@ -23,7 +23,7 @@ export class LightningApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
- this.apiBasePath = network ? '/' + network : '';
+ this.apiBasePath = network && network !== this.stateService.env.ROOT_NETWORK ? '/' + network : '';
});
}
diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts
index e9cb7e2e8..6b0d60ccf 100644
--- a/frontend/src/app/services/api.service.ts
+++ b/frontend/src/app/services/api.service.ts
@@ -29,7 +29,7 @@ export class ApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
- this.apiBasePath = network ? '/' + network : '';
+ this.apiBasePath = network && network !== this.stateService.env.ROOT_NETWORK ? '/' + network : '';
});
}
diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts
index 57dcbb762..7faaea87c 100644
--- a/frontend/src/app/services/electrs-api.service.ts
+++ b/frontend/src/app/services/electrs-api.service.ts
@@ -25,7 +25,7 @@ export class ElectrsApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
- this.apiBasePath = network ? '/' + network : '';
+ this.apiBasePath = network && network !== this.stateService.env.ROOT_NETWORK ? '/' + network : '';
});
}
diff --git a/frontend/src/app/services/navigation.service.ts b/frontend/src/app/services/navigation.service.ts
index 1a22c1371..aed114c75 100644
--- a/frontend/src/app/services/navigation.service.ts
+++ b/frontend/src/app/services/navigation.service.ts
@@ -1,32 +1,31 @@
import { Injectable } from '@angular/core';
-import { Router, ActivatedRoute, NavigationEnd, ActivatedRouteSnapshot } from '@angular/router';
+import { Router, NavigationEnd, ActivatedRouteSnapshot } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { StateService } from './state.service';
-const networkModules = {
- bitcoin: {
- subnets: [
- { name: 'mainnet', path: '' },
- { name: 'testnet', path: '/testnet' },
- { name: 'testnet4', path: '/testnet4' },
- { name: 'signet', path: '/signet' },
- ],
- },
- liquid: {
- subnets: [
- { name: 'liquid', path: '' },
- { name: 'liquidtestnet', path: '/testnet' },
- ],
- }
-};
-const networks = Object.keys(networkModules);
-
@Injectable({
providedIn: 'root'
})
export class NavigationService {
subnetPaths = new BehaviorSubject
>({});
+ networkModules = {
+ bitcoin: {
+ subnets: [
+ { name: 'mainnet', path: '' },
+ { 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' },
+ ],
+ },
+ liquid: {
+ subnets: [
+ { name: 'liquid', path: '' },
+ { name: 'liquidtestnet', path: '/testnet' },
+ ],
+ }
+ };
+ networks = Object.keys(this.networkModules);
constructor(
private stateService: StateService,
@@ -46,11 +45,11 @@ export class NavigationService {
const networkPaths = {};
let route = root;
// traverse the router state tree until all network paths are set, or we reach the end of the tree
- while (!networks.reduce((acc, network) => acc && !!networkPaths[network], true) && route) {
+ while (!this.networks.reduce((acc, network) => acc && !!networkPaths[network], true) && route) {
// 'networkSpecific' paths may correspond to valid routes on other networks, but aren't directly compatible
// (e.g. we shouldn't link a mainnet transaction page to the same txid on testnet or liquid)
if (route.data?.networkSpecific) {
- networks.forEach(network => {
+ this.networks.forEach(network => {
if (networkPaths[network] == null) {
networkPaths[network] = path;
}
@@ -59,7 +58,7 @@ export class NavigationService {
// null or empty networks list is shorthand for "compatible with every network"
if (route.data?.networks?.length) {
// if the list is non-empty, only those networks are compatible
- networks.forEach(network => {
+ this.networks.forEach(network => {
if (!route.data.networks.includes(network)) {
if (networkPaths[network] == null) {
networkPaths[network] = path;
@@ -76,7 +75,7 @@ export class NavigationService {
}
const subnetPaths = {};
- Object.entries(networkModules).forEach(([key, network]) => {
+ Object.entries(this.networkModules).forEach(([key, network]) => {
network.subnets.forEach(subnet => {
subnetPaths[subnet.name] = subnet.path + (networkPaths[key] != null ? networkPaths[key] : path);
});
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index 8c90921ca..0d502747c 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -43,6 +43,7 @@ export interface Customization {
}
export interface Env {
+ MAINNET_ENABLED: boolean;
TESTNET_ENABLED: boolean;
TESTNET4_ENABLED: boolean;
SIGNET_ENABLED: boolean;
@@ -52,6 +53,7 @@ export interface Env {
KEEP_BLOCKS_AMOUNT: number;
OFFICIAL_MEMPOOL_SPACE: boolean;
BASE_MODULE: string;
+ ROOT_NETWORK: string;
NGINX_PROTOCOL?: string;
NGINX_HOSTNAME?: string;
NGINX_PORT?: string;
@@ -77,12 +79,14 @@ export interface Env {
}
const defaultEnv: Env = {
+ 'MAINNET_ENABLED': true,
'TESTNET_ENABLED': false,
'TESTNET4_ENABLED': false,
'SIGNET_ENABLED': false,
'LIQUID_ENABLED': false,
'LIQUID_TESTNET_ENABLED': false,
'BASE_MODULE': 'mempool',
+ 'ROOT_NETWORK': '',
'ITEMS_PER_PAGE': 10,
'KEEP_BLOCKS_AMOUNT': 8,
'OFFICIAL_MEMPOOL_SPACE': false,
@@ -325,7 +329,12 @@ 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
- const networkMatches = 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)($|\/)/);
+
+ if (!networkMatches && this.env.ROOT_NETWORK) {
+ networkMatches = { 1: this.env.ROOT_NETWORK };
+ }
+
switch (networkMatches && networkMatches[1]) {
case 'signet':
if (this.network !== 'signet') {
diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts
index fbadf0de3..22aef76e7 100644
--- a/frontend/src/app/services/websocket.service.ts
+++ b/frontend/src/app/services/websocket.service.ts
@@ -55,7 +55,7 @@ export class WebsocketService {
.pipe(take(1))
.subscribe((response) => this.handleResponse(response));
} else {
- this.network = this.stateService.network;
+ this.network = this.stateService.network === this.stateService.env.ROOT_NETWORK ? '' : this.stateService.network;
this.websocketSubject = webSocket(this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : ''));
const { response: theInitData } = this.transferState.get(initData, null) || {};
@@ -75,7 +75,7 @@ export class WebsocketService {
if (network === this.network) {
return;
}
- this.network = network;
+ this.network = network === this.stateService.env.ROOT_NETWORK ? '' : network;
clearTimeout(this.onlineCheckTimeout);
clearTimeout(this.onlineCheckTimeoutTwo);
diff --git a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts
index 4211765df..8eab3eb0b 100644
--- a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts
+++ b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts
@@ -12,7 +12,9 @@ export class RelativeUrlPipe implements PipeTransform {
transform(value: string, swapNetwork?: string): string {
let network = swapNetwork || this.stateService.network;
- if (network === 'mainnet') network = '';
+ if (network === 'mainnet' || network === this.stateService.env.ROOT_NETWORK) {
+ network = '';
+ }
if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') {
network = 'testnet';
} else if (this.stateService.env.BASE_MODULE !== 'mempool') {