diff --git a/backend/src/index.ts b/backend/src/index.ts index 5d2fb145b..2dba460ed 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -165,13 +165,11 @@ class Server { const foundTransactions: TransactionExtended[] = []; transactions.forEach((tx) => { - const someVin = tx.vin.some((vin) => vin.prevout.scriptpubkey_address === client['track-address']); - if (someVin) { + if (tx.vin.some((vin) => vin.prevout.scriptpubkey_address === client['track-address'])) { foundTransactions.push(tx); return; } - const someVout = tx.vout.some((vout) => vout.scriptpubkey_address === client['track-address']); - if (someVout) { + if (tx.vout.some((vout) => vout.scriptpubkey_address === client['track-address'])) { foundTransactions.push(tx); } }); diff --git a/frontend/proxy.conf.json b/frontend/proxy.conf.json index a2f737d47..7de229905 100644 --- a/frontend/proxy.conf.json +++ b/frontend/proxy.conf.json @@ -9,7 +9,7 @@ "ws": true }, "/electrs": { - "target": "https://www.blockstream.info/api/", + "target": "https://www.blockstream.info/testnet/api/", "secure": false, "pathRewrite": { "^/electrs": "" diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index ebb9abd24..df41afe05 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -40,6 +40,7 @@ import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockc import { BlockchainComponent } from './components/blockchain/blockchain.component'; import { FooterComponent } from './components/footer/footer.component'; import { ExplorerComponent } from './components/explorer/explorer.component'; +import { AudioService } from './services/audio.service'; @NgModule({ declarations: [ @@ -87,6 +88,7 @@ import { ExplorerComponent } from './components/explorer/explorer.component'; StateService, WebsocketService, VbytesPipe, + AudioService, ], bootstrap: [AppComponent] }) diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 6a7d8dded..11152c93f 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -6,13 +6,31 @@

Contributors

-

Mempool.Space is a realtime Bitcoin blockchain explorer and mempool visualizer.

-

Development @softbtc +

Development @softsimon_
Operations @wiz
Design @markjborg + +

+ +

Github

+ + + + + Git + + + + + + github.com/mempool-space/mempool.space -

HTTP API

+

+ +
+

HTTP API

+
@@ -33,7 +51,11 @@
-

WebSocket API

+

+ +
+

WebSocket API

+
diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index a418f74ce..c28b4885c 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -13,17 +13,17 @@
- - - - - + - + + + + +
Number of transactions{{ address.chain_stats.tx_count + address.mempool_stats.tx_count }}
Total received{{ (address.chain_stats.funded_txo_sum + address.mempool_stats.funded_txo_sum) / 100000000 | number: '1.2-2' }} BTC{{ receieved / 100000000 | number: '1.2-8' }} BTC
Total sent{{ (address.chain_stats.spent_txo_sum + address.mempool_stats.spent_txo_sum) / 100000000 | number: '1.2-2' }} BTC{{ sent / 100000000 | number: '1.2-8' }} BTC
Balance{{ (receieved - sent) / 100000000 | number: '1.2-8' }} BTC
@@ -40,7 +40,7 @@
-

{{ transactions?.length || '?' }} of {{ address.chain_stats.tx_count + address.mempool_stats.tx_count + addedTransactions }} transactions

+

{{ transactions?.length || '?' }} of {{ txCount }} transactions

@@ -49,7 +49,7 @@


- + diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index 5e0dfb4a0..e70dda535 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -5,6 +5,7 @@ import { switchMap } from 'rxjs/operators'; import { Address, Transaction } from '../../interfaces/electrs.interface'; import { WebsocketService } from 'src/app/services/websocket.service'; import { StateService } from 'src/app/services/state.service'; +import { AudioService } from 'src/app/services/audio.service'; @Component({ selector: 'app-address', @@ -18,13 +19,17 @@ export class AddressComponent implements OnInit, OnDestroy { transactions: Transaction[]; isLoadingTransactions = true; error: any; - addedTransactions = 0; + + txCount = 0; + receieved = 0; + sent = 0; constructor( private route: ActivatedRoute, private electrsApiService: ElectrsApiService, private websocketService: WebsocketService, private stateService: StateService, + private audioService: AudioService, ) { } ngOnInit() { @@ -43,9 +48,10 @@ export class AddressComponent implements OnInit, OnDestroy { ) .subscribe((address) => { this.address = address; + this.updateChainStats(); this.websocketService.startTrackAddress(address.address); this.isLoadingAddress = false; - this.getAddressTransactions(address.address); + this.reloadAddressTransactions(address.address); }, (error) => { console.log(error); @@ -56,7 +62,25 @@ export class AddressComponent implements OnInit, OnDestroy { this.stateService.mempoolTransactions$ .subscribe((transaction) => { this.transactions.unshift(transaction); - this.addedTransactions++; + this.transactions = this.transactions.slice(); + this.txCount++; + + if (transaction.vout.some((vout) => vout.scriptpubkey_address === this.address.address)) { + this.audioService.playSound('cha-ching'); + } else { + this.audioService.playSound('chime'); + } + + transaction.vin.forEach((vin) => { + if (vin.prevout.scriptpubkey_address === this.address.address) { + this.sent += vin.prevout.value; + } + }); + transaction.vout.forEach((vout) => { + if (vout.scriptpubkey_address === this.address.address) { + this.receieved += vout.value; + } + }); }); this.stateService.blockTransactions$ @@ -64,6 +88,8 @@ export class AddressComponent implements OnInit, OnDestroy { const tx = this.transactions.find((t) => t.txid === transaction.txid); if (tx) { tx.status = transaction.status; + this.transactions = this.transactions.slice(); + this.audioService.playSound('magic'); } }); @@ -71,15 +97,24 @@ export class AddressComponent implements OnInit, OnDestroy { .subscribe((state) => { if (!state && this.transactions && this.transactions.length) { this.isLoadingTransactions = true; - this.getAddressTransactions(this.address.address); + this.reloadAddressTransactions(this.address.address); } }); } - getAddressTransactions(address: string) { + updateChainStats() { + this.receieved = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum; + this.sent = this.address.chain_stats.spent_txo_sum + this.address.mempool_stats.spent_txo_sum; + this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count; + } + + + reloadAddressTransactions(address: string) { + this.isLoadingTransactions = true; this.electrsApiService.getAddressTransactions$(address) .subscribe((transactions: any) => { this.transactions = transactions; + this.updateChainStats(); this.isLoadingTransactions = false; }); } diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 64953f16b..8091fe31e 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -59,6 +59,7 @@ export class BlockComponent implements OnInit { this.block = block; this.blockHeight = block.height; this.isLoadingBlock = false; + this.setBlockSubsidy(); this.getBlockTransactions(block.id); }, (error) => { @@ -74,6 +75,9 @@ export class BlockComponent implements OnInit { this.conversions = conversions; }); + } + + setBlockSubsidy() { let halvenings = Math.floor(this.block.height / 210000); while (halvenings > 0) { this.blockSubsidy = this.blockSubsidy / 2; diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index f2353b8cc..4682752c5 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -6,6 +6,7 @@ import { Transaction, Block } from '../../interfaces/electrs.interface'; import { of } from 'rxjs'; import { StateService } from '../../services/state.service'; import { WebsocketService } from '../../services/websocket.service'; +import { AudioService } from 'src/app/services/audio.service'; @Component({ selector: 'app-transaction', @@ -28,6 +29,7 @@ export class TransactionComponent implements OnInit, OnDestroy { private electrsApiService: ElectrsApiService, private stateService: StateService, private websocketService: WebsocketService, + private audioService: AudioService, ) { } ngOnInit() { @@ -73,6 +75,7 @@ export class TransactionComponent implements OnInit, OnDestroy { block_hash: block.id, block_time: block.timestamp, }; + this.audioService.playSound('magic'); }); } diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index 67140801a..fc130bef8 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef } from '@angular/core'; import { StateService } from '../../services/state.service'; import { Observable, forkJoin } from 'rxjs'; -import { Block, Outspend } from '../../interfaces/electrs.interface'; +import { Block, Outspend, Transaction } from '../../interfaces/electrs.interface'; import { ElectrsApiService } from '../../services/electrs-api.service'; @Component({ @@ -62,7 +62,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { this.stateService.viewFiat$.next(oldvalue); } - trackByFn(index: number) { - return index; + trackByFn(index: number, tx: Transaction) { + return tx.txid; } } diff --git a/frontend/src/app/services/audio.service.ts b/frontend/src/app/services/audio.service.ts new file mode 100644 index 000000000..556fcf0b2 --- /dev/null +++ b/frontend/src/app/services/audio.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class AudioService { + audio = new Audio(); + + constructor() { } + + public playSound(name: 'magic' | 'chime' | 'cha-ching') { + try { + this.audio.src = '../../../assets/sounds/' + name + '.mp3'; + this.audio.load(); + this.audio.play(); + } catch (e) { + console.log('Play sound failed', e); + } + } + +} diff --git a/frontend/src/assets/sounds/cha-ching.mp3 b/frontend/src/assets/sounds/cha-ching.mp3 new file mode 100644 index 000000000..ce5c63ca7 Binary files /dev/null and b/frontend/src/assets/sounds/cha-ching.mp3 differ diff --git a/frontend/src/assets/sounds/chime.mp3 b/frontend/src/assets/sounds/chime.mp3 new file mode 100644 index 000000000..bfbd9bb82 Binary files /dev/null and b/frontend/src/assets/sounds/chime.mp3 differ diff --git a/frontend/src/assets/sounds/magic.mp3 b/frontend/src/assets/sounds/magic.mp3 new file mode 100644 index 000000000..22e40a4ee Binary files /dev/null and b/frontend/src/assets/sounds/magic.mp3 differ