Skip to content

Commit 4569d91

Browse files
authored
Add Bitcoin UTXO verification table to BTC monitoring page (#145)
Display live UTXOs from Bitcoin Core with mempool.space links for independent on-chain balance verification.
1 parent 6c6ca31 commit 4569d91

File tree

4 files changed

+83
-0
lines changed

4 files changed

+83
-0
lines changed

src/assets/monitoring-btc.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ <h2>BTC Balance History</h2>
9898
<div class="loading">Loading BTC monitoring data...</div>
9999
</div>
100100

101+
<div class="section">
102+
<h2>Bitcoin Core UTXOs (On-Chain Verification)</h2>
103+
<div id="utxo-content">
104+
<div class="loading">Loading UTXO data...</div>
105+
</div>
106+
</div>
107+
101108
<script src="/monitoring/chart.min.js"></script>
102109
<script src="/monitoring/chartjs-adapter-date-fns.min.js"></script>
103110
<script src="/monitoring/btc.js"></script>

src/assets/monitoring-btc.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,48 @@ function renderChart(points) {
198198
});
199199
}
200200

201+
async function loadUtxos() {
202+
var container = document.getElementById('utxo-content');
203+
try {
204+
var res = await fetch('/monitoring/btc/utxos');
205+
if (!res.ok) throw new Error('HTTP ' + res.status);
206+
var data = await res.json();
207+
renderUtxos(data);
208+
} catch (e) {
209+
container.innerHTML = '<div class="error">Failed to load UTXOs: ' + e.message + '</div>';
210+
}
211+
}
212+
213+
function renderUtxos(data) {
214+
var container = document.getElementById('utxo-content');
215+
var html = '<table>';
216+
html += '<tr><th>TXID</th><th>Vout</th><th>Address</th><th class="number">BTC</th><th class="number">Confirmations</th></tr>';
217+
218+
for (var i = 0; i < data.utxos.length; i++) {
219+
var u = data.utxos[i];
220+
var shortTxid = u.txid.substring(0, 8) + '...' + u.txid.substring(u.txid.length - 8);
221+
html += '<tr>';
222+
html += '<td><a href="https://mempool.space/tx/' + u.txid + '" target="_blank" rel="noopener">' + shortTxid + '</a></td>';
223+
html += '<td>' + u.vout + '</td>';
224+
html += '<td>' + u.address + '</td>';
225+
html += '<td class="number">' + fmtBtc(u.amount) + '</td>';
226+
html += '<td class="number">' + u.confirmations.toLocaleString() + '</td>';
227+
html += '</tr>';
228+
}
229+
230+
html += '<tr class="total-row">';
231+
html += '<td>' + data.count + ' UTXOs</td><td></td><td></td>';
232+
html += '<td class="number">' + fmtBtc(data.totalAmount) + '</td>';
233+
html += '<td></td>';
234+
html += '</tr>';
235+
html += '</table>';
236+
237+
container.innerHTML = html;
238+
}
239+
201240
loadData();
202241
loadChart('24h');
242+
loadUtxos();
203243

204244
var rangeButtons = document.querySelectorAll('.range-buttons button[data-range]');
205245
for (var i = 0; i < rangeButtons.length; i++) {

src/integration/blockchain/bitcoin/bitcoin-client.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import { Config } from 'src/config/config';
22
import { HttpRequestConfig, HttpService } from 'src/shared/services/http.service';
33
import { LightningHelper } from '../lightning/lightning-helper';
44

5+
export interface UnspentUtxo {
6+
txid: string;
7+
vout: number;
8+
address: string;
9+
amount: number;
10+
confirmations: number;
11+
}
12+
513
export class BitcoinClient {
614
constructor(private readonly http: HttpService) {}
715

@@ -24,6 +32,23 @@ export class BitcoinClient {
2432
return LightningHelper.btcToSat(balanceResult);
2533
}
2634

35+
async listUnspent(): Promise<UnspentUtxo[]> {
36+
const url = Config.blockchain.bitcoin.gatewayUrl;
37+
38+
return this.http
39+
.post<{ result: UnspentUtxo[] }>(
40+
url,
41+
{
42+
id: 1,
43+
jsonrpc: '2.0',
44+
method: 'listunspent',
45+
params: [1, 9999999],
46+
},
47+
this.httpConfig(),
48+
)
49+
.then((r) => r.result);
50+
}
51+
2752
// --- HELPER --- //
2853
private httpConfig(): HttpRequestConfig {
2954
return {

src/subdomains/monitoring/controllers/monitoring.controller.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { ApiExcludeEndpoint } from '@nestjs/swagger';
33
import { Response } from 'express';
44
import { readFileSync } from 'fs';
55
import { join } from 'path';
6+
import { UnspentUtxo } from 'src/integration/blockchain/bitcoin/bitcoin-client';
7+
import { BitcoinService } from 'src/integration/blockchain/bitcoin/bitcoin.service';
68
import { EvmTokenBalanceJson } from '../dto/monitoring.dto';
79
import { MonitoringBalanceRepository } from '../repositories/monitoring-balance.repository';
810
import { MonitoringEvmBalanceRepository } from '../repositories/monitoring-evm-balance.repository';
@@ -14,6 +16,7 @@ export class MonitoringController {
1416
private readonly monitoringRepo: MonitoringRepository,
1517
private readonly monitoringBalanceRepo: MonitoringBalanceRepository,
1618
private readonly monitoringEvmBalanceRepo: MonitoringEvmBalanceRepository,
19+
private readonly bitcoinService: BitcoinService,
1720
) {}
1821

1922
@Get()
@@ -44,6 +47,14 @@ export class MonitoringController {
4447
);
4548
}
4649

50+
@Get('btc/utxos')
51+
@ApiExcludeEndpoint()
52+
async btcUtxos(): Promise<{ utxos: UnspentUtxo[]; count: number; totalAmount: number }> {
53+
const utxos = await this.bitcoinService.getDefaultClient().listUnspent();
54+
const totalAmount = utxos.reduce((sum, u) => sum + u.amount, 0);
55+
return { utxos, count: utxos.length, totalAmount };
56+
}
57+
4758
@Get('usd')
4859
@ApiExcludeEndpoint()
4960
async usdPage(@Res() res: Response): Promise<void> {

0 commit comments

Comments
 (0)