Merge branch 'master' into mononaut/x-widget

This commit is contained in:
softsimon
2024-05-08 22:56:03 +07:00
committed by GitHub
137 changed files with 2695 additions and 825 deletions

View File

@@ -151,7 +151,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' ) {
} else if( network === 'signet' || network === 'testnet' || network === 'testnet4') {
return ' ' + network.charAt(0).toUpperCase() + network.slice(1);
}
return '';

View File

@@ -4,5 +4,6 @@
<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 === 'testnet4'">t-</ng-template>
<ng-template [ngIf]="network === 'signet'">s-</ng-template>{{ unit }}
</span>

View File

@@ -4,13 +4,19 @@
<div class="row main" [class]="{'services': isServicesPage}">
<div class="col-md-12 branding mt-2">
<div class="main-logo" [class]="{'services': isServicesPage}">
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126"></app-svg-images>
@if (enterpriseInfo?.footer_img) {
<img [src]="enterpriseInfo?.footer_img" alt="enterpriseInfo.title" height="60px" class="mr-3">
} @else {
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126"></app-svg-images>
}
</div>
<p class="explore-tagline-mobile">
<ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
<ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>
</p>
@if (!enterpriseInfo?.footer_img) {
<p class="explore-tagline-mobile">
<ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
<ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>
</p>
}
<div class="site-options language-selector d-flex justify-content-center align-items-center" [class]="{'services': isServicesPage}">
<div class="selector">
<app-language-selector></app-language-selector>
@@ -21,34 +27,41 @@
<div class="selector">
<app-rate-unit-selector></app-rate-unit-selector>
</div>
<div class="selector d-none d-sm-flex">
<app-theme-selector></app-theme-selector>
</div>
@if (!env.customize?.theme) {
<div class="selector d-none d-sm-flex">
<app-theme-selector></app-theme-selector>
</div>
}
<a *ngIf="stateService.isMempoolSpaceBuild" class="btn btn-purple sponsor d-none d-sm-flex justify-content-center" [routerLink]="['/login' | relativeUrl]">
<span *ngIf="loggedIn" i18n="shared.my-account" class="nowrap">My Account</span>
<span *ngIf="!loggedIn" i18n="shared.sign-in" class="nowrap">Sign In</span>
</a>
</div>
<div class="selector d-flex d-sm-none justify-content-center ml-auto mr-auto mt-0">
<app-theme-selector></app-theme-selector>
</div>
<a *ngIf="stateService.isMempoolSpaceBuild" class="btn btn-purple sponsor d-flex d-sm-none justify-content-center ml-auto mr-auto mt-0 mb-2" [routerLink]="['/login' | relativeUrl]">
<span *ngIf="loggedIn" i18n="shared.my-account" class="nowrap">My Account</span>
<span *ngIf="!loggedIn" i18n="shared.sign-in" class="nowrap">Sign In</span>
</a>
<p class="explore-tagline-desktop">
<ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
<ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>
</p>
@if (!env.customize?.theme) {
<div class="selector d-flex d-sm-none justify-content-center ml-auto mr-auto mt-0">
<app-theme-selector></app-theme-selector>
</div>
}
@if (!enterpriseInfo?.footer_img) {
<a *ngIf="stateService.isMempoolSpaceBuild" class="btn btn-purple sponsor d-flex d-sm-none justify-content-center ml-auto mr-auto mt-0 mb-2" [routerLink]="['/login' | relativeUrl]">
<span *ngIf="loggedIn" i18n="shared.my-account" class="nowrap">My Account</span>
<span *ngIf="!loggedIn" i18n="shared.sign-in" class="nowrap">Sign In</span>
</a>
<p class="explore-tagline-desktop">
<ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
<ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>
</p>
}
</div>
</div>
<div class="row col-md-12 link-tree" [class]="{'services': isServicesPage}">
<div class="links">
<p class="category" i18n="footer.explore">Explore</p>
<p><a *ngIf="env.MINING_DASHBOARD" [routerLink]="['/mining' | relativeUrl]" i18n="mining.mining-dashboard">Mining Dashboard</a></p>
<p><a *ngIf="env.LIGHTNING" [routerLink]="['/lightning' | relativeUrl]" i18n="master-page.lightning">Lightning Explorer</a></p>
<p><a *ngIf="env.LIGHTNING && lightningNetworks.includes(stateService.network)" [routerLink]="['/lightning' | relativeUrl]" i18n="master-page.lightning">Lightning Explorer</a></p>
<p><a [routerLink]="['/blocks' | relativeUrl]" i18n="dashboard.recent-blocks">Recent Blocks</a></p>
<p><a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a></p>
<p><a [routerLink]="['/tx/test' | relativeUrl]" i18n="shared.test-transaction|Test Transaction">Test Transaction</a></p>
<p *ngIf="officialMempoolSpace"><a [routerLink]="['/lightning/group/the-mempool-open-source-project' | relativeUrl]" i18n="footer.connect-to-our-nodes">Connect to our Nodes</a></p>
<p><a [routerLink]="['/docs/api' | relativeUrl]" i18n="footer.api-documentation">API Documentation</a></p>
</div>
@@ -61,10 +74,11 @@
<p><a [routerLink]="['/docs/faq' | relativeUrl]" i18n="faq.more-faq">More FAQs &raquo;</a></p>
</div>
<div class="links" *ngIf="officialMempoolSpace || env.TESTNET_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.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.testnet-explorer">Testnet 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> <span class="badge badge-pill badge-warning beta-network" i18n="beta">beta</span></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.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>

