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
+
+
+
+
+
+ github.com/mempool-space/mempool.space
- HTTP API
+
+
+
+
HTTP API
+
- 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