2026-10-30 12:28:48 +01:00
|
|
|
// Generated, do not edit. sha256:f1a362d8f5a38865a6fcd067548c9a9a2cf682e9141ad905ab0415dc770a62a9
|
2026-10-29 11:57:29 +01:00
|
|
|
import { CONFIG } from '../../shared/config.js';
|
|
|
|
|
import { activeConnection } from '../data/connections.js';
|
|
|
|
|
import { t } from '../i18n/index.js';
|
|
|
|
|
|
|
|
|
|
export async function explorerBase() {
|
|
|
|
|
return (await activeConnection('btc_explorer')).url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const HTTP_TIMEOUT_MS = 12000;
|
2026-10-30 12:28:48 +01:00
|
|
|
const HTTP_BACKOFF_MS = [2000, 5000, 10000];
|
2026-10-29 11:57:29 +01:00
|
|
|
export async function fetchJson(url, label, options = {}) {
|
|
|
|
|
let laatste;
|
2026-10-30 12:28:48 +01:00
|
|
|
for (let poging = 0; poging <= HTTP_BACKOFF_MS.length; poging++) {
|
|
|
|
|
if (poging) await new Promise(r => setTimeout(r, HTTP_BACKOFF_MS[poging - 1]));
|
2026-10-29 11:57:29 +01:00
|
|
|
const ctrl = new AbortController();
|
|
|
|
|
const timer = setTimeout(() => ctrl.abort(), HTTP_TIMEOUT_MS);
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(url, { ...options, signal: ctrl.signal });
|
|
|
|
|
if (res.ok) return await res.json();
|
|
|
|
|
laatste = new Error(t('integrations.price.callFailed', { label, status: res.status }));
|
|
|
|
|
if (res.status < 500 && res.status !== 429) throw laatste;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (e === laatste) throw e;
|
|
|
|
|
laatste = e.name === 'AbortError'
|
|
|
|
|
? new Error(t('integrations.price.callTimeout', { label, s: HTTP_TIMEOUT_MS / 1000 }))
|
|
|
|
|
: e;
|
|
|
|
|
} finally {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw laatste;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function addressInfo(addr) {
|
|
|
|
|
const base = await explorerBase();
|
|
|
|
|
const d = await fetchJson(`${base}/api/address/${addr}`, t('integrations.price.labelAddress', { adres: addr }));
|
|
|
|
|
const balance_sats = d.chain_stats.funded_txo_sum - d.chain_stats.spent_txo_sum;
|
|
|
|
|
const tx_count = d.chain_stats.tx_count + d.mempool_stats.tx_count;
|
|
|
|
|
return { balance_sats, tx_count };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PAGE = 25;
|
|
|
|
|
const MAX_PAGES = 200;
|
|
|
|
|
const lastConfirmedTxid = batch => {
|
|
|
|
|
for (let i = batch.length - 1; i >= 0; i--) {
|
|
|
|
|
if (batch[i].status && batch[i].status.confirmed) return batch[i].txid;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
export async function addressTxs(addr) {
|
|
|
|
|
const base = await explorerBase();
|
|
|
|
|
const all = await fetchJson(`${base}/api/address/${addr}/txs`, t('integrations.price.labelAddressTxs', { adres: addr }));
|
|
|
|
|
|
|
|
|
|
let cursor = lastConfirmedTxid(all);
|
|
|
|
|
let pages = 0;
|
|
|
|
|
while (cursor && pages < MAX_PAGES) {
|
|
|
|
|
await new Promise(r => setTimeout(r, 150));
|
|
|
|
|
let batch;
|
|
|
|
|
try {
|
|
|
|
|
batch = await fetchJson(`${base}/api/address/${addr}/txs/chain/${cursor}`, t('integrations.price.labelAddressTxsPage'));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn(`[addressTxs] pagination stopped for ${addr}: ${e.message}`);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!batch.length) break;
|
|
|
|
|
all.push(...batch);
|
|
|
|
|
if (batch.length < PAGE) break;
|
|
|
|
|
cursor = batch[batch.length - 1].txid;
|
|
|
|
|
pages++;
|
|
|
|
|
}
|
|
|
|
|
return all;
|
|
|
|
|
}
|