View File

@@ -329,3 +329,8 @@ footer .nowrap {
margin-right: 10px;
}
}
.beta-network {
font-size: 8px;
margin-left: 1em;
}

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, Inject, LOCALE_ID, HostListener } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, Inject, LOCALE_ID, HostListener, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Observable, merge, of, Subject, Subscription } from 'rxjs';
import { tap, takeUntil } from 'rxjs/operators';
@@ -8,6 +8,7 @@ import { LanguageService } from '../../../services/language.service';
import { NavigationService } from '../../../services/navigation.service';
import { StorageService } from '../../../services/storage.service';
import { WebsocketService } from '../../../services/websocket.service';
import { EnterpriseService } from '../../../services/enterprise.service';
@Component({
selector: 'app-global-footer',
@@ -15,7 +16,7 @@ import { WebsocketService } from '../../../services/websocket.service';
styleUrls: ['./global-footer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GlobalFooterComponent implements OnInit {
export class GlobalFooterComponent implements OnInit, OnDestroy {
private destroy$: Subject<any> = new Subject<any>();
env: Env;
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
@@ -27,14 +28,19 @@ export class GlobalFooterComponent implements OnInit {
network$: Observable<string>;
networkPaths: { [network: string]: string };
currentNetwork = '';
lightningNetworks = ['', 'mainnet', 'bitcoin', 'testnet', 'signet'];
loggedIn = false;
urlSubscription: Subscription;
isServicesPage = false;
enterpriseInfo: any;
enterpriseInfo$: Subscription;
constructor(
public stateService: StateService,
private languageService: LanguageService,
private navigationService: NavigationService,
private enterpriseService: EnterpriseService,
@Inject(LOCALE_ID) public locale: string,
private storageService: StorageService,
private route: ActivatedRoute,
@@ -53,6 +59,9 @@ export class GlobalFooterComponent implements OnInit {
this.navigationService.subnetPaths.subscribe((paths) => {
this.networkPaths = paths;
});
this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => {
this.enterpriseInfo = info;
});
this.network$ = merge(of(''), this.stateService.networkChanged$).pipe(
tap((network: string) => {
return network;
@@ -72,11 +81,14 @@ export class GlobalFooterComponent implements OnInit {
this.destroy$.next(true);
this.destroy$.complete();
this.urlSubscription.unsubscribe();
if (this.enterpriseInfo$) {
this.enterpriseInfo$.unsubscribe();
}
}
networkLink(network) {
const thisNetwork = network || 'mainnet';
if( network === '' || network === 'mainnet' || network === 'testnet' || network === 'signet' ) {
if( network === '' || network === 'mainnet' || network === 'testnet' || network === 'testnet4' || network === 'signet' ) {
return (this.env.BASE_MODULE === 'mempool' ? '' : this.env.MEMPOOL_WEBSITE_URL + this.urlLanguage) + this.networkPaths[thisNetwork] || '/';
}
if( network === 'liquid' || network === 'liquidtestnet' ) {

View File

@@ -4,5 +4,6 @@
<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 === 'testnet4'">t-</ng-template>
<ng-template [ngIf]="network === 'signet'">s-</ng-template>sats
</span>

View File

@@ -62,6 +62,20 @@ const ADDRESS_CHARS: {
+ `{20,100}`
+ `)`,
},
testnet4: {
base58: `[mn2]` // Starts with a single m, n, or 2 (P2PKH is m or n, 2 is P2SH)
+ BASE58_CHARS
+ `{33,34}`, // m|n is 34 length, 2 is 35 length (We match the first letter separately)
bech32: `(?:`
+ `tb1` // Starts with tb1
+ BECH32_CHARS_LW
+ `{20,100}` // As per bech32, 6 char checksum is minimum
+ `|`
+ `TB1` // All upper case version
+ BECH32_CHARS_UP
+ `{20,100}`
+ `)`,
},
signet: {
base58: `[mn2]`
+ BASE58_CHARS
@@ -128,7 +142,7 @@ const ADDRESS_CHARS: {
type RegexTypeNoAddrNoBlockHash = | `transaction` | `blockheight` | `date` | `timestamp`;
export type RegexType = `address` | `blockhash` | RegexTypeNoAddrNoBlockHash;
export const NETWORKS = [`testnet`, `signet`, `liquid`, `liquidtestnet`, `mainnet`] as const;
export const NETWORKS = [`testnet`, `testnet4`, `signet`, `liquid`, `liquidtestnet`, `mainnet`] as const;
export type Network = typeof NETWORKS[number]; // Turn const array into union type
export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS
@@ -144,6 +158,8 @@ function isNetworkAvailable(network: Network, env: Env): boolean {
switch (network) {
case 'testnet':
return env.TESTNET_ENABLED === true;
case 'testnet4':
return env.TESTNET4_ENABLED === true;
case 'signet':
return env.SIGNET_ENABLED === true;
case 'liquid':
@@ -160,7 +176,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 !== 'signet';
return toNetwork !== 'mainnet' && toNetwork !== 'testnet' && toNetwork !== 'testnet4' && toNetwork !== 'signet';
}
if (fromBaseModule === 'liquid') {
return toNetwork !== 'liquid' && toNetwork !== 'liquidtestnet';
@@ -175,7 +191,7 @@ export function getTargetUrl(toNetwork: Network, address: string, env: Env): str
targetUrl += '/address/';
targetUrl += address;
}
if (toNetwork === 'mainnet' || toNetwork === 'testnet' || toNetwork === 'signet') {
if (toNetwork === 'mainnet' || toNetwork === 'testnet' || toNetwork === 'testnet4' || toNetwork === 'signet') {
targetUrl = env.MEMPOOL_WEBSITE_URL;
targetUrl += (toNetwork === 'mainnet' ? '' : `/${toNetwork}`);
targetUrl += '/address/';
@@ -209,6 +225,9 @@ export function getRegex(type: RegexType, network?: Network): RegExp {
case `testnet`:
leadingZeroes = 8; // Assumes at least 32 bits of difficulty
break;
case `testnet4`:
leadingZeroes = 8; // Assumes at least 32 bits of difficulty
break;
case `signet`:
leadingZeroes = 5;
break;
@@ -261,6 +280,15 @@ export function getRegex(type: RegexType, network?: Network): RegExp {
regex += `|`; // OR
regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey
break;
case `testnet4`:
regex += ADDRESS_CHARS.testnet.base58;
regex += `|`; // OR
regex += ADDRESS_CHARS.testnet.bech32;
regex += `|`; // OR
regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey
regex += `|`; // OR
regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey
break;
case `signet`:
regex += ADDRESS_CHARS.signet.base58;
regex += `|`; // OR

View File

@@ -70,6 +70,7 @@ import { AddressTransactionsWidgetComponent } from '../components/address-transa
import { RbfTimelineComponent } from '../components/rbf-timeline/rbf-timeline.component';
import { RbfTimelineTooltipComponent } from '../components/rbf-timeline/rbf-timeline-tooltip.component';
import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
import { TestTransactionsComponent } from '../components/test-transactions/test-transactions.component';
import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component';
import { AssetGroupComponent } from '../components/assets/asset-group/asset-group.component';
import { AssetCirculationComponent } from '../components/asset-circulation/asset-circulation.component';
@@ -181,6 +182,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
RbfTimelineComponent,
RbfTimelineTooltipComponent,
PushTransactionComponent,
TestTransactionsComponent,
AssetsNavComponent,
AssetsFeaturedComponent,
AssetGroupComponent,
@@ -320,6 +322,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
RbfTimelineComponent,
RbfTimelineTooltipComponent,
PushTransactionComponent,
TestTransactionsComponent,
AssetsNavComponent,
AssetsFeaturedComponent,
AssetGroupComponent,