Transition new blocks from the mempool onto the blockchain.
Chime on new blocks. fixes #47 fixes #84
This commit is contained in:
parent
ea708de9fb
commit
36e46249b5
@ -1,6 +1,6 @@
|
|||||||
const config = require('../../mempool-config.json');
|
const config = require('../../mempool-config.json');
|
||||||
import bitcoinApi from './bitcoin/electrs-api';
|
import bitcoinApi from './bitcoin/electrs-api';
|
||||||
import { MempoolInfo, TransactionExtended, Transaction } from '../interfaces';
|
import { MempoolInfo, TransactionExtended, Transaction, VbytesPerSecond } from '../interfaces';
|
||||||
|
|
||||||
class Mempool {
|
class Mempool {
|
||||||
private inSync: boolean = false;
|
private inSync: boolean = false;
|
||||||
@ -12,7 +12,7 @@ class Mempool {
|
|||||||
private txPerSecondArray: number[] = [];
|
private txPerSecondArray: number[] = [];
|
||||||
private txPerSecond: number = 0;
|
private txPerSecond: number = 0;
|
||||||
|
|
||||||
private vBytesPerSecondArray: any[] = [];
|
private vBytesPerSecondArray: VbytesPerSecond[] = [];
|
||||||
private vBytesPerSecond: number = 0;
|
private vBytesPerSecond: number = 0;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -222,3 +222,8 @@ export interface WebsocketResponse {
|
|||||||
'track-address': string;
|
'track-address': string;
|
||||||
'watch-mempool': boolean;
|
'watch-mempool': boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VbytesPerSecond {
|
||||||
|
unixTime: number;
|
||||||
|
vSize: number;
|
||||||
|
}
|
||||||
|
@ -35,3 +35,4 @@ export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 7
|
|||||||
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
|
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
|
||||||
|
|
||||||
export const ELCTRS_ITEMS_PER_PAGE = 25;
|
export const ELCTRS_ITEMS_PER_PAGE = 25;
|
||||||
|
export const KEEP_BLOCKS_AMOUNT = 8;
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
<span *ngIf="multisig" class="badge badge-pill badge-warning">multisig {{ multisigM }} of {{ multisigN }}</span>
|
<span *ngIf="multisig" class="badge badge-pill badge-warning">multisig {{ multisigM }} of {{ multisigN }}</span>
|
||||||
<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning">Layer2 Peg-out</span>
|
<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning">Layer{{ network === 'liquid' ? '3' : '2' }} Peg-out</span>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||||
import { Vin, Vout } from '../../interfaces/electrs.interface';
|
import { Vin, Vout } from '../../interfaces/electrs.interface';
|
||||||
|
import { StateService } from 'src/app/services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-address-labels',
|
selector: 'app-address-labels',
|
||||||
@ -8,6 +9,7 @@ import { Vin, Vout } from '../../interfaces/electrs.interface';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AddressLabelsComponent implements OnInit {
|
export class AddressLabelsComponent implements OnInit {
|
||||||
|
network = '';
|
||||||
|
|
||||||
@Input() vin: Vin;
|
@Input() vin: Vin;
|
||||||
@Input() vout: Vout;
|
@Input() vout: Vout;
|
||||||
@ -18,7 +20,11 @@ export class AddressLabelsComponent implements OnInit {
|
|||||||
|
|
||||||
secondLayerClose = false;
|
secondLayerClose = false;
|
||||||
|
|
||||||
constructor() { }
|
constructor(
|
||||||
|
stateService: StateService,
|
||||||
|
) {
|
||||||
|
this.network = stateService.network;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.vin) {
|
if (this.vin) {
|
||||||
|
@ -120,7 +120,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.stateService.blocks$
|
this.stateService.blocks$
|
||||||
.subscribe((block) => this.latestBlock = block);
|
.subscribe(([block]) => this.latestBlock = block);
|
||||||
|
|
||||||
this.stateService.networkChanged$
|
this.stateService.networkChanged$
|
||||||
.subscribe((network) => this.network = network);
|
.subscribe((network) => this.network = network);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="blocks-container" *ngIf="blocks.length">
|
<div class="blocks-container" *ngIf="blocks.length">
|
||||||
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
|
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
|
||||||
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="getStyleForBlock(block)">
|
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
|
||||||
<a [routerLink]="['/block/' | relativeUrl, 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">
|
<div class="block-height">
|
||||||
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
.mined-block {
|
.mined-block {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
transition: 1s;
|
transition: 2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-size {
|
.block-size {
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { Block } from 'src/app/interfaces/electrs.interface';
|
import { Block } from 'src/app/interfaces/electrs.interface';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { AudioService } from 'src/app/services/audio.service';
|
||||||
|
import { KEEP_BLOCKS_AMOUNT } from 'src/app/app.constants';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-blockchain-blocks',
|
selector: 'app-blockchain-blocks',
|
||||||
@ -14,6 +16,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
blocks: Block[] = [];
|
blocks: Block[] = [];
|
||||||
markHeight: number;
|
markHeight: number;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
|
blockStyles = [];
|
||||||
interval: any;
|
interval: any;
|
||||||
|
|
||||||
arrowVisible = false;
|
arrowVisible = false;
|
||||||
@ -30,20 +33,40 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private audioService: AudioService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
this.blocksSubscription = this.stateService.blocks$
|
this.blocksSubscription = this.stateService.blocks$
|
||||||
.subscribe((block) => {
|
.subscribe(([block, txConfirmed]) => {
|
||||||
|
const currentBlocksAmount = this.blocks.length;
|
||||||
if (this.blocks.some((b) => b.height === block.height)) {
|
if (this.blocks.some((b) => b.height === block.height)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.blocks.unshift(block);
|
this.blocks.unshift(block);
|
||||||
this.blocks = this.blocks.slice(0, 8);
|
this.blocks = this.blocks.slice(0, 8);
|
||||||
|
|
||||||
this.moveArrowToPosition(true);
|
if (currentBlocksAmount === KEEP_BLOCKS_AMOUNT) {
|
||||||
|
setTimeout(() => this.audioService.playSound('bright-harmony'));
|
||||||
|
block.stage = block.matchRate >= 80 ? 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txConfirmed) {
|
||||||
|
this.markHeight = block.height;
|
||||||
|
this.moveArrowToPosition(true, true);
|
||||||
|
} else {
|
||||||
|
this.moveArrowToPosition(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.blockStyles = [];
|
||||||
|
this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b)));
|
||||||
|
setTimeout(() => {
|
||||||
|
this.blockStyles = [];
|
||||||
|
this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b)));
|
||||||
|
}, 50);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.stateService.markBlock$
|
this.stateService.markBlock$
|
||||||
@ -83,20 +106,28 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveArrowToPosition(animate: boolean) {
|
moveArrowToPosition(animate: boolean, newBlockFromLeft = false) {
|
||||||
if (!this.markHeight) {
|
if (!this.markHeight) {
|
||||||
this.arrowVisible = false;
|
this.arrowVisible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
|
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
|
||||||
if (blockindex !== -1) {
|
if (blockindex > -1) {
|
||||||
if (!animate) {
|
if (!animate) {
|
||||||
this.transition = 'inherit';
|
this.transition = 'inherit';
|
||||||
}
|
}
|
||||||
this.arrowVisible = true;
|
this.arrowVisible = true;
|
||||||
this.arrowLeftPx = blockindex * 155 + 30;
|
if (newBlockFromLeft) {
|
||||||
if (!animate) {
|
this.arrowLeftPx = blockindex * 155 + 30 - 205;
|
||||||
setTimeout(() => this.transition = '1s');
|
setTimeout(() => {
|
||||||
|
this.transition = '2s';
|
||||||
|
this.arrowLeftPx = blockindex * 155 + 30;
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
this.arrowLeftPx = blockindex * 155 + 30;
|
||||||
|
if (!animate) {
|
||||||
|
setTimeout(() => this.transition = '2s');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,8 +138,15 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
getStyleForBlock(block: Block) {
|
getStyleForBlock(block: Block) {
|
||||||
const greenBackgroundHeight = 100 - (block.weight / 4000000) * 100;
|
const greenBackgroundHeight = 100 - (block.weight / 4000000) * 100;
|
||||||
|
let addLeft = 0;
|
||||||
|
|
||||||
|
if (block.stage === 1) {
|
||||||
|
block.stage = 2;
|
||||||
|
addLeft = -205;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
left: 155 * this.blocks.indexOf(block) + 'px',
|
left: addLeft + 155 * this.blocks.indexOf(block) + 'px',
|
||||||
background: `repeating-linear-gradient(
|
background: `repeating-linear-gradient(
|
||||||
#2d3348,
|
#2d3348,
|
||||||
#2d3348 ${greenBackgroundHeight}%,
|
#2d3348 ${greenBackgroundHeight}%,
|
||||||
|
@ -34,7 +34,7 @@ export class LatestBlocksComponent implements OnInit, OnDestroy {
|
|||||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
this.blockSubscription = this.stateService.blocks$
|
this.blockSubscription = this.stateService.blocks$
|
||||||
.subscribe((block) => {
|
.subscribe(([block]) => {
|
||||||
if (block === null || !this.blocks.length) {
|
if (block === null || !this.blocks.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { switchMap, map, tap } from 'rxjs/operators';
|
import { switchMap, map, tap, filter } from 'rxjs/operators';
|
||||||
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
|
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
@ -29,6 +29,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
|||||||
this.mempoolBlockIndex = parseInt(params.get('id'), 10) || 0;
|
this.mempoolBlockIndex = parseInt(params.get('id'), 10) || 0;
|
||||||
return this.stateService.mempoolBlocks$
|
return this.stateService.mempoolBlocks$
|
||||||
.pipe(
|
.pipe(
|
||||||
|
filter((mempoolBlocks) => mempoolBlocks.length > 0),
|
||||||
map((mempoolBlocks) => {
|
map((mempoolBlocks) => {
|
||||||
while (!mempoolBlocks[this.mempoolBlockIndex]) {
|
while (!mempoolBlocks[this.mempoolBlockIndex]) {
|
||||||
this.mempoolBlockIndex--;
|
this.mempoolBlockIndex--;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
.bitcoin-block {
|
.bitcoin-block {
|
||||||
width: 125px;
|
width: 125px;
|
||||||
height: 125px;
|
height: 125px;
|
||||||
|
transition: 2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-size {
|
.block-size {
|
||||||
|
@ -23,13 +23,14 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
arrowVisible = false;
|
arrowVisible = false;
|
||||||
|
|
||||||
rightPosition = 0;
|
rightPosition = 0;
|
||||||
transition = '1s';
|
transition = '2s';
|
||||||
|
|
||||||
markIndex: number;
|
markIndex: number;
|
||||||
txFeePerVSize: number;
|
txFeePerVSize: number;
|
||||||
|
|
||||||
resetTransitionTimeout: number;
|
resetTransitionTimeout: number;
|
||||||
blocksLeftToHalving: number;
|
|
||||||
|
blockIndex = 1;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@ -37,14 +38,11 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
this.stateService.blocks$
|
|
||||||
.subscribe((block) => {
|
|
||||||
this.blocksLeftToHalving = 630000 - block.height;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.mempoolBlocksSubscription = this.stateService.mempoolBlocks$
|
this.mempoolBlocksSubscription = this.stateService.mempoolBlocks$
|
||||||
.subscribe((blocks) => {
|
.subscribe((blocks) => {
|
||||||
|
blocks.forEach((block, i) => {
|
||||||
|
block.index = this.blockIndex + i;
|
||||||
|
});
|
||||||
const stringifiedBlocks = JSON.stringify(blocks);
|
const stringifiedBlocks = JSON.stringify(blocks);
|
||||||
this.mempoolBlocksFull = JSON.parse(stringifiedBlocks);
|
this.mempoolBlocksFull = JSON.parse(stringifiedBlocks);
|
||||||
this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
|
this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
|
||||||
@ -65,6 +63,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
this.calculateTransactionPosition();
|
this.calculateTransactionPosition();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.stateService.blocks$
|
||||||
|
.subscribe(([block]) => {
|
||||||
|
if (block.matchRate >= 80) {
|
||||||
|
this.blockIndex++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.stateService.networkChanged$
|
this.stateService.networkChanged$
|
||||||
.subscribe((network) => this.network = network);
|
.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
@ -79,7 +84,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.stateService.blocks$
|
this.stateService.blocks$
|
||||||
.pipe(take(8))
|
.pipe(take(8))
|
||||||
.subscribe((block) => {
|
.subscribe(([block]) => {
|
||||||
if (this.stateService.latestBlockHeight === block.height) {
|
if (this.stateService.latestBlockHeight === block.height) {
|
||||||
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', block.id], { state: { data: { block } }});
|
this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', block.id], { state: { data: { block } }});
|
||||||
}
|
}
|
||||||
@ -104,8 +109,8 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
this.mempoolBlocksSubscription.unsubscribe();
|
this.mempoolBlocksSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByFn(index: number) {
|
trackByFn(index: number, block: MempoolBlock) {
|
||||||
return index;
|
return block.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||||
@ -176,7 +181,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
this.transition = 'inherit';
|
this.transition = 'inherit';
|
||||||
this.rightPosition = this.markIndex * (this.blockWidth + this.blockPadding) + 0.5 * this.blockWidth;
|
this.rightPosition = this.markIndex * (this.blockWidth + this.blockPadding) + 0.5 * this.blockWidth;
|
||||||
this.arrowVisible = true;
|
this.arrowVisible = true;
|
||||||
this.resetTransitionTimeout = window.setTimeout(() => this.transition = '1s', 100);
|
this.resetTransitionTimeout = window.setTimeout(() => this.transition = '2s', 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,19 +113,20 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.stateService.blocks$
|
this.stateService.blocks$
|
||||||
.subscribe((block) => this.latestBlock = block);
|
.subscribe(([block, txConfirmed]) => {
|
||||||
|
this.latestBlock = block;
|
||||||
|
|
||||||
this.stateService.txConfirmed$
|
if (txConfirmed) {
|
||||||
.subscribe((block) => {
|
this.tx.status = {
|
||||||
this.tx.status = {
|
confirmed: true,
|
||||||
confirmed: true,
|
block_height: block.height,
|
||||||
block_height: block.height,
|
block_hash: block.id,
|
||||||
block_hash: block.id,
|
block_time: block.timestamp,
|
||||||
block_time: block.timestamp,
|
};
|
||||||
};
|
this.stateService.markBlock$.next({ blockHeight: block.height });
|
||||||
this.stateService.markBlock$.next({ blockHeight: block.height });
|
this.audioService.playSound('magic');
|
||||||
this.audioService.playSound('magic');
|
this.findBlockAndSetFeeRating();
|
||||||
this.findBlockAndSetFeeRating();
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.stateService.txReplaced$
|
this.stateService.txReplaced$
|
||||||
@ -171,10 +172,10 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
findBlockAndSetFeeRating() {
|
findBlockAndSetFeeRating() {
|
||||||
this.stateService.blocks$
|
this.stateService.blocks$
|
||||||
.pipe(
|
.pipe(
|
||||||
filter((block) => block.height === this.tx.status.block_height),
|
filter(([block]) => block.height === this.tx.status.block_height),
|
||||||
take(1)
|
take(1)
|
||||||
)
|
)
|
||||||
.subscribe((block) => {
|
.subscribe(([block]) => {
|
||||||
const feePervByte = this.tx.fee / (this.tx.weight / 4);
|
const feePervByte = this.tx.fee / (this.tx.weight / 4);
|
||||||
this.medianFeeNeeded = Math.round(block.feeRange[Math.round(block.feeRange.length * 0.5)]);
|
this.medianFeeNeeded = Math.round(block.feeRange[Math.round(block.feeRange.length * 0.5)]);
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
<span *ngIf="showConfirmations && latestBlock">
|
||||||
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">{{ latestBlock.height - tx.status.block_height + 1 }} confirmation<ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1">s</ng-container></button>
|
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">{{ latestBlock.height - tx.status.block_height + 1 }} confirmation<ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1">s</ng-container></button>
|
||||||
<ng-template #unconfirmedButton>
|
<ng-template #unconfirmedButton>
|
||||||
<button type="button" class="btn btn-sm btn-danger mt-2">Unconfirmed</button>
|
<button type="button" class="btn btn-sm btn-danger mt-2">Unconfirmed</button>
|
||||||
|
@ -22,7 +22,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
@Output() loadMore = new EventEmitter();
|
@Output() loadMore = new EventEmitter();
|
||||||
|
|
||||||
latestBlock$: Observable<Block>;
|
latestBlock: Block;
|
||||||
outspends: Outspend[] = [];
|
outspends: Outspend[] = [];
|
||||||
assetsMinimal: any;
|
assetsMinimal: any;
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.latestBlock$ = this.stateService.blocks$;
|
this.stateService.blocks$.subscribe(([block]) => this.latestBlock = block);
|
||||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
if (this.network === 'liquid') {
|
if (this.network === 'liquid') {
|
||||||
|
@ -89,10 +89,13 @@ export interface Block {
|
|||||||
merkle_root: string;
|
merkle_root: string;
|
||||||
previousblockhash: string;
|
previousblockhash: string;
|
||||||
|
|
||||||
|
// Custom properties
|
||||||
medianFee?: number;
|
medianFee?: number;
|
||||||
feeRange?: number[];
|
feeRange?: number[];
|
||||||
reward?: number;
|
reward?: number;
|
||||||
coinbaseTx?: Transaction;
|
coinbaseTx?: Transaction;
|
||||||
|
matchRate: number;
|
||||||
|
stage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Address {
|
export interface Address {
|
||||||
|
@ -25,6 +25,7 @@ export interface MempoolBlock {
|
|||||||
medianFee: number;
|
medianFee: number;
|
||||||
totalFees: number;
|
totalFees: number;
|
||||||
feeRange: number[];
|
feeRange: number[];
|
||||||
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MemPoolState {
|
export interface MemPoolState {
|
||||||
|
@ -5,17 +5,20 @@ import { Injectable } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class AudioService {
|
export class AudioService {
|
||||||
audio = new Audio();
|
audio = new Audio();
|
||||||
|
isPlaying = false;
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
public playSound(name: 'magic' | 'chime' | 'cha-ching') {
|
public playSound(name: 'magic' | 'chime' | 'cha-ching' | 'bright-harmony') {
|
||||||
try {
|
if (this.isPlaying) {
|
||||||
this.audio.src = '../../../resources/sounds/' + name + '.mp3';
|
return;
|
||||||
this.audio.load();
|
|
||||||
this.audio.play();
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Play sound failed', e);
|
|
||||||
}
|
}
|
||||||
|
this.isPlaying = true;
|
||||||
|
this.audio.src = '../../../resources/sounds/' + name + '.mp3';
|
||||||
|
this.audio.load();
|
||||||
|
this.audio.play().catch((e) => {
|
||||||
|
console.log('Play sound failed', e);
|
||||||
|
});
|
||||||
|
setTimeout(() => this.isPlaying = false, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { Block, Transaction } from '../interfaces/electrs.interface';
|
|||||||
import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface';
|
import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface';
|
||||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
|
import { KEEP_BLOCKS_AMOUNT } from '../app.constants';
|
||||||
|
|
||||||
interface MarkBlockState {
|
interface MarkBlockState {
|
||||||
blockHeight?: number;
|
blockHeight?: number;
|
||||||
@ -19,11 +20,10 @@ export class StateService {
|
|||||||
latestBlockHeight = 0;
|
latestBlockHeight = 0;
|
||||||
|
|
||||||
networkChanged$ = new ReplaySubject<string>(1);
|
networkChanged$ = new ReplaySubject<string>(1);
|
||||||
blocks$ = new ReplaySubject<Block>(8);
|
blocks$ = new ReplaySubject<[Block, boolean]>(KEEP_BLOCKS_AMOUNT);
|
||||||
conversions$ = new ReplaySubject<any>(1);
|
conversions$ = new ReplaySubject<any>(1);
|
||||||
mempoolStats$ = new ReplaySubject<MemPoolState>(1);
|
mempoolStats$ = new ReplaySubject<MemPoolState>(1);
|
||||||
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
||||||
txConfirmed$ = new Subject<Block>();
|
|
||||||
txReplaced$ = new Subject<Transaction>();
|
txReplaced$ = new Subject<Transaction>();
|
||||||
mempoolTransactions$ = new Subject<Transaction>();
|
mempoolTransactions$ = new Subject<Transaction>();
|
||||||
blockTransactions$ = new Subject<Transaction>();
|
blockTransactions$ = new Subject<Transaction>();
|
||||||
|
@ -64,7 +64,7 @@ export class WebsocketService {
|
|||||||
blocks.forEach((block: Block) => {
|
blocks.forEach((block: Block) => {
|
||||||
if (block.height > this.stateService.latestBlockHeight) {
|
if (block.height > this.stateService.latestBlockHeight) {
|
||||||
this.stateService.latestBlockHeight = block.height;
|
this.stateService.latestBlockHeight = block.height;
|
||||||
this.stateService.blocks$.next(block);
|
this.stateService.blocks$.next([block, false]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -76,12 +76,11 @@ export class WebsocketService {
|
|||||||
if (response.block) {
|
if (response.block) {
|
||||||
if (response.block.height > this.stateService.latestBlockHeight) {
|
if (response.block.height > this.stateService.latestBlockHeight) {
|
||||||
this.stateService.latestBlockHeight = response.block.height;
|
this.stateService.latestBlockHeight = response.block.height;
|
||||||
this.stateService.blocks$.next(response.block);
|
this.stateService.blocks$.next([response.block, !!response.txConfirmed]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.txConfirmed) {
|
if (response.txConfirmed) {
|
||||||
this.isTrackingTx = false;
|
this.isTrackingTx = false;
|
||||||
this.stateService.txConfirmed$.next(response.block);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
frontend/src/resources/sounds/bright-harmony.mp3
Normal file
BIN
frontend/src/resources/sounds/bright-harmony.mp3
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user