mirror of
https://github.com/mempool/mempool.git
synced 2024-11-20 02:11:49 +01:00
parent
20c7ee98e7
commit
1feb985bec
@ -63,14 +63,177 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'liquid',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: LatestBlocksComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
component: BlockComponent
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
component: MempoolBlockComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'tv',
|
||||
component: TelevisionComponent,
|
||||
},
|
||||
{
|
||||
path: 'contributors',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'liquid',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: LatestBlocksComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
component: BlockComponent
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
component: MempoolBlockComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'contributors',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'testnet',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: LatestBlocksComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
component: BlockComponent
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
component: MempoolBlockComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'contributors',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'tv',
|
||||
component: TelevisionComponent,
|
||||
},
|
||||
{
|
||||
path: 'liquid-tv',
|
||||
component: TelevisionComponent,
|
||||
},
|
||||
{
|
||||
path: 'testnet-tv',
|
||||
component: TelevisionComponent,
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -48,6 +48,7 @@ import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.
|
||||
import { AssetComponent } from './components/asset/asset.component';
|
||||
import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubkey-type.pipe';
|
||||
import { AssetsComponent } from './assets/assets.component';
|
||||
import { RelativeUrlPipe } from './pipes/relative-url/relative-url.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -86,6 +87,7 @@ import { AssetsComponent } from './assets/assets.component';
|
||||
AssetComponent,
|
||||
ScriptpubkeyTypePipe,
|
||||
AssetsComponent,
|
||||
RelativeUrlPipe,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -18,8 +18,8 @@
|
||||
<td class="td-name">{{ asset.name }}</td>
|
||||
<td>{{ asset.ticker }}</td>
|
||||
<td class="d-none d-md-block"><a *ngIf="asset.entity" target="_blank" href="{{ 'http://' + asset.entity.domain }}">{{ asset.entity.domain }}</a></td>
|
||||
<td><a [routerLink]="['/asset/', asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
|
||||
<td class="d-none d-lg-block"><ng-template [ngIf]="asset.issuance_txin"><a [routerLink]="['/tx/', 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></ng-template></td>
|
||||
<td><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
|
||||
<td class="d-none d-lg-block"><ng-template [ngIf]="asset.issuance_txin"><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></ng-template></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -1,2 +1,2 @@
|
||||
<span *ngIf="multisig" class="badge badge-pill badge-warning">multisig {{ multisigM }} of {{ multisigN }}</span>
|
||||
<span *ngIf="lnChannelClose" class="badge badge-pill badge-warning">Lightning Channel Force Close</span>
|
||||
<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning">Layer2 Peg-out</span>
|
||||
|
@ -16,7 +16,7 @@ export class AddressLabelsComponent implements OnInit {
|
||||
multisigM: number;
|
||||
multisigN: number;
|
||||
|
||||
lnChannelClose = false;
|
||||
secondLayerClose = false;
|
||||
|
||||
constructor() { }
|
||||
|
||||
@ -33,12 +33,16 @@ export class AddressLabelsComponent implements OnInit {
|
||||
if (this.vin.inner_witnessscript_asm.indexOf('OP_CHECKMULTISIG') > -1) {
|
||||
const matches = this.getMatches(this.vin.inner_witnessscript_asm, /OP_PUSHNUM_([0-9])/g, 1);
|
||||
this.multisig = true;
|
||||
this.multisigM = matches[0];
|
||||
this.multisigN = matches[1];
|
||||
this.multisigM = parseInt(matches[0], 10);
|
||||
this.multisigN = parseInt(matches[1], 10);
|
||||
|
||||
if (this.multisigM === 1 && this.multisigN === 1) {
|
||||
this.multisig = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (/OP_IF (.+) OP_ELSE (.+) OP_CSV OP_DROP/.test(this.vin.inner_witnessscript_asm)) {
|
||||
this.lnChannelClose = true;
|
||||
this.secondLayerClose = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="container-xl">
|
||||
<h1 style="float: left;">Address</h1>
|
||||
<a [routerLink]="['/address/', addressString]" style="line-height: 56px; margin-left: 10px;">
|
||||
<a [routerLink]="['/address/' | relativeUrl, addressString]" style="line-height: 56px; margin-left: 10px;">
|
||||
<span class="d-inline d-lg-none">{{ addressString | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
||||
</a>
|
||||
|
@ -9,7 +9,6 @@ import { AudioService } from 'src/app/services/audio.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { of, merge, Subscription } from 'rxjs';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-address',
|
||||
@ -17,7 +16,7 @@ import { environment } from 'src/environments/environment';
|
||||
styleUrls: ['./address.component.scss']
|
||||
})
|
||||
export class AddressComponent implements OnInit, OnDestroy {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
|
||||
address: Address;
|
||||
addressString: string;
|
||||
@ -48,6 +47,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
|
||||
|
||||
this.mainSubscription = this.route.paramMap
|
||||
|
@ -6,6 +6,7 @@
|
||||
Confidential
|
||||
</ng-template>
|
||||
<ng-template #default>
|
||||
{{ satoshis / 100000000 | number : '1.8-8' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC
|
||||
{{ satoshis / 100000000 | number : digitsInfo }} <ng-template [ngIf]="network === 'liquid'">L-BTC</ng-template>
|
||||
<ng-template [ngIf]="network === 'testnet'">tBTC</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-amount',
|
||||
@ -12,9 +11,10 @@ import { environment } from '../../../environments/environment';
|
||||
export class AmountComponent implements OnInit {
|
||||
conversions$: Observable<any>;
|
||||
viewFiat$: Observable<boolean>;
|
||||
network = environment.network;
|
||||
network = '';
|
||||
|
||||
@Input() satoshis: number;
|
||||
@Input() digitsInfo = '1.8-8';
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
@ -23,6 +23,7 @@ export class AmountComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.viewFiat$ = this.stateService.viewFiat$.asObservable();
|
||||
this.conversions$ = this.stateService.conversions$.asObservable();
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,41 +1,18 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
network = environment.network;
|
||||
export class AppComponent {
|
||||
network = '';
|
||||
link: HTMLLinkElement;
|
||||
|
||||
constructor(
|
||||
public router: Router,
|
||||
private websocketService: WebsocketService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.router.events.subscribe((val) => {
|
||||
if (val instanceof NavigationEnd) {
|
||||
if (this.network === 'liquid' || this.network === 'testnet') {
|
||||
this.updateCanonicalUrlElement('https://' + this.network + '.mempool.ninja' + location.pathname);
|
||||
} else {
|
||||
this.updateCanonicalUrlElement('https://mempool.space' + location.pathname);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateCanonicalUrlElement(url) {
|
||||
if (!this.link) {
|
||||
this.link = window.document.createElement('link');
|
||||
this.link.setAttribute('rel', 'canonical');
|
||||
window.document.head.appendChild(this.link);
|
||||
}
|
||||
this.link.setAttribute('href', url);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="container-xl">
|
||||
<h1 style="float: left;">Asset</h1>
|
||||
<a [routerLink]="['/asset/', assetString]" style="line-height: 56px; margin-left: 10px;">
|
||||
<a [routerLink]="['/asset/' | relativeUrl, assetString]" style="line-height: 56px; margin-left: 10px;">
|
||||
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ assetString }}</span>
|
||||
</a>
|
||||
@ -30,7 +30,7 @@
|
||||
</tr>
|
||||
<tr *ngIf="!isNativeAsset">
|
||||
<td>Issuance tx</td>
|
||||
<td><a [routerLink]="['/tx/', 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>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -18,7 +18,7 @@ import { AssetsService } from 'src/app/services/assets.service';
|
||||
styleUrls: ['./asset.component.scss']
|
||||
})
|
||||
export class AssetComponent implements OnInit, OnDestroy {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
nativeAssetId = environment.nativeAssetId;
|
||||
|
||||
asset: Asset;
|
||||
@ -54,6 +54,7 @@ export class AssetComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
this.mainSubscription = this.route.paramMap
|
||||
.pipe(
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="title-block">
|
||||
<h1>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/', blockHash]">{{ blockHeight }}</a></ng-template></h1>
|
||||
<h1>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
|
||||
</div>
|
||||
|
||||
<ng-template [ngIf]="!isLoadingBlock && !error">
|
||||
@ -40,7 +40,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="td-width">Hash</td>
|
||||
<td><a [routerLink]="['/block/', block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
|
||||
<td><a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
|
||||
</tr>
|
||||
<tr *ngIf="block.medianFee !== undefined">
|
||||
<td>Median fee</td>
|
||||
@ -49,12 +49,12 @@
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
<td>Total fees</td>
|
||||
<td><span title="{{ fees | number: '1.2-8' }} BTC">{{ fees | number: '1.2-2' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC</span> (<app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat>)</td>
|
||||
<td><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-8"></app-amount> (<app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reward + fees:</td>
|
||||
<td>
|
||||
<span title="{{ blockSubsidy + fees | number: '1.2-8' }} BTC">{{ blockSubsidy + fees | number: '1.2-2' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC</span> (<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>)
|
||||
<app-amount [satoshis]="(blockSubsidy + fees) * 100000000" digitsInfo="1.2-8"></app-amount> (<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>)
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
@ -6,7 +6,6 @@ import { Block, Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||
import { of } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block',
|
||||
@ -14,7 +13,7 @@ import { environment } from 'src/environments/environment';
|
||||
styleUrls: ['./block.component.scss']
|
||||
})
|
||||
export class BlockComponent implements OnInit, OnDestroy {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
block: Block;
|
||||
blockHeight: number;
|
||||
blockHash: string;
|
||||
@ -92,6 +91,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.stateService.blocks$
|
||||
.subscribe((block) => this.latestBlock = block);
|
||||
|
||||
this.stateService.networkChanged$
|
||||
.subscribe((network) => this.network = network);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -1,9 +1,9 @@
|
||||
<div class="blocks-container" *ngIf="blocks.length">
|
||||
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
|
||||
<div class="text-center bitcoin-block mined-block" [class.blink-bg]="block.height % 210000 === 0" id="bitcoin-block-{{ block.height }}" [ngStyle]="getStyleForBlock(block)">
|
||||
<a [routerLink]="['/block/', block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a>
|
||||
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a>
|
||||
<div class="block-height">
|
||||
<a [routerLink]="['/block/', block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
</div>
|
||||
<div class="block-body">
|
||||
<div class="fees">
|
||||
|
@ -1,11 +1,13 @@
|
||||
<div class="text-center" class="blockchain-wrapper">
|
||||
<div class="position-container">
|
||||
<app-mempool-blocks></app-mempool-blocks>
|
||||
<app-blockchain-blocks></app-blockchain-blocks>
|
||||
|
||||
<div id="divider" *ngIf="!isLoading; else loadingTmpl"></div>
|
||||
<span [hidden]="isLoading">
|
||||
<app-mempool-blocks></app-mempool-blocks>
|
||||
<app-blockchain-blocks></app-blockchain-blocks>
|
||||
<div id="divider"></div>
|
||||
</span>
|
||||
|
||||
<ng-template #loadingTmpl>
|
||||
<ng-template [ngIf]="isLoading">
|
||||
<div class="loading-block">
|
||||
<h3>Waiting for blocks...</h3>
|
||||
<br>
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
@Component({
|
||||
@ -8,9 +6,7 @@ import { StateService } from 'src/app/services/state.service';
|
||||
templateUrl: './blockchain.component.html',
|
||||
styleUrls: ['./blockchain.component.scss']
|
||||
})
|
||||
export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
blocksSubscription: Subscription;
|
||||
|
||||
export class BlockchainComponent implements OnInit {
|
||||
txTrackingLoading = false;
|
||||
txShowTxNotFound = false;
|
||||
isLoading = true;
|
||||
@ -20,14 +16,7 @@ export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.blocksSubscription = this.stateService.blocks$
|
||||
.pipe(
|
||||
take(1)
|
||||
)
|
||||
.subscribe(() => this.isLoading = false);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.blocksSubscription.unsubscribe();
|
||||
this.stateService.blocks$.subscribe(() => this.isLoading = false);
|
||||
this.stateService.networkChanged$.subscribe(() => this.isLoading = true);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
<div class="text-center pb-1" *ngIf="network === 'mempool' && latestBlockHeight && latestBlockHeight < 630000">
|
||||
<div class="text-center pb-1" *ngIf="network === '' && latestBlockHeight && latestBlockHeight < 630000">
|
||||
<h3>Quantitative Hardening in {{ 630000 - latestBlockHeight }} blocks (~<app-timespan [time]="(630000 - latestBlockHeight) * 10 * 60"></app-timespan>)</h3>
|
||||
</div>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
||||
<td><a [routerLink]="['/block', block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
|
||||
<td><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
|
||||
<td class="d-none d-md-block">{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</td>
|
||||
<td class="d-none d-lg-block">{{ block.tx_count | number }}</td>
|
||||
|
@ -4,7 +4,6 @@ import { StateService } from '../../services/state.service';
|
||||
import { Block } from '../../interfaces/electrs.interface';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-latest-blocks',
|
||||
@ -12,7 +11,7 @@ import { environment } from 'src/environments/environment';
|
||||
styleUrls: ['./latest-blocks.component.scss'],
|
||||
})
|
||||
export class LatestBlocksComponent implements OnInit, OnDestroy {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
|
||||
blocks: any[] = [];
|
||||
blockSubscription: Subscription;
|
||||
@ -32,6 +31,7 @@ export class LatestBlocksComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
this.seoService.resetTitle();
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
this.blockSubscription = this.stateService.blocks$
|
||||
.subscribe((block) => {
|
||||
|
@ -5,27 +5,38 @@
|
||||
<div class="badge badge-warning connection-badge" *ngIf="connectionState === 0">Offline</div>
|
||||
<div class="badge badge-warning connection-badge" style="left: 30px;" *ngIf="connectionState === 1">Reconnecting...</div>
|
||||
</a>
|
||||
|
||||
|
||||
<div class="btn-group" style="margin-right: 16px;">
|
||||
<button type="button" (click)="networkDropdownHidden = !networkDropdownHidden" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu" [class.d-block]="!networkDropdownHidden">
|
||||
<a class="dropdown-item" routerLink="/">Mainnet</a>
|
||||
<a class="dropdown-item" routerLink="/liquid">Liquid</a>
|
||||
<a class="dropdown-item" routerLink="/testnet">Testnet</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" (click)="collapse()" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="navbar-collapse collapse" id="navbarCollapse" [ngClass]="{'show': navCollapsed}">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<ul class="navbar-nav mr-auto {{ network }}">
|
||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
|
||||
<a class="nav-link" routerLink="/" (click)="collapse()">Blockchain</a>
|
||||
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()">Blockchain</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/graphs" (click)="collapse()">Graphs</a>
|
||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()">Graphs</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/tv" (click)="collapse()">TV view <img src="./resources/expand.png" width="15"/></a>
|
||||
<a class="nav-link" [routerLink]="[tvViewRoute]" (click)="collapse()">TV view <img src="./resources/expand.png" width="15"/></a>
|
||||
</li>
|
||||
<li *ngIf="network === 'liquid'" class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/assets" (click)="collapse()">Assets</a>
|
||||
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()">Assets</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/contributors" (click)="collapse()">Contributors</a>
|
||||
<a class="nav-link" [routerLink]="['/contributors' | relativeUrl]" (click)="collapse()">Contributors</a>
|
||||
</li>
|
||||
</ul>
|
||||
<app-search-form location="top" (searchTriggered)="collapse()"></app-search-form>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, HostListener } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-master-page',
|
||||
@ -8,20 +7,43 @@ import { environment } from 'src/environments/environment';
|
||||
styleUrls: ['./master-page.component.scss']
|
||||
})
|
||||
export class MasterPageComponent implements OnInit {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
tvViewRoute = '/tv';
|
||||
|
||||
navCollapsed = false;
|
||||
connectionState = 2;
|
||||
|
||||
networkDropdownHidden = true;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
) { }
|
||||
|
||||
@HostListener('document:click', ['$event'])
|
||||
documentClick(event: any): void {
|
||||
if (!event.target.classList.contains('dropdown-toggle')) {
|
||||
this.networkDropdownHidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.stateService.connectionState$
|
||||
.subscribe((state) => {
|
||||
this.connectionState = state;
|
||||
});
|
||||
|
||||
this.stateService.networkChanged$
|
||||
.subscribe((network) => {
|
||||
this.network = network;
|
||||
|
||||
if (network === 'testnet') {
|
||||
this.tvViewRoute = '/testnet-tv';
|
||||
} else if (network === 'liquid') {
|
||||
this.tvViewRoute = '/liquid-tv';
|
||||
} else {
|
||||
this.tvViewRoute = '/tv';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
collapse(): void {
|
||||
|
@ -5,7 +5,6 @@ import { switchMap, map, tap } from 'rxjs/operators';
|
||||
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mempool-block',
|
||||
@ -13,7 +12,7 @@ import { environment } from 'src/environments/environment';
|
||||
styleUrls: ['./mempool-block.component.scss']
|
||||
})
|
||||
export class MempoolBlockComponent implements OnInit, OnDestroy {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
mempoolBlockIndex: number;
|
||||
mempoolBlock$: Observable<MempoolBlock>;
|
||||
|
||||
@ -43,6 +42,9 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
||||
this.stateService.markBlock$.next({ mempoolBlockIndex: this.mempoolBlockIndex });
|
||||
})
|
||||
);
|
||||
|
||||
this.stateService.networkChanged$
|
||||
.subscribe((network) => this.network = network);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="flashing">
|
||||
<div *ngFor="let projectedBlock of mempoolBlocks; let i = index; trackBy: trackByFn">
|
||||
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="getStyleForMempoolBlockAtIndex(i)">
|
||||
<a [routerLink]="['/mempool-block/', i]" class="blockLink"> </a>
|
||||
<a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink"> </a>
|
||||
<div class="block-body" *ngIf="mempoolBlocks?.length">
|
||||
<div class="fees">
|
||||
<span class="yellow-color">~{{ projectedBlock.medianFee | number:'1.0-0' }} sat/vB</span>
|
||||
|
@ -4,7 +4,7 @@ import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mempool-blocks',
|
||||
templateUrl: './mempool-blocks.component.html',
|
||||
@ -14,7 +14,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
mempoolBlocks: MempoolBlock[];
|
||||
mempoolBlocksFull: MempoolBlock[];
|
||||
mempoolBlocksSubscription: Subscription;
|
||||
network = environment.network;
|
||||
network = '';
|
||||
|
||||
blockWidth = 125;
|
||||
blockPadding = 30;
|
||||
@ -54,6 +54,9 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.calculateTransactionPosition();
|
||||
});
|
||||
|
||||
this.stateService.networkChanged$
|
||||
.subscribe((network) => this.network = network);
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
|
@ -2,8 +2,8 @@ import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, O
|
||||
import { formatDate } from '@angular/common';
|
||||
import { VbytesPipe } from 'src/app/pipes/bytes-pipe/vbytes.pipe';
|
||||
import * as Chartist from 'chartist';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mempool-graph',
|
||||
@ -13,16 +13,18 @@ import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
|
||||
export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
@Input() data;
|
||||
|
||||
network = environment.network;
|
||||
network = '';
|
||||
mempoolVsizeFeesOptions: any;
|
||||
mempoolVsizeFeesData: any;
|
||||
|
||||
constructor(
|
||||
private vbytesPipe: VbytesPipe,
|
||||
private stateService: StateService,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
const labelInterpolationFnc = (value: any, index: any) => {
|
||||
return index % 6 === 0 ? formatDate(value, 'HH:mm', this.locale) : null;
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { AssetsService } from 'src/app/services/assets.service';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search-form',
|
||||
@ -11,7 +11,7 @@ import { AssetsService } from 'src/app/services/assets.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SearchFormComponent implements OnInit {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
assets: object;
|
||||
|
||||
searchForm: FormGroup;
|
||||
@ -25,9 +25,12 @@ export class SearchFormComponent implements OnInit {
|
||||
private formBuilder: FormBuilder,
|
||||
private router: Router,
|
||||
private assetsService: AssetsService,
|
||||
private stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
this.searchForm = this.formBuilder.group({
|
||||
searchText: ['', Validators.required],
|
||||
});
|
||||
@ -43,16 +46,16 @@ export class SearchFormComponent implements OnInit {
|
||||
const searchText = this.searchForm.value.searchText.trim();
|
||||
if (searchText) {
|
||||
if (this.regexAddress.test(searchText)) {
|
||||
this.router.navigate(['/address/', searchText]);
|
||||
this.router.navigate([(this.network ? '/' + this.network : '') + '/address/', searchText]);
|
||||
this.searchTriggered.emit();
|
||||
} else if (this.regexBlockhash.test(searchText)) {
|
||||
this.router.navigate(['/block/', searchText]);
|
||||
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', searchText]);
|
||||
this.searchTriggered.emit();
|
||||
} else if (this.regexTransaction.test(searchText)) {
|
||||
if (this.network === 'liquid' && this.assets[searchText]) {
|
||||
this.router.navigate(['/asset/', searchText]);
|
||||
this.router.navigate([(this.network ? '/' + this.network : '') + '/asset/', searchText]);
|
||||
} else {
|
||||
this.router.navigate(['/tx/', searchText]);
|
||||
this.router.navigate([(this.network ? '/' + this.network : '') + '/tx/', searchText]);
|
||||
}
|
||||
this.searchTriggered.emit();
|
||||
} else {
|
||||
|
@ -19,25 +19,25 @@
|
||||
<div class="spinner-border text-light bootstrap-spinner" *ngIf="spinnerLoading"></div>
|
||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'2h'" [routerLink]="['/graphs']" fragment="2h"> 2H (LIVE)
|
||||
<input ngbButton type="radio" [value]="'2h'" [routerLink]="['/graphs' | relativeUrl]" fragment="2h"> 2H (LIVE)
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/graphs']" fragment="24h"> 24H
|
||||
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/graphs' | relativeUrl]" fragment="24h"> 24H
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/graphs']" fragment="1w"> 1W
|
||||
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/graphs' | relativeUrl]" fragment="1w"> 1W
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/graphs']" fragment="1m"> 1M
|
||||
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/graphs' | relativeUrl]" fragment="1m"> 1M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/graphs']" fragment="3m"> 3M
|
||||
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/graphs' | relativeUrl]" fragment="3m"> 3M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/graphs']" fragment="6m"> 6M
|
||||
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/graphs' | relativeUrl]" fragment="6m"> 6M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs']" fragment="1y"> 1Y
|
||||
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -12,7 +12,6 @@ import { ApiService } from '../../services/api.service';
|
||||
import * as Chartist from 'chartist';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-statistics',
|
||||
@ -20,7 +19,7 @@ import { environment } from 'src/environments/environment';
|
||||
styleUrls: ['./statistics.component.scss']
|
||||
})
|
||||
export class StatisticsComponent implements OnInit {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
|
||||
loading = true;
|
||||
spinnerLoading = false;
|
||||
@ -52,6 +51,8 @@ export class StatisticsComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.seoService.setTitle('Graphs');
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
const labelInterpolationFnc = (value: any, index: any) => {
|
||||
const nr = 6;
|
||||
|
||||
|
@ -4,7 +4,6 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-television',
|
||||
@ -13,7 +12,6 @@ import { environment } from 'src/environments/environment';
|
||||
})
|
||||
export class TelevisionComponent implements OnInit {
|
||||
loading = true;
|
||||
network = environment.network;
|
||||
|
||||
mempoolStats: OptimizedMempoolStats[] = [];
|
||||
mempoolVsizeFeesData: any;
|
||||
|
@ -11,7 +11,7 @@
|
||||
</ng-template>
|
||||
|
||||
<div>
|
||||
<a [routerLink]="['/tx/', txId]" style="line-height: 56px;">
|
||||
<a [routerLink]="['/tx/' | relativeUrl, txId]" style="line-height: 56px;">
|
||||
<span class="d-inline d-lg-none">{{ txId | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ txId }}</span>
|
||||
</a>
|
||||
@ -33,7 +33,7 @@
|
||||
<tr>
|
||||
<td class="td-width">Included in block</td>
|
||||
<td>
|
||||
<a [routerLink]="['/block/', tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
|
||||
<a [routerLink]="['/block/' | relativeUrl, tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
|
||||
<i> (<app-time-since [time]="tx.status.block_time" [fastRender]="true"></app-time-since> ago)</i>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -9,7 +9,6 @@ import { WebsocketService } from '../../services/websocket.service';
|
||||
import { AudioService } from 'src/app/services/audio.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transaction',
|
||||
@ -17,7 +16,7 @@ import { environment } from '../../../environments/environment';
|
||||
styleUrls: ['./transaction.component.scss']
|
||||
})
|
||||
export class TransactionComponent implements OnInit, OnDestroy {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
tx: Transaction;
|
||||
txId: string;
|
||||
feeRating: number;
|
||||
@ -44,6 +43,8 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
this.subscription = this.route.paramMap.pipe(
|
||||
switchMap((params: ParamMap) => {
|
||||
this.txId = params.get('id') || '';
|
||||
|
@ -1,6 +1,6 @@
|
||||
<ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn">
|
||||
<div *ngIf="!transactionPage" class="header-bg box" style="padding: 10px; margin-bottom: 10px;">
|
||||
<a [routerLink]="['/tx/', tx.txid]" [state]="{ data: tx }">
|
||||
<a [routerLink]="['/tx/' | relativeUrl, tx.txid]" [state]="{ data: tx }">
|
||||
<span style="float: left;" class="d-block d-md-none">{{ tx.txid | shortenString : 16 }}</span>
|
||||
<span style="float: left;" class="d-none d-md-block">{{ tx.txid }}</span>
|
||||
</a>
|
||||
@ -18,21 +18,28 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let vin of getFilteredTxVin(tx)">
|
||||
<td class="arrow-td">
|
||||
<ng-template [ngIf]="vin.prevout === null" [ngIfElse]="hasPrevout">
|
||||
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
|
||||
<i class="arrow grey"></i>
|
||||
</ng-template>
|
||||
<ng-template #hasPrevout>
|
||||
<a [routerLink]="['/tx/', vin.txid]">
|
||||
<a *ngIf="vin.is_pegin; else defaultPrevout" [routerLink]="['/tx/', vin.txid]">
|
||||
<i class="arrow red"></i>
|
||||
</a>
|
||||
<ng-template #defaultPrevout>
|
||||
<a [routerLink]="['/tx/' | relativeUrl, vin.txid]">
|
||||
<i class="arrow red"></i>
|
||||
</a>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td>
|
||||
<div [ngSwitch]="true">
|
||||
<ng-container *ngSwitchCase="vin.is_coinbase">Coinbase<ng-template [ngIf]="network !== 'liquid'"> (Newly Generated Coins)</ng-template></ng-container>
|
||||
<ng-container *ngSwitchCase="vin.is_pegin">PEG IN</ng-container>
|
||||
<ng-container *ngSwitchCase="vin.is_pegin">
|
||||
Peg-in
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<a [routerLink]="['/address/', vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
|
||||
<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 : 42 }}</span>
|
||||
</a>
|
||||
@ -60,13 +67,16 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let vout of getFilteredTxVout(tx); let vindex = index;">
|
||||
<td>
|
||||
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/', vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
||||
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
||||
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
|
||||
<span class="d-none d-lg-block">{{ vout.scriptpubkey_address | shortenString : 42 }}</span>
|
||||
</a>
|
||||
<ng-template #scriptpubkey_type>
|
||||
<ng-template [ngIf]="vout.pegout" [ngIfElse]="defaultscriptpubkey_type">
|
||||
PEG OUT
|
||||
Peg-out to <a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
|
||||
<span class="d-block d-lg-none">{{ vout.pegout.scriptpubkey_address | shortenString : 16 }}</span>
|
||||
<span class="d-none d-lg-block">{{ vout.pegout.scriptpubkey_address | shortenString : 42 }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
<ng-template #defaultscriptpubkey_type>
|
||||
{{ vout.scriptpubkey_type | scriptpubkeyType }}
|
||||
@ -80,7 +90,7 @@
|
||||
<br>
|
||||
{{ assetsMinimal[vout.asset][0] }}
|
||||
<br>
|
||||
<a [routerLink]="['/asset/', vout.asset]">{{ vout.asset | shortenString : 13 }}</a>
|
||||
<a [routerLink]="['/asset/' | relativeUrl, vout.asset]">{{ vout.asset | shortenString : 13 }}</a>
|
||||
<br><br>
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -93,7 +103,7 @@
|
||||
<ng-template #outspend>
|
||||
<i *ngIf="!outspends[i][vindex] || !outspends[i][vindex].spent; else spent" class="arrow green"></i>
|
||||
<ng-template #spent>
|
||||
<a [routerLink]="['/tx/', outspends[i][vindex].txid]"><i class="arrow red"></i></a>
|
||||
<a [routerLink]="['/tx/' | relativeUrl, outspends[i][vindex].txid]"><i class="arrow red"></i></a>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</td>
|
||||
|
@ -13,7 +13,7 @@ import { AssetsService } from 'src/app/services/assets.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
nativeAssetId = environment.nativeAssetId;
|
||||
|
||||
@Input() transactions: Transaction[];
|
||||
@ -35,6 +35,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
|
||||
ngOnInit() {
|
||||
this.latestBlock$ = this.stateService.blocks$;
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
|
||||
if (this.network === 'liquid') {
|
||||
this.assetsService.getAssetsMinimalJson$.subscribe((assets) => {
|
||||
this.assetsMinimal = assets;
|
||||
|
@ -68,7 +68,7 @@ interface Pegout {
|
||||
genesis_hash: string;
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_addres: string;
|
||||
scriptpubkey_address: string;
|
||||
}
|
||||
|
||||
export interface Status {
|
||||
|
@ -0,0 +1,8 @@
|
||||
import { RelativeUrlPipe } from './relative-url.pipe';
|
||||
|
||||
describe('RelativeUrlPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new RelativeUrlPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
17
frontend/src/app/pipes/relative-url/relative-url.pipe.ts
Normal file
17
frontend/src/app/pipes/relative-url/relative-url.pipe.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
@Pipe({
|
||||
name: 'relativeUrl'
|
||||
})
|
||||
export class RelativeUrlPipe implements PipeTransform {
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
) { }
|
||||
|
||||
transform(value: string): string {
|
||||
return (this.stateService.network ? '/' + this.stateService.network : '') + value;
|
||||
}
|
||||
|
||||
}
|
@ -2,43 +2,52 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
|
||||
const API_BASE_URL = '/api/v1';
|
||||
const API_BASE_URL = '/api{network}/v1';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
apiBaseUrl: string;
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
) { }
|
||||
private stateService: StateService,
|
||||
) {
|
||||
this.apiBaseUrl = API_BASE_URL.replace('{network}', '');
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
this.apiBaseUrl = API_BASE_URL.replace('{network}', network ? '/' + network : '');
|
||||
});
|
||||
}
|
||||
|
||||
list2HStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/2h');
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/2h');
|
||||
}
|
||||
|
||||
list24HStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/24h');
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/24h');
|
||||
}
|
||||
|
||||
list1WStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/1w');
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/1w');
|
||||
}
|
||||
|
||||
list1MStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/1m');
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/1m');
|
||||
}
|
||||
|
||||
list3MStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/3m');
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/3m');
|
||||
}
|
||||
|
||||
list6MStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/6m');
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/6m');
|
||||
}
|
||||
|
||||
list1YStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(API_BASE_URL + '/statistics/1y');
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + '/statistics/1y');
|
||||
}
|
||||
|
||||
getTransactionTimes$(txIds: string[]): Observable<number[]> {
|
||||
@ -46,6 +55,6 @@ export class ApiService {
|
||||
txIds.forEach((txId: string) => {
|
||||
params = params.append('txId[]', txId);
|
||||
});
|
||||
return this.httpClient.get<number[]>(API_BASE_URL + '/transaction-times', { params });
|
||||
return this.httpClient.get<number[]>(this.apiBaseUrl + '/transaction-times', { params });
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { Observable } from 'rxjs';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AssetsService {
|
||||
network = environment.network;
|
||||
|
||||
getAssetsJson$: Observable<any>;
|
||||
getAssetsMinimalJson$: Observable<any>;
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Block, Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
|
||||
import { StateService } from './state.service';
|
||||
|
||||
const API_BASE_URL = document.location.protocol + '//' + document.location.hostname + ':' + document.location.port + '/electrs';
|
||||
|
||||
@ -9,61 +10,68 @@ const API_BASE_URL = document.location.protocol + '//' + document.location.hostn
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ElectrsApiService {
|
||||
apiBaseUrl: string;
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private stateService: StateService,
|
||||
) {
|
||||
this.apiBaseUrl = API_BASE_URL;
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
this.apiBaseUrl = API_BASE_URL + '/' + network;
|
||||
});
|
||||
}
|
||||
|
||||
getBlock$(hash: string): Observable<Block> {
|
||||
return this.httpClient.get<Block>(API_BASE_URL + '/block/' + hash);
|
||||
return this.httpClient.get<Block>(this.apiBaseUrl + '/block/' + hash);
|
||||
}
|
||||
|
||||
listBlocks$(height?: number): Observable<Block[]> {
|
||||
return this.httpClient.get<Block[]>(API_BASE_URL + '/blocks/' + (height || ''));
|
||||
return this.httpClient.get<Block[]>(this.apiBaseUrl + '/blocks/' + (height || ''));
|
||||
}
|
||||
|
||||
getTransaction$(txId: string): Observable<Transaction> {
|
||||
return this.httpClient.get<Transaction>(API_BASE_URL + '/tx/' + txId);
|
||||
return this.httpClient.get<Transaction>(this.apiBaseUrl + '/tx/' + txId);
|
||||
}
|
||||
|
||||
getRecentTransaction$(): Observable<Recent[]> {
|
||||
return this.httpClient.get<Recent[]>(API_BASE_URL + '/mempool/recent');
|
||||
return this.httpClient.get<Recent[]>(this.apiBaseUrl + '/mempool/recent');
|
||||
}
|
||||
|
||||
getOutspend$(hash: string, vout: number): Observable<Outspend> {
|
||||
return this.httpClient.get<Outspend>(API_BASE_URL + '/tx/' + hash + '/outspend/' + vout);
|
||||
return this.httpClient.get<Outspend>(this.apiBaseUrl + '/tx/' + hash + '/outspend/' + vout);
|
||||
}
|
||||
|
||||
getOutspends$(hash: string): Observable<Outspend[]> {
|
||||
return this.httpClient.get<Outspend[]>(API_BASE_URL + '/tx/' + hash + '/outspends');
|
||||
return this.httpClient.get<Outspend[]>(this.apiBaseUrl + '/tx/' + hash + '/outspends');
|
||||
}
|
||||
|
||||
getBlockTransactions$(hash: string, index: number = 0): Observable<Transaction[]> {
|
||||
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/block/' + hash + '/txs/' + index);
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/block/' + hash + '/txs/' + index);
|
||||
}
|
||||
|
||||
getAddress$(address: string): Observable<Address> {
|
||||
return this.httpClient.get<Address>(API_BASE_URL + '/address/' + address);
|
||||
return this.httpClient.get<Address>(this.apiBaseUrl + '/address/' + address);
|
||||
}
|
||||
|
||||
getAddressTransactions$(address: string): Observable<Transaction[]> {
|
||||
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/address/' + address + '/txs');
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/address/' + address + '/txs');
|
||||
}
|
||||
|
||||
getAddressTransactionsFromHash$(address: string, txid: string): Observable<Transaction[]> {
|
||||
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/address/' + address + '/txs/chain/' + txid);
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/address/' + address + '/txs/chain/' + txid);
|
||||
}
|
||||
|
||||
getAsset$(assetId: string): Observable<Asset> {
|
||||
return this.httpClient.get<Asset>(API_BASE_URL + '/asset/' + assetId);
|
||||
return this.httpClient.get<Asset>(this.apiBaseUrl + '/asset/' + assetId);
|
||||
}
|
||||
|
||||
getAssetTransactions$(assetId: string): Observable<Transaction[]> {
|
||||
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/asset/' + assetId + '/txs');
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/asset/' + assetId + '/txs');
|
||||
}
|
||||
|
||||
getAssetTransactionsFromHash$(assetId: string, txid: string): Observable<Transaction[]> {
|
||||
return this.httpClient.get<Transaction[]>(API_BASE_URL + '/asset/' + assetId + '/txs/chain/' + txid);
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + '/asset/' + assetId + '/txs/chain/' + txid);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { StateService } from './state.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SeoService {
|
||||
network = environment.network;
|
||||
network = '';
|
||||
defaultTitle = 'mempool - Bitcoin Explorer';
|
||||
|
||||
constructor(
|
||||
private titleService: Title,
|
||||
) { }
|
||||
private stateService: StateService,
|
||||
) {
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
}
|
||||
|
||||
setTitle(newTitle: string, prependNetwork = false) {
|
||||
let networkName = '';
|
||||
|
@ -3,6 +3,7 @@ import { ReplaySubject, BehaviorSubject, Subject } from 'rxjs';
|
||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
||||
import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface';
|
||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
|
||||
interface MarkBlockState {
|
||||
blockHeight?: number;
|
||||
@ -14,10 +15,13 @@ interface MarkBlockState {
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StateService {
|
||||
network = '';
|
||||
latestBlockHeight = 0;
|
||||
|
||||
networkChanged$ = new ReplaySubject<string>(1);
|
||||
blocks$ = new ReplaySubject<Block>(8);
|
||||
conversions$ = new ReplaySubject<any>(1);
|
||||
mempoolStats$ = new ReplaySubject<MemPoolState>();
|
||||
mempoolStats$ = new ReplaySubject<MemPoolState>(1);
|
||||
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
||||
txConfirmed$ = new Subject<Block>();
|
||||
mempoolTransactions$ = new Subject<Transaction>();
|
||||
@ -30,4 +34,34 @@ export class StateService {
|
||||
connectionState$ = new BehaviorSubject<0 | 1 | 2>(2);
|
||||
|
||||
markBlock$ = new Subject<MarkBlockState>();
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
) {
|
||||
this.router.events.subscribe((event) => {
|
||||
if (event instanceof NavigationStart) {
|
||||
switch (event.url.split('/')[1]) {
|
||||
case 'liquid':
|
||||
case 'liquid-tv':
|
||||
if (this.network !== 'liquid') {
|
||||
this.network = 'liquid';
|
||||
this.networkChanged$.next('liquid');
|
||||
}
|
||||
return;
|
||||
case 'testnet':
|
||||
case 'testnet-tv':
|
||||
if (this.network !== 'testnet') {
|
||||
this.network = 'testnet';
|
||||
this.networkChanged$.next('testnet');
|
||||
}
|
||||
return;
|
||||
default:
|
||||
if (this.network !== '') {
|
||||
this.network = '';
|
||||
this.networkChanged$.next('');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ const EXPECT_PING_RESPONSE_AFTER_MS = 1000;
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebsocketService {
|
||||
private websocketSubject: WebSocketSubject<WebsocketResponse> = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL);
|
||||
private websocketSubject: WebSocketSubject<WebsocketResponse>;
|
||||
private goneOffline = false;
|
||||
private lastWant: string[] | null = null;
|
||||
private isTrackingTx = false;
|
||||
@ -28,7 +28,22 @@ export class WebsocketService {
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
) {
|
||||
this.websocketSubject = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL + '/' + this.stateService.network);
|
||||
this.startSubscription();
|
||||
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
clearTimeout(this.onlineCheckTimeout);
|
||||
clearTimeout(this.onlineCheckTimeoutTwo);
|
||||
|
||||
this.stateService.latestBlockHeight = 0;
|
||||
|
||||
this.websocketSubject.complete();
|
||||
this.subscription.unsubscribe();
|
||||
this.websocketSubject = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL + '/' + network);
|
||||
|
||||
this.startSubscription();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
startSubscription(retrying = false) {
|
||||
|
@ -1,9 +1,4 @@
|
||||
const full = window.location.host;
|
||||
const parts = full.split('.');
|
||||
const sub = parts[0];
|
||||
|
||||
export const environment = {
|
||||
production: true,
|
||||
network: sub,
|
||||
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
|
||||
};
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
network: 'mainnet',
|
||||
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
|
||||
};
|
||||
|
||||
|
@ -56,6 +56,14 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-nav.liquid > .active {
|
||||
background-color: #116761 !important;
|
||||
}
|
||||
|
||||
.navbar-nav.testnet > .active {
|
||||
background-color: #1d486f !important;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
color: #495057;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user