@anywalletkit/wallet-btc 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/{chunk-4WUAN3DC.js → chunk-TZNOUBSP.js} +24 -3
- package/dist/chunk-TZNOUBSP.js.map +1 -0
- package/dist/history.cjs +10 -2
- package/dist/history.cjs.map +1 -1
- package/dist/history.js +1 -1
- package/dist/index.cjs +76 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +61 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-4WUAN3DC.js.map +0 -1
package/README.md
CHANGED
|
@@ -60,6 +60,14 @@ current tip (`antiFeeSniping`, default on when the transport can report the heig
|
|
|
60
60
|
|
|
61
61
|
## Safety checks
|
|
62
62
|
|
|
63
|
+
A broadcast with no definitive answer (fetch threw, a timeout, a 5xx or 408, an
|
|
64
|
+
unreadable body) throws `BroadcastUnknownError` (`maybeSent`, `hash` = the txid computed
|
|
65
|
+
before sending): the transaction may be in the mempool, so do not build another spend.
|
|
66
|
+
A node's refusal (4xx with a reason, an error body) stays an error marked answered
|
|
67
|
+
(`isAnsweredRefusal`); with several clients it is a refusal only when every one refused.
|
|
68
|
+
"Already in the mempool / block chain" returns the txid. A custom `BtcClient` should
|
|
69
|
+
mark refusals `answered: true`. Outputs are matched to recipient and change by script.
|
|
70
|
+
|
|
63
71
|
Before signing **and** before broadcasting — also for caller-supplied raw hex —
|
|
64
72
|
every transaction is resolved into a `BtcTxDescription` and must pass:
|
|
65
73
|
|
|
@@ -12,6 +12,19 @@ async function fetchWithTimeout(fetchFn, url, init, timeoutMs, provider) {
|
|
|
12
12
|
throw new NetworkError(`Request failed: ${url}`, { provider, cause: e });
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
var refusedStatus = (status) => status >= 400 && status < 500 && status !== 408;
|
|
16
|
+
function refusal(e) {
|
|
17
|
+
return Object.assign(e, { answered: true });
|
|
18
|
+
}
|
|
19
|
+
function isAnsweredRefusal(e) {
|
|
20
|
+
return e?.answered === true;
|
|
21
|
+
}
|
|
22
|
+
function isAlreadyKnown(e) {
|
|
23
|
+
for (let x = e, depth = 0; x instanceof Error && depth < 5; x = x.cause, depth++) {
|
|
24
|
+
if (/txn-already-(in-mempool|known)|already (known|in (the )?(mempool|block ?chain))/i.test(x.message)) return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
15
28
|
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16
29
|
var isHex64 = (v) => typeof v === "string" && /^[0-9a-fA-F]{64}$/.test(v);
|
|
17
30
|
var isIntString = (v) => typeof v === "string" && /^-?\d+$/.test(v);
|
|
@@ -57,7 +70,11 @@ var BlockbookClient = class _BlockbookClient {
|
|
|
57
70
|
async get(path, check) {
|
|
58
71
|
const url = `${this.base}/api${path}`;
|
|
59
72
|
const res = await fetchWithTimeout(this.fetchFn, url, void 0, this.timeoutMs, this.base);
|
|
60
|
-
if (!res.ok)
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const reason = refusedStatus(res.status) ? (await res.text().catch(() => "")).slice(0, 200) : "";
|
|
75
|
+
const err = new NetworkError2(`Blockbook request failed: ${res.status} ${res.statusText} (${url})${reason ? `: ${reason}` : ""}`, { provider: this.base });
|
|
76
|
+
throw refusedStatus(res.status) ? refusal(err) : err;
|
|
77
|
+
}
|
|
61
78
|
let body;
|
|
62
79
|
try {
|
|
63
80
|
body = await res.json();
|
|
@@ -121,7 +138,7 @@ var BlockbookClient = class _BlockbookClient {
|
|
|
121
138
|
}
|
|
122
139
|
async broadcast(rawTx) {
|
|
123
140
|
const data = await this.get(`/v2/sendtx/${rawTx}`, (v) => isRecord(v));
|
|
124
|
-
if (data.error) throw new Error(typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed");
|
|
141
|
+
if (data.error) throw refusal(new Error(typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed"));
|
|
125
142
|
if (!data.result) throw new NetworkError2("Blockbook returned no txid", { provider: this.base });
|
|
126
143
|
return data.result;
|
|
127
144
|
}
|
|
@@ -304,6 +321,10 @@ export {
|
|
|
304
321
|
CAIP2_GENESIS,
|
|
305
322
|
DEFAULT_TIMEOUT_MS,
|
|
306
323
|
fetchWithTimeout,
|
|
324
|
+
refusedStatus,
|
|
325
|
+
refusal,
|
|
326
|
+
isAnsweredRefusal,
|
|
327
|
+
isAlreadyKnown,
|
|
307
328
|
isRecord,
|
|
308
329
|
isHex64,
|
|
309
330
|
unconfirmedOutgoing,
|
|
@@ -315,4 +336,4 @@ export {
|
|
|
315
336
|
btcExplorerTxUrl,
|
|
316
337
|
createBtcHistory
|
|
317
338
|
};
|
|
318
|
-
//# sourceMappingURL=chunk-
|
|
339
|
+
//# sourceMappingURL=chunk-TZNOUBSP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blockbook.ts","../src/http.ts","../src/types.ts","../src/history.ts"],"sourcesContent":["import { NetworkError } from '@anywalletkit/core'\nimport { DEFAULT_TIMEOUT_MS, fetchWithTimeout, isIntString, isRecord, refusal, refusedStatus } from './http.js'\nimport type { BlockbookTx, BtcClient, Utxo } from './types.js'\n\nconst MEMPOOL_SPACE_URL = 'https://mempool.space'\n\nexport interface BlockbookVin { txid?: string; vout?: number; n?: number; isAddress?: boolean; isOwn?: boolean; addresses?: string[]; value?: string }\nexport interface BlockbookVout { n: number; isAddress?: boolean; isOwn?: boolean; addresses?: string[]; value?: string; spent?: boolean }\ninterface BlockbookAddressTx { txid: string; blockHeight: number; vin?: BlockbookVin[]; vout?: BlockbookVout[] }\n/** A Blockbook transaction with its inputs and outputs (`/api/v2/tx/{txid}`, or one row of `?details=txs`). */\nexport interface BlockbookTxDetails extends BlockbookTx {\n vin?: BlockbookVin[]\n vout?: BlockbookVout[]\n value?: string\n valueIn?: string\n}\n/** One `/api/v2/address/{a}?details=txs` page. */\nexport interface BlockbookAddressPage {\n page?: number\n totalPages?: number\n itemsOnPage?: number\n address?: string\n balance?: string\n unconfirmedBalance?: string\n txs?: number\n transactions?: BlockbookTxDetails[]\n}\n\n/**\n * Pending outgoing value the way Bitcoin Core's wallet counts it: a pending tx\n * is trusted only if every input is ours and confirmed or chained from another\n * trusted tx; only then its change counts back.\n */\nexport function unconfirmedOutgoing(transactions: BlockbookAddressTx[] | undefined, address: string): bigint {\n const pending = (transactions ?? []).filter((tx) => tx.blockHeight === -1)\n const pendingIds = new Set(pending.map((t) => t.txid))\n const byId = new Map(pending.map((t) => [t.txid, t]))\n const trusted = new Map<string, boolean>()\n const owned = (entries: Array<BlockbookVin | BlockbookVout> | undefined) =>\n (entries ?? []).filter((e) => e.isAddress && e.addresses?.includes(address)).reduce((s, e) => s + BigInt(e.value ?? 0), 0n)\n const isTrusted = (tx: BlockbookAddressTx): boolean => {\n if (trusted.has(tx.txid)) return trusted.get(tx.txid)!\n trusted.set(tx.txid, false)\n const vins = tx.vin ?? []\n const ours = vins.filter((v) => v.isAddress && v.addresses?.includes(address))\n const ok = vins.length > 0 && ours.length === vins.length && ours.every((v) => !v.txid || !pendingIds.has(v.txid) || isTrusted(byId.get(v.txid)!))\n trusted.set(tx.txid, ok)\n return ok\n }\n const consumed = new Set<string>()\n for (const tx of pending) for (const v of tx.vin ?? []) if (v.txid && pendingIds.has(v.txid)) consumed.add(`${v.txid}:${v.vout}`)\n return pending.reduce((total, tx) => {\n const spent = owned((tx.vin ?? []).filter((v) => !v.txid || !pendingIds.has(v.txid)))\n const change = isTrusted(tx) ? owned((tx.vout ?? []).filter((o) => !consumed.has(`${tx.txid}:${o.n}`))) : 0n\n return total + (spent - change)\n }, 0n)\n}\n\n/** Blockbook REST transport (Trezor's indexer; QuickNode's Blockbook add-on speaks the same API). */\nexport class BlockbookClient implements BtcClient {\n private readonly base: string\n private readonly fetchFn: typeof fetch\n private readonly timeoutMs: number\n\n constructor(config: { url: string }, fetchFn: typeof fetch = globalThis.fetch, timeoutMs = DEFAULT_TIMEOUT_MS) {\n this.base = config.url.replace(/\\/+$/, '').replace(/\\/api$/, '')\n this.fetchFn = fetchFn\n this.timeoutMs = timeoutMs\n }\n\n async connect(): Promise<void> {}\n async close(): Promise<void> {}\n\n private async get<T>(path: string, check: (v: unknown) => v is T): Promise<T> {\n const url = `${this.base}/api${path}`\n const res = await fetchWithTimeout(this.fetchFn, url, undefined, this.timeoutMs, this.base)\n if (!res.ok) {\n // A 4xx (not 408) is the server refusing: nothing was taken. A 5xx may have been forwarded (round 3, M12).\n const reason = refusedStatus(res.status) ? (await res.text().catch(() => '')).slice(0, 200) : ''\n const err = new NetworkError(`Blockbook request failed: ${res.status} ${res.statusText} (${url})${reason ? `: ${reason}` : ''}`, { provider: this.base })\n throw refusedStatus(res.status) ? refusal(err) : err\n }\n let body: unknown\n try { body = await res.json() } catch (e) { throw new NetworkError(`Blockbook returned non-JSON for ${url}`, { provider: this.base, cause: e }) }\n if (!check(body)) throw new NetworkError(`Blockbook returned an unexpected shape for ${url}`, { provider: this.base })\n return body\n }\n\n async getBalance(address: string) {\n const data = await this.get(`/v2/address/${address}?details=txs`, (v): v is { balance: string; unconfirmedBalance: string; transactions?: BlockbookAddressTx[] } =>\n isRecord(v) && isIntString(v.balance) && isIntString(v.unconfirmedBalance) && (v.transactions === undefined || Array.isArray(v.transactions)))\n return { confirmed: BigInt(data.balance), unconfirmed: BigInt(data.unconfirmedBalance), unconfirmedOutgoing: unconfirmedOutgoing(data.transactions, address) }\n }\n\n async listUnspent(address: string): Promise<Utxo[]> {\n const data = await this.get(`/v2/utxo/${address}`, (v): v is Array<{ txid: string; vout: number; value: string; height?: number }> =>\n Array.isArray(v) && v.every((u) => isRecord(u) && typeof u.txid === 'string' && typeof u.vout === 'number' && isIntString(u.value)))\n return data.map((u) => ({ txid: u.txid, vout: u.vout, value: BigInt(u.value), height: u.height ?? -1 }))\n }\n\n async getHistory(address: string) {\n const items: Array<{ txid: string; height: number }> = []\n for (let page = 1; ; page++) {\n const data = await this.get(`/v2/address/${address}?details=txslight&pageSize=1000&page=${page}`, (v): v is { transactions?: Array<{ txid: string; blockHeight: number }>; totalPages?: number } =>\n isRecord(v) && (v.transactions === undefined || Array.isArray(v.transactions)))\n for (const tx of data.transactions ?? []) items.push({ txid: tx.txid, height: tx.blockHeight })\n if (page >= (data.totalPages ?? 1)) break\n }\n return items\n }\n\n /** One page of the address's transactions, newest first, with inputs / outputs (what the activity history reads). */\n async getAddressPage(address: string, page = 1, pageSize = 25): Promise<BlockbookAddressPage> {\n const data = await this.get(`/v2/address/${address}?details=txs&page=${page}&pageSize=${pageSize}`, (v): v is BlockbookAddressPage =>\n isRecord(v) && (v.transactions === undefined || (Array.isArray(v.transactions) && v.transactions.every(BlockbookClient.isTx))))\n const out: BlockbookAddressPage = { ...data }\n for (const tx of out.transactions ?? []) delete (tx as { hex?: string }).hex\n return out\n }\n\n /** A transaction with its inputs / outputs; undefined when Blockbook does not know it. */\n async getTransactionDetails(txid: string): Promise<BlockbookTxDetails | undefined> {\n try {\n const tx = await this.get(`/v2/tx/${txid}`, (v): v is BlockbookTxDetails => BlockbookClient.isTx(v))\n const out: BlockbookTxDetails = { ...tx }\n delete (out as { hex?: string }).hex\n return out\n } catch (e) {\n if (e instanceof NetworkError && /\\b(400|404)\\b/.test(e.message)) return undefined\n throw e\n }\n }\n\n private static isTx(v: unknown): v is BlockbookTx {\n return isRecord(v) && typeof v.txid === 'string' && typeof v.blockHeight === 'number' && typeof v.confirmations === 'number'\n }\n\n async getTransactionInfo(txid: string): Promise<BlockbookTx | undefined> {\n try { return await this.get(`/v2/tx/${txid}`, BlockbookClient.isTx) } catch (e) {\n if (e instanceof NetworkError && /\\b(400|404)\\b/.test(e.message)) return undefined\n throw e\n }\n }\n\n async getTransaction(txid: string): Promise<string> {\n const data = await this.get(`/v2/tx/${txid}`, BlockbookClient.isTx)\n if (typeof data.hex !== 'string' || !data.hex) throw new NetworkError(`Transaction ${txid} has no hex data`, { provider: this.base })\n return data.hex\n }\n\n async broadcast(rawTx: string): Promise<string> {\n const data = await this.get(`/v2/sendtx/${rawTx}`, (v): v is { result?: string; error?: { message?: string } | string } => isRecord(v))\n if (data.error) throw refusal(new Error(typeof data.error === 'string' ? data.error : data.error.message ?? 'broadcast failed'))\n if (!data.result) throw new NetworkError('Blockbook returned no txid', { provider: this.base })\n return data.result\n }\n\n /** sat/vB. Blockbook reports BTC/kB (×1e5 → sat/vB); falls back to mempool.space when it has no estimate. */\n async estimateFee(blocks: number): Promise<number> {\n try {\n const data = await this.get(`/v1/estimatefee/${blocks}`, (v): v is { result?: string } | string => typeof v === 'string' || isRecord(v))\n const rate = Number(typeof data === 'string' ? data : data.result)\n if (rate > 0) return Math.max(rate * 100_000, 1)\n } catch { /* fall through */ }\n const res = await fetchWithTimeout(this.fetchFn, `${MEMPOOL_SPACE_URL}/api/v1/fees/recommended`, undefined, this.timeoutMs, MEMPOOL_SPACE_URL)\n if (!res.ok) throw new NetworkError('Fee estimation request failed', { provider: MEMPOOL_SPACE_URL })\n const d = (await res.json()) as { fastestFee: number; halfHourFee: number; hourFee: number; economyFee: number }\n const satPerVb = blocks <= 1 ? d.fastestFee : blocks <= 3 ? d.halfHourFee : blocks <= 6 ? d.hourFee : d.economyFee\n if (!satPerVb || satPerVb <= 0) throw new NetworkError('Fee estimation is unavailable', { provider: MEMPOOL_SPACE_URL })\n return satPerVb\n }\n\n async getBlockHeight(): Promise<number> {\n const data = await this.get('/v2', (v): v is { blockbook?: { bestHeight?: number } } => isRecord(v))\n const h = data.blockbook?.bestHeight\n if (typeof h !== 'number' || h <= 0) throw new NetworkError('Blockbook did not report a block height', { provider: this.base })\n return h\n }\n}\n","import { NetworkError } from '@anywalletkit/core'\n\nexport const DEFAULT_TIMEOUT_MS = 15_000\n\n/** fetch with a per-request timeout when the runtime has `AbortSignal.timeout` (Hermes needs runtime-hermes for it). */\nexport async function fetchWithTimeout(fetchFn: typeof fetch, url: string, init: RequestInit | undefined, timeoutMs: number, provider: string): Promise<Response> {\n const signal = typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(timeoutMs) : undefined\n try {\n return await fetchFn(url, { ...init, ...(signal ? { signal } : {}) })\n } catch (e) {\n throw new NetworkError(`Request failed: ${url}`, { provider, cause: e })\n }\n}\n\n/** An HTTP status that is the server refusing the request itself: 4xx, but not 408 (request timeout). */\nexport const refusedStatus = (status: number): boolean => status >= 400 && status < 500 && status !== 408\n\n/** Mark an error as a node's answer refusing the request: nothing was taken (round 3, M12). */\nexport function refusal<E extends Error>(e: E): E {\n return Object.assign(e, { answered: true })\n}\n\n/**\n * An endpoint answered with a refusal (HTTP 4xx other than 408, or an error body): nothing was\n * taken. A thrown fetch, a timeout, a 5xx or a body that cannot be read may have been.\n */\nexport function isAnsweredRefusal(e: unknown): boolean {\n return (e as { answered?: unknown } | null)?.answered === true\n}\n\n/** The node says it already holds the transaction (an earlier attempt delivered it): it is out, not refused. */\nexport function isAlreadyKnown(e: unknown): boolean {\n for (let x: unknown = e, depth = 0; x instanceof Error && depth < 5; x = x.cause, depth++) {\n if (/txn-already-(in-mempool|known)|already (known|in (the )?(mempool|block ?chain))/i.test(x.message)) return true\n }\n return false\n}\n\nexport const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nexport const isHex64 = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-fA-F]{64}$/.test(v)\nexport const isIntString = (v: unknown): v is string => typeof v === 'string' && /^-?\\d+$/.test(v)\n","/** Bitcoin mainnet, testnet or regtest. */\nexport type BtcNetwork = 'bitcoin' | 'testnet' | 'regtest'\n\n/** Address / script type the account spends from. BIP-84 p2wpkh (bc1q…) or BIP-86 p2tr (bc1p…). */\nexport type BtcAddressType = 'p2wpkh' | 'p2tr'\n\n/** Minimal chain-data client every transport implements. Values in satoshis, fee rates in sat/vB. */\nexport interface BtcClient {\n connect(): Promise<void>\n close(): Promise<void>\n getBalance(address: string): Promise<{ confirmed: bigint; unconfirmed: bigint; unconfirmedOutgoing?: bigint }>\n listUnspent(address: string): Promise<Utxo[]>\n getHistory(address: string): Promise<Array<{ txid: string; height: number }>>\n /** Raw transaction hex. */\n getTransaction(txid: string): Promise<string>\n /** Full tx record (used for receipts). */\n getTransactionInfo?(txid: string): Promise<BlockbookTx | undefined>\n /** Broadcast raw hex; returns the txid. */\n broadcast(rawTx: string): Promise<string>\n /** Fee rate in sat/vB for confirmation within `blocks`. */\n estimateFee(blocks: number): Promise<number>\n /** Current chain tip height (anti-fee-sniping locktime). Optional. */\n getBlockHeight?(): Promise<number>\n}\n\n/** An unspent output the account can spend. */\nexport interface Utxo {\n txid: string\n vout: number\n /** Satoshis. */\n value: bigint\n /** Block height; 0 or -1 while unconfirmed. */\n height: number\n}\n\n/** One transport to a Bitcoin indexer: Blockbook or Esplora over HTTP. */\nexport type BtcClientConfig =\n /** Trezor Blockbook REST (`https://btc1.trezor.io`, QuickNode's Blockbook add-on, …). */\n | { type: 'blockbook-http'; clientConfig: { url: string } }\n /** Esplora REST (`https://mempool.space/api`, `https://blockstream.info/api`). */\n | { type: 'esplora-http'; clientConfig: { url: string } }\n\n/**\n * Options for a Bitcoin wallet manager or account: transports, network, address type and fee\n * limits.\n */\nexport interface BtcConfig {\n /** One or more transports (config objects or ready `BtcClient`s). A list fails over in order. Omit for an offline (sign-only) wallet. */\n client?: BtcClientConfig | BtcClient | Array<BtcClientConfig | BtcClient>\n /** Network; default `bitcoin` (mainnet). */\n network?: BtcNetwork\n /** p2wpkh (BIP-84, default) or p2tr (BIP-86 taproot). Changes the derivation path and the address. */\n addressType?: BtcAddressType\n /** Extra attempts across the client list after the first failure (default 3). */\n retries?: number\n /** Per-request timeout in ms (default 15000). */\n timeoutMs?: number\n /** Max fee (sats) for `transfer`. */\n transferMaxFee?: bigint | number\n /** Max fee (sats) for `sendTransaction` / `signTransaction`. */\n transactionMaxFee?: bigint | number\n /** Fee-rate ceiling in sat/vB (default 500). Anything above is refused before signing or broadcast. */\n maxFeeRate?: number\n /** Coin-selection input cap (default 200). */\n maxInputs?: number\n /** Opt-in to replace-by-fee signalling (default true). */\n rbf?: boolean\n /** Set nLockTime to the current height so the tx cannot be mined into a reorg (default true; skipped when the transport has no `getBlockHeight`). */\n antiFeeSniping?: boolean\n /** Injectable fetch (tests, custom agents). Defaults to globalThis.fetch. */\n fetch?: typeof fetch\n}\n\n/** A spend. `value` in satoshis; `feeRate` in sat/vB (estimated for `confirmationTarget` blocks when omitted). */\nexport interface BtcSpend {\n to: string\n value: bigint | number | string\n feeRate?: bigint | number\n confirmationTarget?: number\n /** Override the account-level `rbf` default for this spend. */\n rbf?: boolean\n}\n\n/** `sendTransaction` accepts a spend or an already signed raw transaction hex. */\nexport type BtcTransaction = BtcSpend | string\n\n/** A Bitcoin transfer. Amount in satoshis. */\nexport interface BtcTransferOptions {\n /** Not supported on Bitcoin; present for the shared account interface. */\n token?: string\n recipient: string\n amount: bigint | number | string\n}\n\n/** A mined Bitcoin transaction. */\nexport interface BtcReceipt {\n hash: string\n /** Always 1 once mined — a Bitcoin transaction cannot fail after inclusion. */\n status: 1\n blockNumber: bigint\n confirmations: number\n /** Total fee in sats (when the transport reports it). */\n fee?: bigint\n hex?: string\n}\n\n/** Transport-neutral tx record (Blockbook's /api/v2/tx shape; Esplora is mapped onto it). */\nexport interface BlockbookTx {\n txid: string\n blockHeight: number\n confirmations: number\n fees?: string\n hex?: string\n blockTime?: number\n}\n\n/** The most one transaction can send (a sweep of every UTXO), after the fee. */\nexport interface BtcMaxSpendableResult {\n /** Max sats one transaction can send after the fee. 0n when nothing is spendable. */\n amount: bigint\n fee: bigint\n /** Always 0n: a sweep leaves no change. */\n changeValue: bigint\n /** Inputs the sweep would consume. */\n inputs: number\n feeRate: bigint\n}\n\n/** Problems a described transaction is flagged with. */\nexport type BtcWarning =\n /** Fee rate above `maxFeeRate` (also a hard error before signing/broadcast). */\n | 'high-fee-rate'\n /** Fee exceeds 10 % of the amount sent. */\n | 'fee-over-10pct-of-amount'\n /** An output is below its dust threshold. */\n | 'dust-output'\n /** Change was below dust and folded into the fee. */\n | 'no-change'\n\n/** One input of a described transaction: the UTXO it spends. */\nexport interface BtcTxInputDescription {\n txid: string\n vout: number\n value: bigint\n}\n\n/** One output of a described transaction, tagged as the recipient, our change, or unknown. */\nexport interface BtcTxOutputDescription {\n /** Undefined for scripts without an address form. */\n address: string | undefined\n value: bigint\n role: 'recipient' | 'change' | 'unknown'\n}\n\n/** \"What you sign\": the fully resolved transaction before (or after) signing. */\nexport interface BtcTxDescription {\n kind: 'btc-transfer'\n inputs: BtcTxInputDescription[]\n outputs: BtcTxOutputDescription[]\n /** Sats sent to non-change outputs. */\n amount: bigint\n fee: bigint\n /** sat/vB, rounded to 2 decimals. */\n feeRate: number\n vsize: number\n rbf: boolean\n lockTime: number\n /** Present once the transaction is signed. */\n txid?: string\n warnings: BtcWarning[]\n}\n\n/** CAIP-2 ids of the Bitcoin networks, by name. */\nexport const CAIP2_GENESIS: Record<BtcNetwork, string> = {\n bitcoin: 'bip122:000000000019d6689c085ae165831e93',\n testnet: 'bip122:000000000933ea01ad0ee984209779ba',\n regtest: 'bip122:0f9188f13cb7b2c71f2a335e3a4fc328',\n}\n","/**\n * Activity history for Bitcoin: a `HistorySource` over Blockbook's\n * `/api/v2/address/{a}?details=txs` pages (Trezor's indexer, QuickNode's\n * Blockbook add-on, any self-hosted instance), reusing the package's\n * `BlockbookClient` and failing over across several instances.\n *\n * One transaction = one native transfer in the owner's direction: a send is\n * what left to non-own outputs (fee separate), a receive is what landed on the\n * address. The cursor is the Blockbook page number. Explorer: mempool.space.\n */\nimport type { ChainTransfer, ChainTx, HistoryPage, HistoryQuery, HistorySource } from '@anywalletkit/core'\nimport { BlockbookClient, type BlockbookAddressPage, type BlockbookTxDetails } from './blockbook.js'\nimport { DEFAULT_TIMEOUT_MS } from './http.js'\nimport { CAIP2_GENESIS, type BtcNetwork } from './types.js'\n\n// ── Public types ─────────────────────────────────────────────────────────────\n\n/** Options for {@link createBtcHistory}. */\nexport interface BtcHistoryOptions {\n /** One Blockbook root (`https://btc1.trezor.io`). */\n blockbookUrl?: string\n /** Several roots, tried in order (failover). */\n blockbookUrls?: string[]\n /** `bitcoin` (default), `testnet`, `regtest`. */\n network?: BtcNetwork\n fetch?: typeof fetch\n /** Per request, ms. Default 15 000. */\n timeoutMs?: number\n /** Rows per Blockbook page (its max is 1000). Default: the query limit, capped at 100. */\n pageSize?: number\n}\n\n/** What a Bitcoin `ChainTx` was built from, and the address's own totals. */\nexport interface BtcHistoryRaw {\n /** The Blockbook transaction (vin / vout with addresses and values; `hex` dropped). */\n tx: BlockbookTxDetails\n /** Sum of inputs spent by the address, sats. */\n ownIn: bigint\n /** Sum of outputs paid to the address, sats. */\n ownOut: bigint\n}\n\n/** A `ChainTx` with the Blockbook transaction it was built from. */\nexport interface BtcChainTx extends ChainTx {\n raw: BtcHistoryRaw\n}\n\n/** One page of Bitcoin history. */\nexport interface BtcHistoryPage extends HistoryPage {\n items: BtcChainTx[]\n /** Blockbook page served / total pages. */\n page?: number\n totalPages?: number\n}\n\n/** Bitcoin history from a Blockbook instance. Build it with {@link createBtcHistory}. */\nexport interface BtcHistorySource extends HistorySource {\n listActivity(address: string, query?: HistoryQuery): Promise<BtcHistoryPage>\n getTransaction(hash: string, address: string): Promise<BtcChainTx | undefined>\n}\n\n/** Every configured Blockbook endpoint failed (or none was configured). */\nexport class HistoryUnavailableError extends Error {\n readonly chain: string\n readonly reason: string\n readonly attempts: readonly string[]\n constructor(chain: string, reason: string, attempts: readonly string[] = []) {\n super(`History unavailable for ${chain}: ${reason}`)\n this.name = 'HistoryUnavailableError'\n this.chain = chain\n this.reason = reason\n this.attempts = attempts\n }\n}\n\nconst DEFAULT_LIMIT = 25\nconst MAX_PAGE_SIZE = 100\n\n// ── Cursor: the next Blockbook page, base64 JSON ─────────────────────────────\n\n/** @internal Decoded form of a Bitcoin history cursor. */\nexport interface BtcHistoryCursor {\n v: 1\n /** Next page (1-based). */\n p: number\n /** Page size the pagination started with (changing it mid-way would skip or repeat rows). */\n s: number\n}\n\nconst B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\n\nfunction b64encode(s: string): string {\n let out = ''\n for (let i = 0; i < s.length; i += 3) {\n const c = [s.charCodeAt(i), s.charCodeAt(i + 1), s.charCodeAt(i + 2)]\n const n = (c[0]! << 16) | ((c[1]! || 0) << 8) | (c[2]! || 0)\n out += B64[(n >> 18) & 63]! + B64[(n >> 12) & 63]! + (Number.isNaN(c[1]) ? '=' : B64[(n >> 6) & 63]!) + (Number.isNaN(c[2]) ? '=' : B64[n & 63]!)\n }\n return out\n}\n\nfunction b64decode(s: string): string {\n const clean = s.replace(/[^A-Za-z0-9+/]/g, '')\n let out = ''\n for (let i = 0; i < clean.length; i += 4) {\n const c = [0, 1, 2, 3].map((k) => (i + k < clean.length ? B64.indexOf(clean[i + k]!) : -1))\n if (c[0]! < 0 || c[1]! < 0) throw new Error('bad base64')\n const n = (c[0]! << 18) | (c[1]! << 12) | ((c[2]! < 0 ? 0 : c[2]!) << 6) | (c[3]! < 0 ? 0 : c[3]!)\n out += String.fromCharCode((n >> 16) & 0xff)\n if (c[2]! >= 0) out += String.fromCharCode((n >> 8) & 0xff)\n if (c[3]! >= 0) out += String.fromCharCode(n & 0xff)\n }\n return out\n}\n\nexport function encodeCursor(c: BtcHistoryCursor): string {\n return b64encode(JSON.stringify(c))\n}\n\nexport function decodeCursor(s: string): BtcHistoryCursor {\n let parsed: unknown\n try {\n parsed = JSON.parse(b64decode(s))\n } catch {\n throw new Error('@anywalletkit/wallet-btc: malformed history cursor')\n }\n const c = parsed as Partial<BtcHistoryCursor> | null\n if (!c || c.v !== 1 || !Number.isInteger(c.p) || (c.p as number) < 1 || !Number.isInteger(c.s) || (c.s as number) < 1) throw new Error('@anywalletkit/wallet-btc: malformed history cursor')\n return { v: 1, p: c.p as number, s: c.s as number }\n}\n\n// ── Mapping ──────────────────────────────────────────────────────────────────\n\nconst big = (v: unknown): bigint => (typeof v === 'string' && /^-?\\d+$/.test(v) ? BigInt(v) : typeof v === 'number' ? BigInt(Math.trunc(v)) : 0n)\n\nexport function mapBlockbookTx(chain: string, address: string, tx: BlockbookTxDetails): BtcChainTx {\n const owns = (e: { isAddress?: boolean; addresses?: string[] }) => e.isAddress !== false && (e.addresses ?? []).includes(address)\n let ownIn = 0n, ownOut = 0n, sentToOthers = 0n\n for (const vin of tx.vin ?? []) if (owns(vin)) ownIn += big(vin.value)\n for (const vout of tx.vout ?? []) {\n if (owns(vout)) ownOut += big(vout.value)\n else sentToOthers += big(vout.value)\n }\n const firstInput = (tx.vin ?? []).find((v) => v.isAddress !== false && v.addresses?.[0])?.addresses?.[0]\n const firstOtherOutput = (tx.vout ?? []).find((v) => v.isAddress !== false && !owns(v) && v.addresses?.[0])?.addresses?.[0]\n const transfers: ChainTransfer[] = []\n let from: string, to: string | null, nativeValue: bigint\n if (ownIn > 0n) {\n // We spent inputs: a send (or a consolidation when every output is ours).\n from = address\n to = firstOtherOutput ?? address\n nativeValue = firstOtherOutput ? sentToOthers : ownOut\n if (nativeValue > 0n) transfers.push({ token: null, from: address, to, amount: nativeValue })\n } else {\n // Only outputs are ours: a receive (coinbase has no input address).\n from = firstInput ?? 'coinbase'\n to = address\n nativeValue = ownOut\n if (nativeValue > 0n) transfers.push({ token: null, from, to: address, amount: nativeValue })\n }\n const confirmed = tx.confirmations > 0 && tx.blockHeight > 0\n const out: BtcChainTx = {\n chain, hash: tx.txid, time: tx.blockTime ?? 0, status: confirmed ? 'confirmed' : 'pending', address, from, to, nativeValue, transfers,\n raw: { tx, ownIn, ownOut },\n }\n if (tx.fees !== undefined) out.fee = big(tx.fees)\n if (confirmed) out.block = BigInt(tx.blockHeight)\n return out\n}\n\n// ── Source ───────────────────────────────────────────────────────────────────\n\n/** mempool.space link for a transaction. */\nexport function btcExplorerTxUrl(hash: string, network: BtcNetwork = 'bitcoin'): string {\n return `https://mempool.space/${network === 'testnet' ? 'testnet/' : ''}tx/${hash}`\n}\n\n/**\n * A history source for Bitcoin addresses backed by Blockbook (Trezor's public instances,\n * QuickNode's add-on, or your own).\n *\n * @throws Error when no Blockbook URL is given.\n */\nexport function createBtcHistory(options: BtcHistoryOptions = {}): BtcHistorySource {\n const network = options.network ?? 'bitcoin'\n const chain = CAIP2_GENESIS[network]\n const urls = [...(options.blockbookUrls ?? []), ...(options.blockbookUrl ? [options.blockbookUrl] : [])]\n if (urls.length === 0) throw new Error('@anywalletkit/wallet-btc: createBtcHistory needs blockbookUrl or blockbookUrls')\n const fetchFn = options.fetch ?? globalThis.fetch\n if (typeof fetchFn !== 'function') throw new Error('@anywalletkit/wallet-btc: no global fetch; pass `fetch` in the options')\n const clients = urls.map((url) => ({ url, client: new BlockbookClient({ url }, fetchFn, options.timeoutMs ?? DEFAULT_TIMEOUT_MS) }))\n\n async function failover<T>(run: (client: BlockbookClient) => Promise<T>): Promise<T> {\n const attempts: string[] = []\n for (const { url, client } of clients) {\n try {\n return await run(client)\n } catch (e) {\n attempts.push(`${url}: ${e instanceof Error ? e.message : String(e)}`)\n }\n }\n throw new HistoryUnavailableError(chain, attempts[attempts.length - 1] ?? 'every Blockbook failed', attempts)\n }\n\n return {\n chain,\n\n async listActivity(address: string, query: HistoryQuery = {}): Promise<BtcHistoryPage> {\n const cursor = query.cursor ? decodeCursor(query.cursor) : undefined\n const pageSize = cursor?.s ?? Math.min(MAX_PAGE_SIZE, Math.max(1, Math.floor(options.pageSize ?? query.limit ?? DEFAULT_LIMIT)))\n const pageNo = cursor?.p ?? 1\n const data: BlockbookAddressPage = await failover((c) => c.getAddressPage(address, pageNo, pageSize))\n let items = (data.transactions ?? []).map((tx) => mapBlockbookTx(chain, address, tx))\n items.sort((a, b) => (b.block === undefined ? 1 : a.block === undefined ? -1 : (b.block > a.block ? 1 : b.block < a.block ? -1 : 0)) || b.time - a.time)\n const totalPages = data.totalPages ?? 1\n let more = pageNo < totalPages\n if (query.since !== undefined) {\n const since = query.since\n if (items.some((t) => t.status === 'confirmed' && t.time < since)) more = false\n items = items.filter((t) => t.status !== 'confirmed' || t.time >= since)\n }\n const page: BtcHistoryPage = { items, page: pageNo, totalPages }\n if (more) page.cursor = encodeCursor({ v: 1, p: pageNo + 1, s: pageSize })\n return page\n },\n\n async getTransaction(hash: string, address: string): Promise<BtcChainTx | undefined> {\n const tx = await failover((c) => c.getTransactionDetails(hash))\n return tx ? mapBlockbookTx(chain, address, tx) : undefined\n },\n\n explorerTxUrl(hash: string): string {\n return btcExplorerTxUrl(hash, network)\n },\n }\n}\n\n"],"mappings":";AAAA,SAAS,gBAAAA,qBAAoB;;;ACA7B,SAAS,oBAAoB;AAEtB,IAAM,qBAAqB;AAGlC,eAAsB,iBAAiB,SAAuB,KAAa,MAA+B,WAAmB,UAAqC;AAChK,QAAM,SAAS,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aAAa,YAAY,QAAQ,SAAS,IAAI;AAClI,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,EAAE,GAAG,MAAM,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG,CAAC;AAAA,EACtE,SAAS,GAAG;AACV,UAAM,IAAI,aAAa,mBAAmB,GAAG,IAAI,EAAE,UAAU,OAAO,EAAE,CAAC;AAAA,EACzE;AACF;AAGO,IAAM,gBAAgB,CAAC,WAA4B,UAAU,OAAO,SAAS,OAAO,WAAW;AAG/F,SAAS,QAAyB,GAAS;AAChD,SAAO,OAAO,OAAO,GAAG,EAAE,UAAU,KAAK,CAAC;AAC5C;AAMO,SAAS,kBAAkB,GAAqB;AACrD,SAAQ,GAAqC,aAAa;AAC5D;AAGO,SAAS,eAAe,GAAqB;AAClD,WAAS,IAAa,GAAG,QAAQ,GAAG,aAAa,SAAS,QAAQ,GAAG,IAAI,EAAE,OAAO,SAAS;AACzF,QAAI,mFAAmF,KAAK,EAAE,OAAO,EAAG,QAAO;AAAA,EACjH;AACA,SAAO;AACT;AAEO,IAAM,WAAW,CAAC,MAA6C,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AACtH,IAAM,UAAU,CAAC,MAA4B,OAAO,MAAM,YAAY,oBAAoB,KAAK,CAAC;AAChG,IAAM,cAAc,CAAC,MAA4B,OAAO,MAAM,YAAY,UAAU,KAAK,CAAC;;;ADpCjG,IAAM,oBAAoB;AA6BnB,SAAS,oBAAoB,cAAgD,SAAyB;AAC3G,QAAM,WAAW,gBAAgB,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,gBAAgB,EAAE;AACzE,QAAM,aAAa,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACrD,QAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACpD,QAAM,UAAU,oBAAI,IAAqB;AACzC,QAAM,QAAQ,CAAC,aACZ,WAAW,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,SAAS,OAAO,CAAC,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,SAAS,CAAC,GAAG,EAAE;AAC5H,QAAM,YAAY,CAAC,OAAoC;AACrD,QAAI,QAAQ,IAAI,GAAG,IAAI,EAAG,QAAO,QAAQ,IAAI,GAAG,IAAI;AACpD,YAAQ,IAAI,GAAG,MAAM,KAAK;AAC1B,UAAM,OAAO,GAAG,OAAO,CAAC;AACxB,UAAM,OAAO,KAAK,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,SAAS,OAAO,CAAC;AAC7E,UAAM,KAAK,KAAK,SAAS,KAAK,KAAK,WAAW,KAAK,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,IAAI,EAAE,IAAI,KAAK,UAAU,KAAK,IAAI,EAAE,IAAI,CAAE,CAAC;AACjJ,YAAQ,IAAI,GAAG,MAAM,EAAE;AACvB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,MAAM,QAAS,YAAW,KAAK,GAAG,OAAO,CAAC,EAAG,KAAI,EAAE,QAAQ,WAAW,IAAI,EAAE,IAAI,EAAG,UAAS,IAAI,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE;AAChI,SAAO,QAAQ,OAAO,CAAC,OAAO,OAAO;AACnC,UAAM,QAAQ,OAAO,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC,CAAC;AACpF,UAAM,SAAS,UAAU,EAAE,IAAI,OAAO,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI;AAC1G,WAAO,SAAS,QAAQ;AAAA,EAC1B,GAAG,EAAE;AACP;AAGO,IAAM,kBAAN,MAAM,iBAAqC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAAyB,UAAwB,WAAW,OAAO,YAAY,oBAAoB;AAC7G,SAAK,OAAO,OAAO,IAAI,QAAQ,QAAQ,EAAE,EAAE,QAAQ,UAAU,EAAE;AAC/D,SAAK,UAAU;AACf,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,UAAyB;AAAA,EAAC;AAAA,EAChC,MAAM,QAAuB;AAAA,EAAC;AAAA,EAE9B,MAAc,IAAO,MAAc,OAA2C;AAC5E,UAAM,MAAM,GAAG,KAAK,IAAI,OAAO,IAAI;AACnC,UAAM,MAAM,MAAM,iBAAiB,KAAK,SAAS,KAAK,QAAW,KAAK,WAAW,KAAK,IAAI;AAC1F,QAAI,CAAC,IAAI,IAAI;AAEX,YAAM,SAAS,cAAc,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE,GAAG,MAAM,GAAG,GAAG,IAAI;AAC9F,YAAM,MAAM,IAAIC,cAAa,6BAA6B,IAAI,MAAM,IAAI,IAAI,UAAU,KAAK,GAAG,IAAI,SAAS,KAAK,MAAM,KAAK,EAAE,IAAI,EAAE,UAAU,KAAK,KAAK,CAAC;AACxJ,YAAM,cAAc,IAAI,MAAM,IAAI,QAAQ,GAAG,IAAI;AAAA,IACnD;AACA,QAAI;AACJ,QAAI;AAAE,aAAO,MAAM,IAAI,KAAK;AAAA,IAAE,SAAS,GAAG;AAAE,YAAM,IAAIA,cAAa,mCAAmC,GAAG,IAAI,EAAE,UAAU,KAAK,MAAM,OAAO,EAAE,CAAC;AAAA,IAAE;AAChJ,QAAI,CAAC,MAAM,IAAI,EAAG,OAAM,IAAIA,cAAa,8CAA8C,GAAG,IAAI,EAAE,UAAU,KAAK,KAAK,CAAC;AACrH,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,SAAiB;AAChC,UAAM,OAAO,MAAM,KAAK,IAAI,eAAe,OAAO,gBAAgB,CAAC,MACjE,SAAS,CAAC,KAAK,YAAY,EAAE,OAAO,KAAK,YAAY,EAAE,kBAAkB,MAAM,EAAE,iBAAiB,UAAa,MAAM,QAAQ,EAAE,YAAY,EAAE;AAC/I,WAAO,EAAE,WAAW,OAAO,KAAK,OAAO,GAAG,aAAa,OAAO,KAAK,kBAAkB,GAAG,qBAAqB,oBAAoB,KAAK,cAAc,OAAO,EAAE;AAAA,EAC/J;AAAA,EAEA,MAAM,YAAY,SAAkC;AAClD,UAAM,OAAO,MAAM,KAAK,IAAI,YAAY,OAAO,IAAI,CAAC,MAClD,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,SAAS,CAAC,KAAK,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,SAAS,YAAY,YAAY,EAAE,KAAK,CAAC,CAAC;AACrI,WAAO,KAAK,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,OAAO,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,UAAU,GAAG,EAAE;AAAA,EACzG;AAAA,EAEA,MAAM,WAAW,SAAiB;AAChC,UAAM,QAAiD,CAAC;AACxD,aAAS,OAAO,KAAK,QAAQ;AAC3B,YAAM,OAAO,MAAM,KAAK,IAAI,eAAe,OAAO,wCAAwC,IAAI,IAAI,CAAC,MACjG,SAAS,CAAC,MAAM,EAAE,iBAAiB,UAAa,MAAM,QAAQ,EAAE,YAAY,EAAE;AAChF,iBAAW,MAAM,KAAK,gBAAgB,CAAC,EAAG,OAAM,KAAK,EAAE,MAAM,GAAG,MAAM,QAAQ,GAAG,YAAY,CAAC;AAC9F,UAAI,SAAS,KAAK,cAAc,GAAI;AAAA,IACtC;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,eAAe,SAAiB,OAAO,GAAG,WAAW,IAAmC;AAC5F,UAAM,OAAO,MAAM,KAAK,IAAI,eAAe,OAAO,qBAAqB,IAAI,aAAa,QAAQ,IAAI,CAAC,MACnG,SAAS,CAAC,MAAM,EAAE,iBAAiB,UAAc,MAAM,QAAQ,EAAE,YAAY,KAAK,EAAE,aAAa,MAAM,iBAAgB,IAAI,EAAG;AAChI,UAAM,MAA4B,EAAE,GAAG,KAAK;AAC5C,eAAW,MAAM,IAAI,gBAAgB,CAAC,EAAG,QAAQ,GAAwB;AACzE,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,sBAAsB,MAAuD;AACjF,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,IAAI,UAAU,IAAI,IAAI,CAAC,MAA+B,iBAAgB,KAAK,CAAC,CAAC;AACnG,YAAM,MAA0B,EAAE,GAAG,GAAG;AACxC,aAAQ,IAAyB;AACjC,aAAO;AAAA,IACT,SAAS,GAAG;AACV,UAAI,aAAaA,iBAAgB,gBAAgB,KAAK,EAAE,OAAO,EAAG,QAAO;AACzE,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,OAAe,KAAK,GAA8B;AAChD,WAAO,SAAS,CAAC,KAAK,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,gBAAgB,YAAY,OAAO,EAAE,kBAAkB;AAAA,EACtH;AAAA,EAEA,MAAM,mBAAmB,MAAgD;AACvE,QAAI;AAAE,aAAO,MAAM,KAAK,IAAI,UAAU,IAAI,IAAI,iBAAgB,IAAI;AAAA,IAAE,SAAS,GAAG;AAC9E,UAAI,aAAaA,iBAAgB,gBAAgB,KAAK,EAAE,OAAO,EAAG,QAAO;AACzE,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,MAA+B;AAClD,UAAM,OAAO,MAAM,KAAK,IAAI,UAAU,IAAI,IAAI,iBAAgB,IAAI;AAClE,QAAI,OAAO,KAAK,QAAQ,YAAY,CAAC,KAAK,IAAK,OAAM,IAAIA,cAAa,eAAe,IAAI,oBAAoB,EAAE,UAAU,KAAK,KAAK,CAAC;AACpI,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAU,OAAgC;AAC9C,UAAM,OAAO,MAAM,KAAK,IAAI,cAAc,KAAK,IAAI,CAAC,MAAuE,SAAS,CAAC,CAAC;AACtI,QAAI,KAAK,MAAO,OAAM,QAAQ,IAAI,MAAM,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAK,MAAM,WAAW,kBAAkB,CAAC;AAC/H,QAAI,CAAC,KAAK,OAAQ,OAAM,IAAIA,cAAa,8BAA8B,EAAE,UAAU,KAAK,KAAK,CAAC;AAC9F,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,YAAY,QAAiC;AACjD,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,IAAI,mBAAmB,MAAM,IAAI,CAAC,MAAyC,OAAO,MAAM,YAAY,SAAS,CAAC,CAAC;AACvI,YAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,MAAM;AACjE,UAAI,OAAO,EAAG,QAAO,KAAK,IAAI,OAAO,KAAS,CAAC;AAAA,IACjD,QAAQ;AAAA,IAAqB;AAC7B,UAAM,MAAM,MAAM,iBAAiB,KAAK,SAAS,GAAG,iBAAiB,4BAA4B,QAAW,KAAK,WAAW,iBAAiB;AAC7I,QAAI,CAAC,IAAI,GAAI,OAAM,IAAIA,cAAa,iCAAiC,EAAE,UAAU,kBAAkB,CAAC;AACpG,UAAM,IAAK,MAAM,IAAI,KAAK;AAC1B,UAAM,WAAW,UAAU,IAAI,EAAE,aAAa,UAAU,IAAI,EAAE,cAAc,UAAU,IAAI,EAAE,UAAU,EAAE;AACxG,QAAI,CAAC,YAAY,YAAY,EAAG,OAAM,IAAIA,cAAa,iCAAiC,EAAE,UAAU,kBAAkB,CAAC;AACvH,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,iBAAkC;AACtC,UAAM,OAAO,MAAM,KAAK,IAAI,OAAO,CAAC,MAAoD,SAAS,CAAC,CAAC;AACnG,UAAM,IAAI,KAAK,WAAW;AAC1B,QAAI,OAAO,MAAM,YAAY,KAAK,EAAG,OAAM,IAAIA,cAAa,2CAA2C,EAAE,UAAU,KAAK,KAAK,CAAC;AAC9H,WAAO;AAAA,EACT;AACF;;;AELO,IAAM,gBAA4C;AAAA,EACvD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACX;;;ACnHO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY,OAAe,QAAgB,WAA8B,CAAC,GAAG;AAC3E,UAAM,2BAA2B,KAAK,KAAK,MAAM,EAAE;AACnD,SAAK,OAAO;AACZ,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AAatB,IAAM,MAAM;AAEZ,SAAS,UAAU,GAAmB;AACpC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,GAAG;AACpC,UAAM,IAAI,CAAC,EAAE,WAAW,CAAC,GAAG,EAAE,WAAW,IAAI,CAAC,GAAG,EAAE,WAAW,IAAI,CAAC,CAAC;AACpE,UAAM,IAAK,EAAE,CAAC,KAAM,MAAQ,EAAE,CAAC,KAAM,MAAM,KAAM,EAAE,CAAC,KAAM;AAC1D,WAAO,IAAK,KAAK,KAAM,EAAE,IAAK,IAAK,KAAK,KAAM,EAAE,KAAM,OAAO,MAAM,EAAE,CAAC,CAAC,IAAI,MAAM,IAAK,KAAK,IAAK,EAAE,MAAO,OAAO,MAAM,EAAE,CAAC,CAAC,IAAI,MAAM,IAAI,IAAI,EAAE;AAAA,EAChJ;AACA,SAAO;AACT;AAEA,SAAS,UAAU,GAAmB;AACpC,QAAM,QAAQ,EAAE,QAAQ,mBAAmB,EAAE;AAC7C,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAO,IAAI,IAAI,MAAM,SAAS,IAAI,QAAQ,MAAM,IAAI,CAAC,CAAE,IAAI,EAAG;AAC1F,QAAI,EAAE,CAAC,IAAK,KAAK,EAAE,CAAC,IAAK,EAAG,OAAM,IAAI,MAAM,YAAY;AACxD,UAAM,IAAK,EAAE,CAAC,KAAM,KAAO,EAAE,CAAC,KAAM,MAAQ,EAAE,CAAC,IAAK,IAAI,IAAI,EAAE,CAAC,MAAO,KAAM,EAAE,CAAC,IAAK,IAAI,IAAI,EAAE,CAAC;AAC/F,WAAO,OAAO,aAAc,KAAK,KAAM,GAAI;AAC3C,QAAI,EAAE,CAAC,KAAM,EAAG,QAAO,OAAO,aAAc,KAAK,IAAK,GAAI;AAC1D,QAAI,EAAE,CAAC,KAAM,EAAG,QAAO,OAAO,aAAa,IAAI,GAAI;AAAA,EACrD;AACA,SAAO;AACT;AAEO,SAAS,aAAa,GAA6B;AACxD,SAAO,UAAU,KAAK,UAAU,CAAC,CAAC;AACpC;AAEO,SAAS,aAAa,GAA6B;AACxD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,UAAU,CAAC,CAAC;AAAA,EAClC,QAAQ;AACN,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,QAAM,IAAI;AACV,MAAI,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,OAAO,UAAU,EAAE,CAAC,KAAM,EAAE,IAAe,KAAK,CAAC,OAAO,UAAU,EAAE,CAAC,KAAM,EAAE,IAAe,EAAG,OAAM,IAAI,MAAM,oDAAoD;AAC3L,SAAO,EAAE,GAAG,GAAG,GAAG,EAAE,GAAa,GAAG,EAAE,EAAY;AACpD;AAIA,IAAM,MAAM,CAAC,MAAwB,OAAO,MAAM,YAAY,UAAU,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,OAAO,MAAM,WAAW,OAAO,KAAK,MAAM,CAAC,CAAC,IAAI;AAEvI,SAAS,eAAe,OAAe,SAAiB,IAAoC;AACjG,QAAM,OAAO,CAAC,MAAqD,EAAE,cAAc,UAAU,EAAE,aAAa,CAAC,GAAG,SAAS,OAAO;AAChI,MAAI,QAAQ,IAAI,SAAS,IAAI,eAAe;AAC5C,aAAW,OAAO,GAAG,OAAO,CAAC,EAAG,KAAI,KAAK,GAAG,EAAG,UAAS,IAAI,IAAI,KAAK;AACrE,aAAW,QAAQ,GAAG,QAAQ,CAAC,GAAG;AAChC,QAAI,KAAK,IAAI,EAAG,WAAU,IAAI,KAAK,KAAK;AAAA,QACnC,iBAAgB,IAAI,KAAK,KAAK;AAAA,EACrC;AACA,QAAM,cAAc,GAAG,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS,EAAE,YAAY,CAAC,CAAC,GAAG,YAAY,CAAC;AACvG,QAAM,oBAAoB,GAAG,QAAQ,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,GAAG,YAAY,CAAC;AAC1H,QAAM,YAA6B,CAAC;AACpC,MAAI,MAAc,IAAmB;AACrC,MAAI,QAAQ,IAAI;AAEd,WAAO;AACP,SAAK,oBAAoB;AACzB,kBAAc,mBAAmB,eAAe;AAChD,QAAI,cAAc,GAAI,WAAU,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,IAAI,QAAQ,YAAY,CAAC;AAAA,EAC9F,OAAO;AAEL,WAAO,cAAc;AACrB,SAAK;AACL,kBAAc;AACd,QAAI,cAAc,GAAI,WAAU,KAAK,EAAE,OAAO,MAAM,MAAM,IAAI,SAAS,QAAQ,YAAY,CAAC;AAAA,EAC9F;AACA,QAAM,YAAY,GAAG,gBAAgB,KAAK,GAAG,cAAc;AAC3D,QAAM,MAAkB;AAAA,IACtB;AAAA,IAAO,MAAM,GAAG;AAAA,IAAM,MAAM,GAAG,aAAa;AAAA,IAAG,QAAQ,YAAY,cAAc;AAAA,IAAW;AAAA,IAAS;AAAA,IAAM;AAAA,IAAI;AAAA,IAAa;AAAA,IAC5H,KAAK,EAAE,IAAI,OAAO,OAAO;AAAA,EAC3B;AACA,MAAI,GAAG,SAAS,OAAW,KAAI,MAAM,IAAI,GAAG,IAAI;AAChD,MAAI,UAAW,KAAI,QAAQ,OAAO,GAAG,WAAW;AAChD,SAAO;AACT;AAKO,SAAS,iBAAiB,MAAc,UAAsB,WAAmB;AACtF,SAAO,yBAAyB,YAAY,YAAY,aAAa,EAAE,MAAM,IAAI;AACnF;AAQO,SAAS,iBAAiB,UAA6B,CAAC,GAAqB;AAClF,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,QAAQ,cAAc,OAAO;AACnC,QAAM,OAAO,CAAC,GAAI,QAAQ,iBAAiB,CAAC,GAAI,GAAI,QAAQ,eAAe,CAAC,QAAQ,YAAY,IAAI,CAAC,CAAE;AACvG,MAAI,KAAK,WAAW,EAAG,OAAM,IAAI,MAAM,gFAAgF;AACvH,QAAM,UAAU,QAAQ,SAAS,WAAW;AAC5C,MAAI,OAAO,YAAY,WAAY,OAAM,IAAI,MAAM,wEAAwE;AAC3H,QAAM,UAAU,KAAK,IAAI,CAAC,SAAS,EAAE,KAAK,QAAQ,IAAI,gBAAgB,EAAE,IAAI,GAAG,SAAS,QAAQ,aAAa,kBAAkB,EAAE,EAAE;AAEnI,iBAAe,SAAY,KAA0D;AACnF,UAAM,WAAqB,CAAC;AAC5B,eAAW,EAAE,KAAK,OAAO,KAAK,SAAS;AACrC,UAAI;AACF,eAAO,MAAM,IAAI,MAAM;AAAA,MACzB,SAAS,GAAG;AACV,iBAAS,KAAK,GAAG,GAAG,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,MACvE;AAAA,IACF;AACA,UAAM,IAAI,wBAAwB,OAAO,SAAS,SAAS,SAAS,CAAC,KAAK,0BAA0B,QAAQ;AAAA,EAC9G;AAEA,SAAO;AAAA,IACL;AAAA,IAEA,MAAM,aAAa,SAAiB,QAAsB,CAAC,GAA4B;AACrF,YAAM,SAAS,MAAM,SAAS,aAAa,MAAM,MAAM,IAAI;AAC3D,YAAM,WAAW,QAAQ,KAAK,KAAK,IAAI,eAAe,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,YAAY,MAAM,SAAS,aAAa,CAAC,CAAC;AAC/H,YAAM,SAAS,QAAQ,KAAK;AAC5B,YAAM,OAA6B,MAAM,SAAS,CAAC,MAAM,EAAE,eAAe,SAAS,QAAQ,QAAQ,CAAC;AACpG,UAAI,SAAS,KAAK,gBAAgB,CAAC,GAAG,IAAI,CAAC,OAAO,eAAe,OAAO,SAAS,EAAE,CAAC;AACpF,YAAM,KAAK,CAAC,GAAG,OAAO,EAAE,UAAU,SAAY,IAAI,EAAE,UAAU,SAAY,KAAM,EAAE,QAAQ,EAAE,QAAQ,IAAI,EAAE,QAAQ,EAAE,QAAQ,KAAK,MAAO,EAAE,OAAO,EAAE,IAAI;AACvJ,YAAM,aAAa,KAAK,cAAc;AACtC,UAAI,OAAO,SAAS;AACpB,UAAI,MAAM,UAAU,QAAW;AAC7B,cAAM,QAAQ,MAAM;AACpB,YAAI,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,eAAe,EAAE,OAAO,KAAK,EAAG,QAAO;AAC1E,gBAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,eAAe,EAAE,QAAQ,KAAK;AAAA,MACzE;AACA,YAAM,OAAuB,EAAE,OAAO,MAAM,QAAQ,WAAW;AAC/D,UAAI,KAAM,MAAK,SAAS,aAAa,EAAE,GAAG,GAAG,GAAG,SAAS,GAAG,GAAG,SAAS,CAAC;AACzE,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,eAAe,MAAc,SAAkD;AACnF,YAAM,KAAK,MAAM,SAAS,CAAC,MAAM,EAAE,sBAAsB,IAAI,CAAC;AAC9D,aAAO,KAAK,eAAe,OAAO,SAAS,EAAE,IAAI;AAAA,IACnD;AAAA,IAEA,cAAc,MAAsB;AAClC,aAAO,iBAAiB,MAAM,OAAO;AAAA,IACvC;AAAA,EACF;AACF;","names":["NetworkError","NetworkError"]}
|
package/dist/history.cjs
CHANGED
|
@@ -43,6 +43,10 @@ async function fetchWithTimeout(fetchFn, url, init, timeoutMs, provider) {
|
|
|
43
43
|
throw new import_core.NetworkError(`Request failed: ${url}`, { provider, cause: e });
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
var refusedStatus = (status) => status >= 400 && status < 500 && status !== 408;
|
|
47
|
+
function refusal(e) {
|
|
48
|
+
return Object.assign(e, { answered: true });
|
|
49
|
+
}
|
|
46
50
|
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
47
51
|
var isIntString = (v) => typeof v === "string" && /^-?\d+$/.test(v);
|
|
48
52
|
|
|
@@ -87,7 +91,11 @@ var BlockbookClient = class _BlockbookClient {
|
|
|
87
91
|
async get(path, check) {
|
|
88
92
|
const url = `${this.base}/api${path}`;
|
|
89
93
|
const res = await fetchWithTimeout(this.fetchFn, url, void 0, this.timeoutMs, this.base);
|
|
90
|
-
if (!res.ok)
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
const reason = refusedStatus(res.status) ? (await res.text().catch(() => "")).slice(0, 200) : "";
|
|
96
|
+
const err = new import_core2.NetworkError(`Blockbook request failed: ${res.status} ${res.statusText} (${url})${reason ? `: ${reason}` : ""}`, { provider: this.base });
|
|
97
|
+
throw refusedStatus(res.status) ? refusal(err) : err;
|
|
98
|
+
}
|
|
91
99
|
let body;
|
|
92
100
|
try {
|
|
93
101
|
body = await res.json();
|
|
@@ -151,7 +159,7 @@ var BlockbookClient = class _BlockbookClient {
|
|
|
151
159
|
}
|
|
152
160
|
async broadcast(rawTx) {
|
|
153
161
|
const data = await this.get(`/v2/sendtx/${rawTx}`, (v) => isRecord(v));
|
|
154
|
-
if (data.error) throw new Error(typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed");
|
|
162
|
+
if (data.error) throw refusal(new Error(typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed"));
|
|
155
163
|
if (!data.result) throw new import_core2.NetworkError("Blockbook returned no txid", { provider: this.base });
|
|
156
164
|
return data.result;
|
|
157
165
|
}
|
package/dist/history.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/history.ts","../src/blockbook.ts","../src/http.ts","../src/types.ts"],"sourcesContent":["/**\n * Activity history for Bitcoin: a `HistorySource` over Blockbook's\n * `/api/v2/address/{a}?details=txs` pages (Trezor's indexer, QuickNode's\n * Blockbook add-on, any self-hosted instance), reusing the package's\n * `BlockbookClient` and failing over across several instances.\n *\n * One transaction = one native transfer in the owner's direction: a send is\n * what left to non-own outputs (fee separate), a receive is what landed on the\n * address. The cursor is the Blockbook page number. Explorer: mempool.space.\n */\nimport type { ChainTransfer, ChainTx, HistoryPage, HistoryQuery, HistorySource } from '@anywalletkit/core'\nimport { BlockbookClient, type BlockbookAddressPage, type BlockbookTxDetails } from './blockbook.js'\nimport { DEFAULT_TIMEOUT_MS } from './http.js'\nimport { CAIP2_GENESIS, type BtcNetwork } from './types.js'\n\n// ── Public types ─────────────────────────────────────────────────────────────\n\n/** Options for {@link createBtcHistory}. */\nexport interface BtcHistoryOptions {\n /** One Blockbook root (`https://btc1.trezor.io`). */\n blockbookUrl?: string\n /** Several roots, tried in order (failover). */\n blockbookUrls?: string[]\n /** `bitcoin` (default), `testnet`, `regtest`. */\n network?: BtcNetwork\n fetch?: typeof fetch\n /** Per request, ms. Default 15 000. */\n timeoutMs?: number\n /** Rows per Blockbook page (its max is 1000). Default: the query limit, capped at 100. */\n pageSize?: number\n}\n\n/** What a Bitcoin `ChainTx` was built from, and the address's own totals. */\nexport interface BtcHistoryRaw {\n /** The Blockbook transaction (vin / vout with addresses and values; `hex` dropped). */\n tx: BlockbookTxDetails\n /** Sum of inputs spent by the address, sats. */\n ownIn: bigint\n /** Sum of outputs paid to the address, sats. */\n ownOut: bigint\n}\n\n/** A `ChainTx` with the Blockbook transaction it was built from. */\nexport interface BtcChainTx extends ChainTx {\n raw: BtcHistoryRaw\n}\n\n/** One page of Bitcoin history. */\nexport interface BtcHistoryPage extends HistoryPage {\n items: BtcChainTx[]\n /** Blockbook page served / total pages. */\n page?: number\n totalPages?: number\n}\n\n/** Bitcoin history from a Blockbook instance. Build it with {@link createBtcHistory}. */\nexport interface BtcHistorySource extends HistorySource {\n listActivity(address: string, query?: HistoryQuery): Promise<BtcHistoryPage>\n getTransaction(hash: string, address: string): Promise<BtcChainTx | undefined>\n}\n\n/** Every configured Blockbook endpoint failed (or none was configured). */\nexport class HistoryUnavailableError extends Error {\n readonly chain: string\n readonly reason: string\n readonly attempts: readonly string[]\n constructor(chain: string, reason: string, attempts: readonly string[] = []) {\n super(`History unavailable for ${chain}: ${reason}`)\n this.name = 'HistoryUnavailableError'\n this.chain = chain\n this.reason = reason\n this.attempts = attempts\n }\n}\n\nconst DEFAULT_LIMIT = 25\nconst MAX_PAGE_SIZE = 100\n\n// ── Cursor: the next Blockbook page, base64 JSON ─────────────────────────────\n\n/** @internal Decoded form of a Bitcoin history cursor. */\nexport interface BtcHistoryCursor {\n v: 1\n /** Next page (1-based). */\n p: number\n /** Page size the pagination started with (changing it mid-way would skip or repeat rows). */\n s: number\n}\n\nconst B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\n\nfunction b64encode(s: string): string {\n let out = ''\n for (let i = 0; i < s.length; i += 3) {\n const c = [s.charCodeAt(i), s.charCodeAt(i + 1), s.charCodeAt(i + 2)]\n const n = (c[0]! << 16) | ((c[1]! || 0) << 8) | (c[2]! || 0)\n out += B64[(n >> 18) & 63]! + B64[(n >> 12) & 63]! + (Number.isNaN(c[1]) ? '=' : B64[(n >> 6) & 63]!) + (Number.isNaN(c[2]) ? '=' : B64[n & 63]!)\n }\n return out\n}\n\nfunction b64decode(s: string): string {\n const clean = s.replace(/[^A-Za-z0-9+/]/g, '')\n let out = ''\n for (let i = 0; i < clean.length; i += 4) {\n const c = [0, 1, 2, 3].map((k) => (i + k < clean.length ? B64.indexOf(clean[i + k]!) : -1))\n if (c[0]! < 0 || c[1]! < 0) throw new Error('bad base64')\n const n = (c[0]! << 18) | (c[1]! << 12) | ((c[2]! < 0 ? 0 : c[2]!) << 6) | (c[3]! < 0 ? 0 : c[3]!)\n out += String.fromCharCode((n >> 16) & 0xff)\n if (c[2]! >= 0) out += String.fromCharCode((n >> 8) & 0xff)\n if (c[3]! >= 0) out += String.fromCharCode(n & 0xff)\n }\n return out\n}\n\nexport function encodeCursor(c: BtcHistoryCursor): string {\n return b64encode(JSON.stringify(c))\n}\n\nexport function decodeCursor(s: string): BtcHistoryCursor {\n let parsed: unknown\n try {\n parsed = JSON.parse(b64decode(s))\n } catch {\n throw new Error('@anywalletkit/wallet-btc: malformed history cursor')\n }\n const c = parsed as Partial<BtcHistoryCursor> | null\n if (!c || c.v !== 1 || !Number.isInteger(c.p) || (c.p as number) < 1 || !Number.isInteger(c.s) || (c.s as number) < 1) throw new Error('@anywalletkit/wallet-btc: malformed history cursor')\n return { v: 1, p: c.p as number, s: c.s as number }\n}\n\n// ── Mapping ──────────────────────────────────────────────────────────────────\n\nconst big = (v: unknown): bigint => (typeof v === 'string' && /^-?\\d+$/.test(v) ? BigInt(v) : typeof v === 'number' ? BigInt(Math.trunc(v)) : 0n)\n\nexport function mapBlockbookTx(chain: string, address: string, tx: BlockbookTxDetails): BtcChainTx {\n const owns = (e: { isAddress?: boolean; addresses?: string[] }) => e.isAddress !== false && (e.addresses ?? []).includes(address)\n let ownIn = 0n, ownOut = 0n, sentToOthers = 0n\n for (const vin of tx.vin ?? []) if (owns(vin)) ownIn += big(vin.value)\n for (const vout of tx.vout ?? []) {\n if (owns(vout)) ownOut += big(vout.value)\n else sentToOthers += big(vout.value)\n }\n const firstInput = (tx.vin ?? []).find((v) => v.isAddress !== false && v.addresses?.[0])?.addresses?.[0]\n const firstOtherOutput = (tx.vout ?? []).find((v) => v.isAddress !== false && !owns(v) && v.addresses?.[0])?.addresses?.[0]\n const transfers: ChainTransfer[] = []\n let from: string, to: string | null, nativeValue: bigint\n if (ownIn > 0n) {\n // We spent inputs: a send (or a consolidation when every output is ours).\n from = address\n to = firstOtherOutput ?? address\n nativeValue = firstOtherOutput ? sentToOthers : ownOut\n if (nativeValue > 0n) transfers.push({ token: null, from: address, to, amount: nativeValue })\n } else {\n // Only outputs are ours: a receive (coinbase has no input address).\n from = firstInput ?? 'coinbase'\n to = address\n nativeValue = ownOut\n if (nativeValue > 0n) transfers.push({ token: null, from, to: address, amount: nativeValue })\n }\n const confirmed = tx.confirmations > 0 && tx.blockHeight > 0\n const out: BtcChainTx = {\n chain, hash: tx.txid, time: tx.blockTime ?? 0, status: confirmed ? 'confirmed' : 'pending', address, from, to, nativeValue, transfers,\n raw: { tx, ownIn, ownOut },\n }\n if (tx.fees !== undefined) out.fee = big(tx.fees)\n if (confirmed) out.block = BigInt(tx.blockHeight)\n return out\n}\n\n// ── Source ───────────────────────────────────────────────────────────────────\n\n/** mempool.space link for a transaction. */\nexport function btcExplorerTxUrl(hash: string, network: BtcNetwork = 'bitcoin'): string {\n return `https://mempool.space/${network === 'testnet' ? 'testnet/' : ''}tx/${hash}`\n}\n\n/**\n * A history source for Bitcoin addresses backed by Blockbook (Trezor's public instances,\n * QuickNode's add-on, or your own).\n *\n * @throws Error when no Blockbook URL is given.\n */\nexport function createBtcHistory(options: BtcHistoryOptions = {}): BtcHistorySource {\n const network = options.network ?? 'bitcoin'\n const chain = CAIP2_GENESIS[network]\n const urls = [...(options.blockbookUrls ?? []), ...(options.blockbookUrl ? [options.blockbookUrl] : [])]\n if (urls.length === 0) throw new Error('@anywalletkit/wallet-btc: createBtcHistory needs blockbookUrl or blockbookUrls')\n const fetchFn = options.fetch ?? globalThis.fetch\n if (typeof fetchFn !== 'function') throw new Error('@anywalletkit/wallet-btc: no global fetch; pass `fetch` in the options')\n const clients = urls.map((url) => ({ url, client: new BlockbookClient({ url }, fetchFn, options.timeoutMs ?? DEFAULT_TIMEOUT_MS) }))\n\n async function failover<T>(run: (client: BlockbookClient) => Promise<T>): Promise<T> {\n const attempts: string[] = []\n for (const { url, client } of clients) {\n try {\n return await run(client)\n } catch (e) {\n attempts.push(`${url}: ${e instanceof Error ? e.message : String(e)}`)\n }\n }\n throw new HistoryUnavailableError(chain, attempts[attempts.length - 1] ?? 'every Blockbook failed', attempts)\n }\n\n return {\n chain,\n\n async listActivity(address: string, query: HistoryQuery = {}): Promise<BtcHistoryPage> {\n const cursor = query.cursor ? decodeCursor(query.cursor) : undefined\n const pageSize = cursor?.s ?? Math.min(MAX_PAGE_SIZE, Math.max(1, Math.floor(options.pageSize ?? query.limit ?? DEFAULT_LIMIT)))\n const pageNo = cursor?.p ?? 1\n const data: BlockbookAddressPage = await failover((c) => c.getAddressPage(address, pageNo, pageSize))\n let items = (data.transactions ?? []).map((tx) => mapBlockbookTx(chain, address, tx))\n items.sort((a, b) => (b.block === undefined ? 1 : a.block === undefined ? -1 : (b.block > a.block ? 1 : b.block < a.block ? -1 : 0)) || b.time - a.time)\n const totalPages = data.totalPages ?? 1\n let more = pageNo < totalPages\n if (query.since !== undefined) {\n const since = query.since\n if (items.some((t) => t.status === 'confirmed' && t.time < since)) more = false\n items = items.filter((t) => t.status !== 'confirmed' || t.time >= since)\n }\n const page: BtcHistoryPage = { items, page: pageNo, totalPages }\n if (more) page.cursor = encodeCursor({ v: 1, p: pageNo + 1, s: pageSize })\n return page\n },\n\n async getTransaction(hash: string, address: string): Promise<BtcChainTx | undefined> {\n const tx = await failover((c) => c.getTransactionDetails(hash))\n return tx ? mapBlockbookTx(chain, address, tx) : undefined\n },\n\n explorerTxUrl(hash: string): string {\n return btcExplorerTxUrl(hash, network)\n },\n }\n}\n\n","import { NetworkError } from '@anywalletkit/core'\nimport { DEFAULT_TIMEOUT_MS, fetchWithTimeout, isIntString, isRecord } from './http.js'\nimport type { BlockbookTx, BtcClient, Utxo } from './types.js'\n\nconst MEMPOOL_SPACE_URL = 'https://mempool.space'\n\nexport interface BlockbookVin { txid?: string; vout?: number; n?: number; isAddress?: boolean; isOwn?: boolean; addresses?: string[]; value?: string }\nexport interface BlockbookVout { n: number; isAddress?: boolean; isOwn?: boolean; addresses?: string[]; value?: string; spent?: boolean }\ninterface BlockbookAddressTx { txid: string; blockHeight: number; vin?: BlockbookVin[]; vout?: BlockbookVout[] }\n/** A Blockbook transaction with its inputs and outputs (`/api/v2/tx/{txid}`, or one row of `?details=txs`). */\nexport interface BlockbookTxDetails extends BlockbookTx {\n vin?: BlockbookVin[]\n vout?: BlockbookVout[]\n value?: string\n valueIn?: string\n}\n/** One `/api/v2/address/{a}?details=txs` page. */\nexport interface BlockbookAddressPage {\n page?: number\n totalPages?: number\n itemsOnPage?: number\n address?: string\n balance?: string\n unconfirmedBalance?: string\n txs?: number\n transactions?: BlockbookTxDetails[]\n}\n\n/**\n * Pending outgoing value the way Bitcoin Core's wallet counts it: a pending tx\n * is trusted only if every input is ours and confirmed or chained from another\n * trusted tx; only then its change counts back.\n */\nexport function unconfirmedOutgoing(transactions: BlockbookAddressTx[] | undefined, address: string): bigint {\n const pending = (transactions ?? []).filter((tx) => tx.blockHeight === -1)\n const pendingIds = new Set(pending.map((t) => t.txid))\n const byId = new Map(pending.map((t) => [t.txid, t]))\n const trusted = new Map<string, boolean>()\n const owned = (entries: Array<BlockbookVin | BlockbookVout> | undefined) =>\n (entries ?? []).filter((e) => e.isAddress && e.addresses?.includes(address)).reduce((s, e) => s + BigInt(e.value ?? 0), 0n)\n const isTrusted = (tx: BlockbookAddressTx): boolean => {\n if (trusted.has(tx.txid)) return trusted.get(tx.txid)!\n trusted.set(tx.txid, false)\n const vins = tx.vin ?? []\n const ours = vins.filter((v) => v.isAddress && v.addresses?.includes(address))\n const ok = vins.length > 0 && ours.length === vins.length && ours.every((v) => !v.txid || !pendingIds.has(v.txid) || isTrusted(byId.get(v.txid)!))\n trusted.set(tx.txid, ok)\n return ok\n }\n const consumed = new Set<string>()\n for (const tx of pending) for (const v of tx.vin ?? []) if (v.txid && pendingIds.has(v.txid)) consumed.add(`${v.txid}:${v.vout}`)\n return pending.reduce((total, tx) => {\n const spent = owned((tx.vin ?? []).filter((v) => !v.txid || !pendingIds.has(v.txid)))\n const change = isTrusted(tx) ? owned((tx.vout ?? []).filter((o) => !consumed.has(`${tx.txid}:${o.n}`))) : 0n\n return total + (spent - change)\n }, 0n)\n}\n\n/** Blockbook REST transport (Trezor's indexer; QuickNode's Blockbook add-on speaks the same API). */\nexport class BlockbookClient implements BtcClient {\n private readonly base: string\n private readonly fetchFn: typeof fetch\n private readonly timeoutMs: number\n\n constructor(config: { url: string }, fetchFn: typeof fetch = globalThis.fetch, timeoutMs = DEFAULT_TIMEOUT_MS) {\n this.base = config.url.replace(/\\/+$/, '').replace(/\\/api$/, '')\n this.fetchFn = fetchFn\n this.timeoutMs = timeoutMs\n }\n\n async connect(): Promise<void> {}\n async close(): Promise<void> {}\n\n private async get<T>(path: string, check: (v: unknown) => v is T): Promise<T> {\n const url = `${this.base}/api${path}`\n const res = await fetchWithTimeout(this.fetchFn, url, undefined, this.timeoutMs, this.base)\n if (!res.ok) throw new NetworkError(`Blockbook request failed: ${res.status} ${res.statusText} (${url})`, { provider: this.base })\n let body: unknown\n try { body = await res.json() } catch (e) { throw new NetworkError(`Blockbook returned non-JSON for ${url}`, { provider: this.base, cause: e }) }\n if (!check(body)) throw new NetworkError(`Blockbook returned an unexpected shape for ${url}`, { provider: this.base })\n return body\n }\n\n async getBalance(address: string) {\n const data = await this.get(`/v2/address/${address}?details=txs`, (v): v is { balance: string; unconfirmedBalance: string; transactions?: BlockbookAddressTx[] } =>\n isRecord(v) && isIntString(v.balance) && isIntString(v.unconfirmedBalance) && (v.transactions === undefined || Array.isArray(v.transactions)))\n return { confirmed: BigInt(data.balance), unconfirmed: BigInt(data.unconfirmedBalance), unconfirmedOutgoing: unconfirmedOutgoing(data.transactions, address) }\n }\n\n async listUnspent(address: string): Promise<Utxo[]> {\n const data = await this.get(`/v2/utxo/${address}`, (v): v is Array<{ txid: string; vout: number; value: string; height?: number }> =>\n Array.isArray(v) && v.every((u) => isRecord(u) && typeof u.txid === 'string' && typeof u.vout === 'number' && isIntString(u.value)))\n return data.map((u) => ({ txid: u.txid, vout: u.vout, value: BigInt(u.value), height: u.height ?? -1 }))\n }\n\n async getHistory(address: string) {\n const items: Array<{ txid: string; height: number }> = []\n for (let page = 1; ; page++) {\n const data = await this.get(`/v2/address/${address}?details=txslight&pageSize=1000&page=${page}`, (v): v is { transactions?: Array<{ txid: string; blockHeight: number }>; totalPages?: number } =>\n isRecord(v) && (v.transactions === undefined || Array.isArray(v.transactions)))\n for (const tx of data.transactions ?? []) items.push({ txid: tx.txid, height: tx.blockHeight })\n if (page >= (data.totalPages ?? 1)) break\n }\n return items\n }\n\n /** One page of the address's transactions, newest first, with inputs / outputs (what the activity history reads). */\n async getAddressPage(address: string, page = 1, pageSize = 25): Promise<BlockbookAddressPage> {\n const data = await this.get(`/v2/address/${address}?details=txs&page=${page}&pageSize=${pageSize}`, (v): v is BlockbookAddressPage =>\n isRecord(v) && (v.transactions === undefined || (Array.isArray(v.transactions) && v.transactions.every(BlockbookClient.isTx))))\n const out: BlockbookAddressPage = { ...data }\n for (const tx of out.transactions ?? []) delete (tx as { hex?: string }).hex\n return out\n }\n\n /** A transaction with its inputs / outputs; undefined when Blockbook does not know it. */\n async getTransactionDetails(txid: string): Promise<BlockbookTxDetails | undefined> {\n try {\n const tx = await this.get(`/v2/tx/${txid}`, (v): v is BlockbookTxDetails => BlockbookClient.isTx(v))\n const out: BlockbookTxDetails = { ...tx }\n delete (out as { hex?: string }).hex\n return out\n } catch (e) {\n if (e instanceof NetworkError && /\\b(400|404)\\b/.test(e.message)) return undefined\n throw e\n }\n }\n\n private static isTx(v: unknown): v is BlockbookTx {\n return isRecord(v) && typeof v.txid === 'string' && typeof v.blockHeight === 'number' && typeof v.confirmations === 'number'\n }\n\n async getTransactionInfo(txid: string): Promise<BlockbookTx | undefined> {\n try { return await this.get(`/v2/tx/${txid}`, BlockbookClient.isTx) } catch (e) {\n if (e instanceof NetworkError && /\\b(400|404)\\b/.test(e.message)) return undefined\n throw e\n }\n }\n\n async getTransaction(txid: string): Promise<string> {\n const data = await this.get(`/v2/tx/${txid}`, BlockbookClient.isTx)\n if (typeof data.hex !== 'string' || !data.hex) throw new NetworkError(`Transaction ${txid} has no hex data`, { provider: this.base })\n return data.hex\n }\n\n async broadcast(rawTx: string): Promise<string> {\n const data = await this.get(`/v2/sendtx/${rawTx}`, (v): v is { result?: string; error?: { message?: string } | string } => isRecord(v))\n if (data.error) throw new Error(typeof data.error === 'string' ? data.error : data.error.message ?? 'broadcast failed')\n if (!data.result) throw new NetworkError('Blockbook returned no txid', { provider: this.base })\n return data.result\n }\n\n /** sat/vB. Blockbook reports BTC/kB (×1e5 → sat/vB); falls back to mempool.space when it has no estimate. */\n async estimateFee(blocks: number): Promise<number> {\n try {\n const data = await this.get(`/v1/estimatefee/${blocks}`, (v): v is { result?: string } | string => typeof v === 'string' || isRecord(v))\n const rate = Number(typeof data === 'string' ? data : data.result)\n if (rate > 0) return Math.max(rate * 100_000, 1)\n } catch { /* fall through */ }\n const res = await fetchWithTimeout(this.fetchFn, `${MEMPOOL_SPACE_URL}/api/v1/fees/recommended`, undefined, this.timeoutMs, MEMPOOL_SPACE_URL)\n if (!res.ok) throw new NetworkError('Fee estimation request failed', { provider: MEMPOOL_SPACE_URL })\n const d = (await res.json()) as { fastestFee: number; halfHourFee: number; hourFee: number; economyFee: number }\n const satPerVb = blocks <= 1 ? d.fastestFee : blocks <= 3 ? d.halfHourFee : blocks <= 6 ? d.hourFee : d.economyFee\n if (!satPerVb || satPerVb <= 0) throw new NetworkError('Fee estimation is unavailable', { provider: MEMPOOL_SPACE_URL })\n return satPerVb\n }\n\n async getBlockHeight(): Promise<number> {\n const data = await this.get('/v2', (v): v is { blockbook?: { bestHeight?: number } } => isRecord(v))\n const h = data.blockbook?.bestHeight\n if (typeof h !== 'number' || h <= 0) throw new NetworkError('Blockbook did not report a block height', { provider: this.base })\n return h\n }\n}\n","import { NetworkError } from '@anywalletkit/core'\n\nexport const DEFAULT_TIMEOUT_MS = 15_000\n\n/** fetch with a per-request timeout when the runtime has `AbortSignal.timeout` (Hermes needs runtime-hermes for it). */\nexport async function fetchWithTimeout(fetchFn: typeof fetch, url: string, init: RequestInit | undefined, timeoutMs: number, provider: string): Promise<Response> {\n const signal = typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(timeoutMs) : undefined\n try {\n return await fetchFn(url, { ...init, ...(signal ? { signal } : {}) })\n } catch (e) {\n throw new NetworkError(`Request failed: ${url}`, { provider, cause: e })\n }\n}\n\nexport const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nexport const isHex64 = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-fA-F]{64}$/.test(v)\nexport const isIntString = (v: unknown): v is string => typeof v === 'string' && /^-?\\d+$/.test(v)\n","/** Bitcoin mainnet, testnet or regtest. */\nexport type BtcNetwork = 'bitcoin' | 'testnet' | 'regtest'\n\n/** Address / script type the account spends from. BIP-84 p2wpkh (bc1q…) or BIP-86 p2tr (bc1p…). */\nexport type BtcAddressType = 'p2wpkh' | 'p2tr'\n\n/** Minimal chain-data client every transport implements. Values in satoshis, fee rates in sat/vB. */\nexport interface BtcClient {\n connect(): Promise<void>\n close(): Promise<void>\n getBalance(address: string): Promise<{ confirmed: bigint; unconfirmed: bigint; unconfirmedOutgoing?: bigint }>\n listUnspent(address: string): Promise<Utxo[]>\n getHistory(address: string): Promise<Array<{ txid: string; height: number }>>\n /** Raw transaction hex. */\n getTransaction(txid: string): Promise<string>\n /** Full tx record (used for receipts). */\n getTransactionInfo?(txid: string): Promise<BlockbookTx | undefined>\n /** Broadcast raw hex; returns the txid. */\n broadcast(rawTx: string): Promise<string>\n /** Fee rate in sat/vB for confirmation within `blocks`. */\n estimateFee(blocks: number): Promise<number>\n /** Current chain tip height (anti-fee-sniping locktime). Optional. */\n getBlockHeight?(): Promise<number>\n}\n\n/** An unspent output the account can spend. */\nexport interface Utxo {\n txid: string\n vout: number\n /** Satoshis. */\n value: bigint\n /** Block height; 0 or -1 while unconfirmed. */\n height: number\n}\n\n/** One transport to a Bitcoin indexer: Blockbook or Esplora over HTTP. */\nexport type BtcClientConfig =\n /** Trezor Blockbook REST (`https://btc1.trezor.io`, QuickNode's Blockbook add-on, …). */\n | { type: 'blockbook-http'; clientConfig: { url: string } }\n /** Esplora REST (`https://mempool.space/api`, `https://blockstream.info/api`). */\n | { type: 'esplora-http'; clientConfig: { url: string } }\n\n/**\n * Options for a Bitcoin wallet manager or account: transports, network, address type and fee\n * limits.\n */\nexport interface BtcConfig {\n /** One or more transports (config objects or ready `BtcClient`s). A list fails over in order. Omit for an offline (sign-only) wallet. */\n client?: BtcClientConfig | BtcClient | Array<BtcClientConfig | BtcClient>\n /** Network; default `bitcoin` (mainnet). */\n network?: BtcNetwork\n /** p2wpkh (BIP-84, default) or p2tr (BIP-86 taproot). Changes the derivation path and the address. */\n addressType?: BtcAddressType\n /** Extra attempts across the client list after the first failure (default 3). */\n retries?: number\n /** Per-request timeout in ms (default 15000). */\n timeoutMs?: number\n /** Max fee (sats) for `transfer`. */\n transferMaxFee?: bigint | number\n /** Max fee (sats) for `sendTransaction` / `signTransaction`. */\n transactionMaxFee?: bigint | number\n /** Fee-rate ceiling in sat/vB (default 500). Anything above is refused before signing or broadcast. */\n maxFeeRate?: number\n /** Coin-selection input cap (default 200). */\n maxInputs?: number\n /** Opt-in to replace-by-fee signalling (default true). */\n rbf?: boolean\n /** Set nLockTime to the current height so the tx cannot be mined into a reorg (default true; skipped when the transport has no `getBlockHeight`). */\n antiFeeSniping?: boolean\n /** Injectable fetch (tests, custom agents). Defaults to globalThis.fetch. */\n fetch?: typeof fetch\n}\n\n/** A spend. `value` in satoshis; `feeRate` in sat/vB (estimated for `confirmationTarget` blocks when omitted). */\nexport interface BtcSpend {\n to: string\n value: bigint | number | string\n feeRate?: bigint | number\n confirmationTarget?: number\n /** Override the account-level `rbf` default for this spend. */\n rbf?: boolean\n}\n\n/** `sendTransaction` accepts a spend or an already signed raw transaction hex. */\nexport type BtcTransaction = BtcSpend | string\n\n/** A Bitcoin transfer. Amount in satoshis. */\nexport interface BtcTransferOptions {\n /** Not supported on Bitcoin; present for the shared account interface. */\n token?: string\n recipient: string\n amount: bigint | number | string\n}\n\n/** A mined Bitcoin transaction. */\nexport interface BtcReceipt {\n hash: string\n /** Always 1 once mined — a Bitcoin transaction cannot fail after inclusion. */\n status: 1\n blockNumber: bigint\n confirmations: number\n /** Total fee in sats (when the transport reports it). */\n fee?: bigint\n hex?: string\n}\n\n/** Transport-neutral tx record (Blockbook's /api/v2/tx shape; Esplora is mapped onto it). */\nexport interface BlockbookTx {\n txid: string\n blockHeight: number\n confirmations: number\n fees?: string\n hex?: string\n blockTime?: number\n}\n\n/** The most one transaction can send (a sweep of every UTXO), after the fee. */\nexport interface BtcMaxSpendableResult {\n /** Max sats one transaction can send after the fee. 0n when nothing is spendable. */\n amount: bigint\n fee: bigint\n /** Always 0n: a sweep leaves no change. */\n changeValue: bigint\n /** Inputs the sweep would consume. */\n inputs: number\n feeRate: bigint\n}\n\n/** Problems a described transaction is flagged with. */\nexport type BtcWarning =\n /** Fee rate above `maxFeeRate` (also a hard error before signing/broadcast). */\n | 'high-fee-rate'\n /** Fee exceeds 10 % of the amount sent. */\n | 'fee-over-10pct-of-amount'\n /** An output is below its dust threshold. */\n | 'dust-output'\n /** Change was below dust and folded into the fee. */\n | 'no-change'\n\n/** One input of a described transaction: the UTXO it spends. */\nexport interface BtcTxInputDescription {\n txid: string\n vout: number\n value: bigint\n}\n\n/** One output of a described transaction, tagged as the recipient, our change, or unknown. */\nexport interface BtcTxOutputDescription {\n /** Undefined for scripts without an address form. */\n address: string | undefined\n value: bigint\n role: 'recipient' | 'change' | 'unknown'\n}\n\n/** \"What you sign\": the fully resolved transaction before (or after) signing. */\nexport interface BtcTxDescription {\n kind: 'btc-transfer'\n inputs: BtcTxInputDescription[]\n outputs: BtcTxOutputDescription[]\n /** Sats sent to non-change outputs. */\n amount: bigint\n fee: bigint\n /** sat/vB, rounded to 2 decimals. */\n feeRate: number\n vsize: number\n rbf: boolean\n lockTime: number\n /** Present once the transaction is signed. */\n txid?: string\n warnings: BtcWarning[]\n}\n\n/** CAIP-2 ids of the Bitcoin networks, by name. */\nexport const CAIP2_GENESIS: Record<BtcNetwork, string> = {\n bitcoin: 'bip122:000000000019d6689c085ae165831e93',\n testnet: 'bip122:000000000933ea01ad0ee984209779ba',\n regtest: 'bip122:0f9188f13cb7b2c71f2a335e3a4fc328',\n}\n"],"mappings":"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","names":["import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/history.ts","../src/blockbook.ts","../src/http.ts","../src/types.ts"],"sourcesContent":["/**\n * Activity history for Bitcoin: a `HistorySource` over Blockbook's\n * `/api/v2/address/{a}?details=txs` pages (Trezor's indexer, QuickNode's\n * Blockbook add-on, any self-hosted instance), reusing the package's\n * `BlockbookClient` and failing over across several instances.\n *\n * One transaction = one native transfer in the owner's direction: a send is\n * what left to non-own outputs (fee separate), a receive is what landed on the\n * address. The cursor is the Blockbook page number. Explorer: mempool.space.\n */\nimport type { ChainTransfer, ChainTx, HistoryPage, HistoryQuery, HistorySource } from '@anywalletkit/core'\nimport { BlockbookClient, type BlockbookAddressPage, type BlockbookTxDetails } from './blockbook.js'\nimport { DEFAULT_TIMEOUT_MS } from './http.js'\nimport { CAIP2_GENESIS, type BtcNetwork } from './types.js'\n\n// ── Public types ─────────────────────────────────────────────────────────────\n\n/** Options for {@link createBtcHistory}. */\nexport interface BtcHistoryOptions {\n /** One Blockbook root (`https://btc1.trezor.io`). */\n blockbookUrl?: string\n /** Several roots, tried in order (failover). */\n blockbookUrls?: string[]\n /** `bitcoin` (default), `testnet`, `regtest`. */\n network?: BtcNetwork\n fetch?: typeof fetch\n /** Per request, ms. Default 15 000. */\n timeoutMs?: number\n /** Rows per Blockbook page (its max is 1000). Default: the query limit, capped at 100. */\n pageSize?: number\n}\n\n/** What a Bitcoin `ChainTx` was built from, and the address's own totals. */\nexport interface BtcHistoryRaw {\n /** The Blockbook transaction (vin / vout with addresses and values; `hex` dropped). */\n tx: BlockbookTxDetails\n /** Sum of inputs spent by the address, sats. */\n ownIn: bigint\n /** Sum of outputs paid to the address, sats. */\n ownOut: bigint\n}\n\n/** A `ChainTx` with the Blockbook transaction it was built from. */\nexport interface BtcChainTx extends ChainTx {\n raw: BtcHistoryRaw\n}\n\n/** One page of Bitcoin history. */\nexport interface BtcHistoryPage extends HistoryPage {\n items: BtcChainTx[]\n /** Blockbook page served / total pages. */\n page?: number\n totalPages?: number\n}\n\n/** Bitcoin history from a Blockbook instance. Build it with {@link createBtcHistory}. */\nexport interface BtcHistorySource extends HistorySource {\n listActivity(address: string, query?: HistoryQuery): Promise<BtcHistoryPage>\n getTransaction(hash: string, address: string): Promise<BtcChainTx | undefined>\n}\n\n/** Every configured Blockbook endpoint failed (or none was configured). */\nexport class HistoryUnavailableError extends Error {\n readonly chain: string\n readonly reason: string\n readonly attempts: readonly string[]\n constructor(chain: string, reason: string, attempts: readonly string[] = []) {\n super(`History unavailable for ${chain}: ${reason}`)\n this.name = 'HistoryUnavailableError'\n this.chain = chain\n this.reason = reason\n this.attempts = attempts\n }\n}\n\nconst DEFAULT_LIMIT = 25\nconst MAX_PAGE_SIZE = 100\n\n// ── Cursor: the next Blockbook page, base64 JSON ─────────────────────────────\n\n/** @internal Decoded form of a Bitcoin history cursor. */\nexport interface BtcHistoryCursor {\n v: 1\n /** Next page (1-based). */\n p: number\n /** Page size the pagination started with (changing it mid-way would skip or repeat rows). */\n s: number\n}\n\nconst B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\n\nfunction b64encode(s: string): string {\n let out = ''\n for (let i = 0; i < s.length; i += 3) {\n const c = [s.charCodeAt(i), s.charCodeAt(i + 1), s.charCodeAt(i + 2)]\n const n = (c[0]! << 16) | ((c[1]! || 0) << 8) | (c[2]! || 0)\n out += B64[(n >> 18) & 63]! + B64[(n >> 12) & 63]! + (Number.isNaN(c[1]) ? '=' : B64[(n >> 6) & 63]!) + (Number.isNaN(c[2]) ? '=' : B64[n & 63]!)\n }\n return out\n}\n\nfunction b64decode(s: string): string {\n const clean = s.replace(/[^A-Za-z0-9+/]/g, '')\n let out = ''\n for (let i = 0; i < clean.length; i += 4) {\n const c = [0, 1, 2, 3].map((k) => (i + k < clean.length ? B64.indexOf(clean[i + k]!) : -1))\n if (c[0]! < 0 || c[1]! < 0) throw new Error('bad base64')\n const n = (c[0]! << 18) | (c[1]! << 12) | ((c[2]! < 0 ? 0 : c[2]!) << 6) | (c[3]! < 0 ? 0 : c[3]!)\n out += String.fromCharCode((n >> 16) & 0xff)\n if (c[2]! >= 0) out += String.fromCharCode((n >> 8) & 0xff)\n if (c[3]! >= 0) out += String.fromCharCode(n & 0xff)\n }\n return out\n}\n\nexport function encodeCursor(c: BtcHistoryCursor): string {\n return b64encode(JSON.stringify(c))\n}\n\nexport function decodeCursor(s: string): BtcHistoryCursor {\n let parsed: unknown\n try {\n parsed = JSON.parse(b64decode(s))\n } catch {\n throw new Error('@anywalletkit/wallet-btc: malformed history cursor')\n }\n const c = parsed as Partial<BtcHistoryCursor> | null\n if (!c || c.v !== 1 || !Number.isInteger(c.p) || (c.p as number) < 1 || !Number.isInteger(c.s) || (c.s as number) < 1) throw new Error('@anywalletkit/wallet-btc: malformed history cursor')\n return { v: 1, p: c.p as number, s: c.s as number }\n}\n\n// ── Mapping ──────────────────────────────────────────────────────────────────\n\nconst big = (v: unknown): bigint => (typeof v === 'string' && /^-?\\d+$/.test(v) ? BigInt(v) : typeof v === 'number' ? BigInt(Math.trunc(v)) : 0n)\n\nexport function mapBlockbookTx(chain: string, address: string, tx: BlockbookTxDetails): BtcChainTx {\n const owns = (e: { isAddress?: boolean; addresses?: string[] }) => e.isAddress !== false && (e.addresses ?? []).includes(address)\n let ownIn = 0n, ownOut = 0n, sentToOthers = 0n\n for (const vin of tx.vin ?? []) if (owns(vin)) ownIn += big(vin.value)\n for (const vout of tx.vout ?? []) {\n if (owns(vout)) ownOut += big(vout.value)\n else sentToOthers += big(vout.value)\n }\n const firstInput = (tx.vin ?? []).find((v) => v.isAddress !== false && v.addresses?.[0])?.addresses?.[0]\n const firstOtherOutput = (tx.vout ?? []).find((v) => v.isAddress !== false && !owns(v) && v.addresses?.[0])?.addresses?.[0]\n const transfers: ChainTransfer[] = []\n let from: string, to: string | null, nativeValue: bigint\n if (ownIn > 0n) {\n // We spent inputs: a send (or a consolidation when every output is ours).\n from = address\n to = firstOtherOutput ?? address\n nativeValue = firstOtherOutput ? sentToOthers : ownOut\n if (nativeValue > 0n) transfers.push({ token: null, from: address, to, amount: nativeValue })\n } else {\n // Only outputs are ours: a receive (coinbase has no input address).\n from = firstInput ?? 'coinbase'\n to = address\n nativeValue = ownOut\n if (nativeValue > 0n) transfers.push({ token: null, from, to: address, amount: nativeValue })\n }\n const confirmed = tx.confirmations > 0 && tx.blockHeight > 0\n const out: BtcChainTx = {\n chain, hash: tx.txid, time: tx.blockTime ?? 0, status: confirmed ? 'confirmed' : 'pending', address, from, to, nativeValue, transfers,\n raw: { tx, ownIn, ownOut },\n }\n if (tx.fees !== undefined) out.fee = big(tx.fees)\n if (confirmed) out.block = BigInt(tx.blockHeight)\n return out\n}\n\n// ── Source ───────────────────────────────────────────────────────────────────\n\n/** mempool.space link for a transaction. */\nexport function btcExplorerTxUrl(hash: string, network: BtcNetwork = 'bitcoin'): string {\n return `https://mempool.space/${network === 'testnet' ? 'testnet/' : ''}tx/${hash}`\n}\n\n/**\n * A history source for Bitcoin addresses backed by Blockbook (Trezor's public instances,\n * QuickNode's add-on, or your own).\n *\n * @throws Error when no Blockbook URL is given.\n */\nexport function createBtcHistory(options: BtcHistoryOptions = {}): BtcHistorySource {\n const network = options.network ?? 'bitcoin'\n const chain = CAIP2_GENESIS[network]\n const urls = [...(options.blockbookUrls ?? []), ...(options.blockbookUrl ? [options.blockbookUrl] : [])]\n if (urls.length === 0) throw new Error('@anywalletkit/wallet-btc: createBtcHistory needs blockbookUrl or blockbookUrls')\n const fetchFn = options.fetch ?? globalThis.fetch\n if (typeof fetchFn !== 'function') throw new Error('@anywalletkit/wallet-btc: no global fetch; pass `fetch` in the options')\n const clients = urls.map((url) => ({ url, client: new BlockbookClient({ url }, fetchFn, options.timeoutMs ?? DEFAULT_TIMEOUT_MS) }))\n\n async function failover<T>(run: (client: BlockbookClient) => Promise<T>): Promise<T> {\n const attempts: string[] = []\n for (const { url, client } of clients) {\n try {\n return await run(client)\n } catch (e) {\n attempts.push(`${url}: ${e instanceof Error ? e.message : String(e)}`)\n }\n }\n throw new HistoryUnavailableError(chain, attempts[attempts.length - 1] ?? 'every Blockbook failed', attempts)\n }\n\n return {\n chain,\n\n async listActivity(address: string, query: HistoryQuery = {}): Promise<BtcHistoryPage> {\n const cursor = query.cursor ? decodeCursor(query.cursor) : undefined\n const pageSize = cursor?.s ?? Math.min(MAX_PAGE_SIZE, Math.max(1, Math.floor(options.pageSize ?? query.limit ?? DEFAULT_LIMIT)))\n const pageNo = cursor?.p ?? 1\n const data: BlockbookAddressPage = await failover((c) => c.getAddressPage(address, pageNo, pageSize))\n let items = (data.transactions ?? []).map((tx) => mapBlockbookTx(chain, address, tx))\n items.sort((a, b) => (b.block === undefined ? 1 : a.block === undefined ? -1 : (b.block > a.block ? 1 : b.block < a.block ? -1 : 0)) || b.time - a.time)\n const totalPages = data.totalPages ?? 1\n let more = pageNo < totalPages\n if (query.since !== undefined) {\n const since = query.since\n if (items.some((t) => t.status === 'confirmed' && t.time < since)) more = false\n items = items.filter((t) => t.status !== 'confirmed' || t.time >= since)\n }\n const page: BtcHistoryPage = { items, page: pageNo, totalPages }\n if (more) page.cursor = encodeCursor({ v: 1, p: pageNo + 1, s: pageSize })\n return page\n },\n\n async getTransaction(hash: string, address: string): Promise<BtcChainTx | undefined> {\n const tx = await failover((c) => c.getTransactionDetails(hash))\n return tx ? mapBlockbookTx(chain, address, tx) : undefined\n },\n\n explorerTxUrl(hash: string): string {\n return btcExplorerTxUrl(hash, network)\n },\n }\n}\n\n","import { NetworkError } from '@anywalletkit/core'\nimport { DEFAULT_TIMEOUT_MS, fetchWithTimeout, isIntString, isRecord, refusal, refusedStatus } from './http.js'\nimport type { BlockbookTx, BtcClient, Utxo } from './types.js'\n\nconst MEMPOOL_SPACE_URL = 'https://mempool.space'\n\nexport interface BlockbookVin { txid?: string; vout?: number; n?: number; isAddress?: boolean; isOwn?: boolean; addresses?: string[]; value?: string }\nexport interface BlockbookVout { n: number; isAddress?: boolean; isOwn?: boolean; addresses?: string[]; value?: string; spent?: boolean }\ninterface BlockbookAddressTx { txid: string; blockHeight: number; vin?: BlockbookVin[]; vout?: BlockbookVout[] }\n/** A Blockbook transaction with its inputs and outputs (`/api/v2/tx/{txid}`, or one row of `?details=txs`). */\nexport interface BlockbookTxDetails extends BlockbookTx {\n vin?: BlockbookVin[]\n vout?: BlockbookVout[]\n value?: string\n valueIn?: string\n}\n/** One `/api/v2/address/{a}?details=txs` page. */\nexport interface BlockbookAddressPage {\n page?: number\n totalPages?: number\n itemsOnPage?: number\n address?: string\n balance?: string\n unconfirmedBalance?: string\n txs?: number\n transactions?: BlockbookTxDetails[]\n}\n\n/**\n * Pending outgoing value the way Bitcoin Core's wallet counts it: a pending tx\n * is trusted only if every input is ours and confirmed or chained from another\n * trusted tx; only then its change counts back.\n */\nexport function unconfirmedOutgoing(transactions: BlockbookAddressTx[] | undefined, address: string): bigint {\n const pending = (transactions ?? []).filter((tx) => tx.blockHeight === -1)\n const pendingIds = new Set(pending.map((t) => t.txid))\n const byId = new Map(pending.map((t) => [t.txid, t]))\n const trusted = new Map<string, boolean>()\n const owned = (entries: Array<BlockbookVin | BlockbookVout> | undefined) =>\n (entries ?? []).filter((e) => e.isAddress && e.addresses?.includes(address)).reduce((s, e) => s + BigInt(e.value ?? 0), 0n)\n const isTrusted = (tx: BlockbookAddressTx): boolean => {\n if (trusted.has(tx.txid)) return trusted.get(tx.txid)!\n trusted.set(tx.txid, false)\n const vins = tx.vin ?? []\n const ours = vins.filter((v) => v.isAddress && v.addresses?.includes(address))\n const ok = vins.length > 0 && ours.length === vins.length && ours.every((v) => !v.txid || !pendingIds.has(v.txid) || isTrusted(byId.get(v.txid)!))\n trusted.set(tx.txid, ok)\n return ok\n }\n const consumed = new Set<string>()\n for (const tx of pending) for (const v of tx.vin ?? []) if (v.txid && pendingIds.has(v.txid)) consumed.add(`${v.txid}:${v.vout}`)\n return pending.reduce((total, tx) => {\n const spent = owned((tx.vin ?? []).filter((v) => !v.txid || !pendingIds.has(v.txid)))\n const change = isTrusted(tx) ? owned((tx.vout ?? []).filter((o) => !consumed.has(`${tx.txid}:${o.n}`))) : 0n\n return total + (spent - change)\n }, 0n)\n}\n\n/** Blockbook REST transport (Trezor's indexer; QuickNode's Blockbook add-on speaks the same API). */\nexport class BlockbookClient implements BtcClient {\n private readonly base: string\n private readonly fetchFn: typeof fetch\n private readonly timeoutMs: number\n\n constructor(config: { url: string }, fetchFn: typeof fetch = globalThis.fetch, timeoutMs = DEFAULT_TIMEOUT_MS) {\n this.base = config.url.replace(/\\/+$/, '').replace(/\\/api$/, '')\n this.fetchFn = fetchFn\n this.timeoutMs = timeoutMs\n }\n\n async connect(): Promise<void> {}\n async close(): Promise<void> {}\n\n private async get<T>(path: string, check: (v: unknown) => v is T): Promise<T> {\n const url = `${this.base}/api${path}`\n const res = await fetchWithTimeout(this.fetchFn, url, undefined, this.timeoutMs, this.base)\n if (!res.ok) {\n // A 4xx (not 408) is the server refusing: nothing was taken. A 5xx may have been forwarded (round 3, M12).\n const reason = refusedStatus(res.status) ? (await res.text().catch(() => '')).slice(0, 200) : ''\n const err = new NetworkError(`Blockbook request failed: ${res.status} ${res.statusText} (${url})${reason ? `: ${reason}` : ''}`, { provider: this.base })\n throw refusedStatus(res.status) ? refusal(err) : err\n }\n let body: unknown\n try { body = await res.json() } catch (e) { throw new NetworkError(`Blockbook returned non-JSON for ${url}`, { provider: this.base, cause: e }) }\n if (!check(body)) throw new NetworkError(`Blockbook returned an unexpected shape for ${url}`, { provider: this.base })\n return body\n }\n\n async getBalance(address: string) {\n const data = await this.get(`/v2/address/${address}?details=txs`, (v): v is { balance: string; unconfirmedBalance: string; transactions?: BlockbookAddressTx[] } =>\n isRecord(v) && isIntString(v.balance) && isIntString(v.unconfirmedBalance) && (v.transactions === undefined || Array.isArray(v.transactions)))\n return { confirmed: BigInt(data.balance), unconfirmed: BigInt(data.unconfirmedBalance), unconfirmedOutgoing: unconfirmedOutgoing(data.transactions, address) }\n }\n\n async listUnspent(address: string): Promise<Utxo[]> {\n const data = await this.get(`/v2/utxo/${address}`, (v): v is Array<{ txid: string; vout: number; value: string; height?: number }> =>\n Array.isArray(v) && v.every((u) => isRecord(u) && typeof u.txid === 'string' && typeof u.vout === 'number' && isIntString(u.value)))\n return data.map((u) => ({ txid: u.txid, vout: u.vout, value: BigInt(u.value), height: u.height ?? -1 }))\n }\n\n async getHistory(address: string) {\n const items: Array<{ txid: string; height: number }> = []\n for (let page = 1; ; page++) {\n const data = await this.get(`/v2/address/${address}?details=txslight&pageSize=1000&page=${page}`, (v): v is { transactions?: Array<{ txid: string; blockHeight: number }>; totalPages?: number } =>\n isRecord(v) && (v.transactions === undefined || Array.isArray(v.transactions)))\n for (const tx of data.transactions ?? []) items.push({ txid: tx.txid, height: tx.blockHeight })\n if (page >= (data.totalPages ?? 1)) break\n }\n return items\n }\n\n /** One page of the address's transactions, newest first, with inputs / outputs (what the activity history reads). */\n async getAddressPage(address: string, page = 1, pageSize = 25): Promise<BlockbookAddressPage> {\n const data = await this.get(`/v2/address/${address}?details=txs&page=${page}&pageSize=${pageSize}`, (v): v is BlockbookAddressPage =>\n isRecord(v) && (v.transactions === undefined || (Array.isArray(v.transactions) && v.transactions.every(BlockbookClient.isTx))))\n const out: BlockbookAddressPage = { ...data }\n for (const tx of out.transactions ?? []) delete (tx as { hex?: string }).hex\n return out\n }\n\n /** A transaction with its inputs / outputs; undefined when Blockbook does not know it. */\n async getTransactionDetails(txid: string): Promise<BlockbookTxDetails | undefined> {\n try {\n const tx = await this.get(`/v2/tx/${txid}`, (v): v is BlockbookTxDetails => BlockbookClient.isTx(v))\n const out: BlockbookTxDetails = { ...tx }\n delete (out as { hex?: string }).hex\n return out\n } catch (e) {\n if (e instanceof NetworkError && /\\b(400|404)\\b/.test(e.message)) return undefined\n throw e\n }\n }\n\n private static isTx(v: unknown): v is BlockbookTx {\n return isRecord(v) && typeof v.txid === 'string' && typeof v.blockHeight === 'number' && typeof v.confirmations === 'number'\n }\n\n async getTransactionInfo(txid: string): Promise<BlockbookTx | undefined> {\n try { return await this.get(`/v2/tx/${txid}`, BlockbookClient.isTx) } catch (e) {\n if (e instanceof NetworkError && /\\b(400|404)\\b/.test(e.message)) return undefined\n throw e\n }\n }\n\n async getTransaction(txid: string): Promise<string> {\n const data = await this.get(`/v2/tx/${txid}`, BlockbookClient.isTx)\n if (typeof data.hex !== 'string' || !data.hex) throw new NetworkError(`Transaction ${txid} has no hex data`, { provider: this.base })\n return data.hex\n }\n\n async broadcast(rawTx: string): Promise<string> {\n const data = await this.get(`/v2/sendtx/${rawTx}`, (v): v is { result?: string; error?: { message?: string } | string } => isRecord(v))\n if (data.error) throw refusal(new Error(typeof data.error === 'string' ? data.error : data.error.message ?? 'broadcast failed'))\n if (!data.result) throw new NetworkError('Blockbook returned no txid', { provider: this.base })\n return data.result\n }\n\n /** sat/vB. Blockbook reports BTC/kB (×1e5 → sat/vB); falls back to mempool.space when it has no estimate. */\n async estimateFee(blocks: number): Promise<number> {\n try {\n const data = await this.get(`/v1/estimatefee/${blocks}`, (v): v is { result?: string } | string => typeof v === 'string' || isRecord(v))\n const rate = Number(typeof data === 'string' ? data : data.result)\n if (rate > 0) return Math.max(rate * 100_000, 1)\n } catch { /* fall through */ }\n const res = await fetchWithTimeout(this.fetchFn, `${MEMPOOL_SPACE_URL}/api/v1/fees/recommended`, undefined, this.timeoutMs, MEMPOOL_SPACE_URL)\n if (!res.ok) throw new NetworkError('Fee estimation request failed', { provider: MEMPOOL_SPACE_URL })\n const d = (await res.json()) as { fastestFee: number; halfHourFee: number; hourFee: number; economyFee: number }\n const satPerVb = blocks <= 1 ? d.fastestFee : blocks <= 3 ? d.halfHourFee : blocks <= 6 ? d.hourFee : d.economyFee\n if (!satPerVb || satPerVb <= 0) throw new NetworkError('Fee estimation is unavailable', { provider: MEMPOOL_SPACE_URL })\n return satPerVb\n }\n\n async getBlockHeight(): Promise<number> {\n const data = await this.get('/v2', (v): v is { blockbook?: { bestHeight?: number } } => isRecord(v))\n const h = data.blockbook?.bestHeight\n if (typeof h !== 'number' || h <= 0) throw new NetworkError('Blockbook did not report a block height', { provider: this.base })\n return h\n }\n}\n","import { NetworkError } from '@anywalletkit/core'\n\nexport const DEFAULT_TIMEOUT_MS = 15_000\n\n/** fetch with a per-request timeout when the runtime has `AbortSignal.timeout` (Hermes needs runtime-hermes for it). */\nexport async function fetchWithTimeout(fetchFn: typeof fetch, url: string, init: RequestInit | undefined, timeoutMs: number, provider: string): Promise<Response> {\n const signal = typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(timeoutMs) : undefined\n try {\n return await fetchFn(url, { ...init, ...(signal ? { signal } : {}) })\n } catch (e) {\n throw new NetworkError(`Request failed: ${url}`, { provider, cause: e })\n }\n}\n\n/** An HTTP status that is the server refusing the request itself: 4xx, but not 408 (request timeout). */\nexport const refusedStatus = (status: number): boolean => status >= 400 && status < 500 && status !== 408\n\n/** Mark an error as a node's answer refusing the request: nothing was taken (round 3, M12). */\nexport function refusal<E extends Error>(e: E): E {\n return Object.assign(e, { answered: true })\n}\n\n/**\n * An endpoint answered with a refusal (HTTP 4xx other than 408, or an error body): nothing was\n * taken. A thrown fetch, a timeout, a 5xx or a body that cannot be read may have been.\n */\nexport function isAnsweredRefusal(e: unknown): boolean {\n return (e as { answered?: unknown } | null)?.answered === true\n}\n\n/** The node says it already holds the transaction (an earlier attempt delivered it): it is out, not refused. */\nexport function isAlreadyKnown(e: unknown): boolean {\n for (let x: unknown = e, depth = 0; x instanceof Error && depth < 5; x = x.cause, depth++) {\n if (/txn-already-(in-mempool|known)|already (known|in (the )?(mempool|block ?chain))/i.test(x.message)) return true\n }\n return false\n}\n\nexport const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nexport const isHex64 = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-fA-F]{64}$/.test(v)\nexport const isIntString = (v: unknown): v is string => typeof v === 'string' && /^-?\\d+$/.test(v)\n","/** Bitcoin mainnet, testnet or regtest. */\nexport type BtcNetwork = 'bitcoin' | 'testnet' | 'regtest'\n\n/** Address / script type the account spends from. BIP-84 p2wpkh (bc1q…) or BIP-86 p2tr (bc1p…). */\nexport type BtcAddressType = 'p2wpkh' | 'p2tr'\n\n/** Minimal chain-data client every transport implements. Values in satoshis, fee rates in sat/vB. */\nexport interface BtcClient {\n connect(): Promise<void>\n close(): Promise<void>\n getBalance(address: string): Promise<{ confirmed: bigint; unconfirmed: bigint; unconfirmedOutgoing?: bigint }>\n listUnspent(address: string): Promise<Utxo[]>\n getHistory(address: string): Promise<Array<{ txid: string; height: number }>>\n /** Raw transaction hex. */\n getTransaction(txid: string): Promise<string>\n /** Full tx record (used for receipts). */\n getTransactionInfo?(txid: string): Promise<BlockbookTx | undefined>\n /** Broadcast raw hex; returns the txid. */\n broadcast(rawTx: string): Promise<string>\n /** Fee rate in sat/vB for confirmation within `blocks`. */\n estimateFee(blocks: number): Promise<number>\n /** Current chain tip height (anti-fee-sniping locktime). Optional. */\n getBlockHeight?(): Promise<number>\n}\n\n/** An unspent output the account can spend. */\nexport interface Utxo {\n txid: string\n vout: number\n /** Satoshis. */\n value: bigint\n /** Block height; 0 or -1 while unconfirmed. */\n height: number\n}\n\n/** One transport to a Bitcoin indexer: Blockbook or Esplora over HTTP. */\nexport type BtcClientConfig =\n /** Trezor Blockbook REST (`https://btc1.trezor.io`, QuickNode's Blockbook add-on, …). */\n | { type: 'blockbook-http'; clientConfig: { url: string } }\n /** Esplora REST (`https://mempool.space/api`, `https://blockstream.info/api`). */\n | { type: 'esplora-http'; clientConfig: { url: string } }\n\n/**\n * Options for a Bitcoin wallet manager or account: transports, network, address type and fee\n * limits.\n */\nexport interface BtcConfig {\n /** One or more transports (config objects or ready `BtcClient`s). A list fails over in order. Omit for an offline (sign-only) wallet. */\n client?: BtcClientConfig | BtcClient | Array<BtcClientConfig | BtcClient>\n /** Network; default `bitcoin` (mainnet). */\n network?: BtcNetwork\n /** p2wpkh (BIP-84, default) or p2tr (BIP-86 taproot). Changes the derivation path and the address. */\n addressType?: BtcAddressType\n /** Extra attempts across the client list after the first failure (default 3). */\n retries?: number\n /** Per-request timeout in ms (default 15000). */\n timeoutMs?: number\n /** Max fee (sats) for `transfer`. */\n transferMaxFee?: bigint | number\n /** Max fee (sats) for `sendTransaction` / `signTransaction`. */\n transactionMaxFee?: bigint | number\n /** Fee-rate ceiling in sat/vB (default 500). Anything above is refused before signing or broadcast. */\n maxFeeRate?: number\n /** Coin-selection input cap (default 200). */\n maxInputs?: number\n /** Opt-in to replace-by-fee signalling (default true). */\n rbf?: boolean\n /** Set nLockTime to the current height so the tx cannot be mined into a reorg (default true; skipped when the transport has no `getBlockHeight`). */\n antiFeeSniping?: boolean\n /** Injectable fetch (tests, custom agents). Defaults to globalThis.fetch. */\n fetch?: typeof fetch\n}\n\n/** A spend. `value` in satoshis; `feeRate` in sat/vB (estimated for `confirmationTarget` blocks when omitted). */\nexport interface BtcSpend {\n to: string\n value: bigint | number | string\n feeRate?: bigint | number\n confirmationTarget?: number\n /** Override the account-level `rbf` default for this spend. */\n rbf?: boolean\n}\n\n/** `sendTransaction` accepts a spend or an already signed raw transaction hex. */\nexport type BtcTransaction = BtcSpend | string\n\n/** A Bitcoin transfer. Amount in satoshis. */\nexport interface BtcTransferOptions {\n /** Not supported on Bitcoin; present for the shared account interface. */\n token?: string\n recipient: string\n amount: bigint | number | string\n}\n\n/** A mined Bitcoin transaction. */\nexport interface BtcReceipt {\n hash: string\n /** Always 1 once mined — a Bitcoin transaction cannot fail after inclusion. */\n status: 1\n blockNumber: bigint\n confirmations: number\n /** Total fee in sats (when the transport reports it). */\n fee?: bigint\n hex?: string\n}\n\n/** Transport-neutral tx record (Blockbook's /api/v2/tx shape; Esplora is mapped onto it). */\nexport interface BlockbookTx {\n txid: string\n blockHeight: number\n confirmations: number\n fees?: string\n hex?: string\n blockTime?: number\n}\n\n/** The most one transaction can send (a sweep of every UTXO), after the fee. */\nexport interface BtcMaxSpendableResult {\n /** Max sats one transaction can send after the fee. 0n when nothing is spendable. */\n amount: bigint\n fee: bigint\n /** Always 0n: a sweep leaves no change. */\n changeValue: bigint\n /** Inputs the sweep would consume. */\n inputs: number\n feeRate: bigint\n}\n\n/** Problems a described transaction is flagged with. */\nexport type BtcWarning =\n /** Fee rate above `maxFeeRate` (also a hard error before signing/broadcast). */\n | 'high-fee-rate'\n /** Fee exceeds 10 % of the amount sent. */\n | 'fee-over-10pct-of-amount'\n /** An output is below its dust threshold. */\n | 'dust-output'\n /** Change was below dust and folded into the fee. */\n | 'no-change'\n\n/** One input of a described transaction: the UTXO it spends. */\nexport interface BtcTxInputDescription {\n txid: string\n vout: number\n value: bigint\n}\n\n/** One output of a described transaction, tagged as the recipient, our change, or unknown. */\nexport interface BtcTxOutputDescription {\n /** Undefined for scripts without an address form. */\n address: string | undefined\n value: bigint\n role: 'recipient' | 'change' | 'unknown'\n}\n\n/** \"What you sign\": the fully resolved transaction before (or after) signing. */\nexport interface BtcTxDescription {\n kind: 'btc-transfer'\n inputs: BtcTxInputDescription[]\n outputs: BtcTxOutputDescription[]\n /** Sats sent to non-change outputs. */\n amount: bigint\n fee: bigint\n /** sat/vB, rounded to 2 decimals. */\n feeRate: number\n vsize: number\n rbf: boolean\n lockTime: number\n /** Present once the transaction is signed. */\n txid?: string\n warnings: BtcWarning[]\n}\n\n/** CAIP-2 ids of the Bitcoin networks, by name. */\nexport const CAIP2_GENESIS: Record<BtcNetwork, string> = {\n bitcoin: 'bip122:000000000019d6689c085ae165831e93',\n testnet: 'bip122:000000000933ea01ad0ee984209779ba',\n regtest: 'bip122:0f9188f13cb7b2c71f2a335e3a4fc328',\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/history.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
BlockbookClient: () => BlockbookClient,
|
|
24
|
+
BroadcastUnknownError: () => BroadcastUnknownError,
|
|
24
25
|
CAIP2_GENESIS: () => CAIP2_GENESIS,
|
|
25
26
|
DEFAULT_MAX_FEE_RATE: () => DEFAULT_MAX_FEE_RATE,
|
|
26
27
|
DUST_LIMIT_P2TR: () => DUST_LIMIT_P2TR,
|
|
@@ -54,6 +55,7 @@ __export(src_exports, {
|
|
|
54
55
|
encodeHistoryCursor: () => encodeCursor,
|
|
55
56
|
encodeMessageSignature: () => encodeMessageSignature,
|
|
56
57
|
estimateVsize: () => estimateVsize,
|
|
58
|
+
isAnsweredRefusal: () => isAnsweredRefusal,
|
|
57
59
|
magicHash: () => magicHash,
|
|
58
60
|
mapBlockbookTx: () => mapBlockbookTx,
|
|
59
61
|
networkParams: () => networkParams,
|
|
@@ -102,9 +104,17 @@ var UnsupportedAddressTypeError = class extends Error {
|
|
|
102
104
|
this.name = "UnsupportedAddressTypeError";
|
|
103
105
|
}
|
|
104
106
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
var BroadcastUnknownError = class extends Error {
|
|
108
|
+
/** Always true: the transaction may be on its way. */
|
|
109
|
+
maybeSent = true;
|
|
110
|
+
/** The transaction id (hex). */
|
|
111
|
+
hash;
|
|
112
|
+
constructor(hash, cause) {
|
|
113
|
+
super(`Broadcast outcome unknown for ${hash}: ${cause instanceof Error ? cause.message.slice(0, 200) : String(cause).slice(0, 200)}`, { cause });
|
|
114
|
+
this.name = "BroadcastUnknownError";
|
|
115
|
+
this.hash = hash;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
108
118
|
|
|
109
119
|
// src/http.ts
|
|
110
120
|
var import_core2 = require("@anywalletkit/core");
|
|
@@ -117,11 +127,25 @@ async function fetchWithTimeout(fetchFn, url, init, timeoutMs, provider) {
|
|
|
117
127
|
throw new import_core2.NetworkError(`Request failed: ${url}`, { provider, cause: e });
|
|
118
128
|
}
|
|
119
129
|
}
|
|
130
|
+
var refusedStatus = (status) => status >= 400 && status < 500 && status !== 408;
|
|
131
|
+
function refusal(e) {
|
|
132
|
+
return Object.assign(e, { answered: true });
|
|
133
|
+
}
|
|
134
|
+
function isAnsweredRefusal(e) {
|
|
135
|
+
return e?.answered === true;
|
|
136
|
+
}
|
|
137
|
+
function isAlreadyKnown(e) {
|
|
138
|
+
for (let x = e, depth = 0; x instanceof Error && depth < 5; x = x.cause, depth++) {
|
|
139
|
+
if (/txn-already-(in-mempool|known)|already (known|in (the )?(mempool|block ?chain))/i.test(x.message)) return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
120
143
|
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
121
144
|
var isHex64 = (v) => typeof v === "string" && /^[0-9a-fA-F]{64}$/.test(v);
|
|
122
145
|
var isIntString = (v) => typeof v === "string" && /^-?\d+$/.test(v);
|
|
123
146
|
|
|
124
147
|
// src/blockbook.ts
|
|
148
|
+
var import_core3 = require("@anywalletkit/core");
|
|
125
149
|
var MEMPOOL_SPACE_URL = "https://mempool.space";
|
|
126
150
|
function unconfirmedOutgoing(transactions, address) {
|
|
127
151
|
const pending = (transactions ?? []).filter((tx) => tx.blockHeight === -1);
|
|
@@ -162,7 +186,11 @@ var BlockbookClient = class _BlockbookClient {
|
|
|
162
186
|
async get(path, check) {
|
|
163
187
|
const url = `${this.base}/api${path}`;
|
|
164
188
|
const res = await fetchWithTimeout(this.fetchFn, url, void 0, this.timeoutMs, this.base);
|
|
165
|
-
if (!res.ok)
|
|
189
|
+
if (!res.ok) {
|
|
190
|
+
const reason = refusedStatus(res.status) ? (await res.text().catch(() => "")).slice(0, 200) : "";
|
|
191
|
+
const err = new import_core3.NetworkError(`Blockbook request failed: ${res.status} ${res.statusText} (${url})${reason ? `: ${reason}` : ""}`, { provider: this.base });
|
|
192
|
+
throw refusedStatus(res.status) ? refusal(err) : err;
|
|
193
|
+
}
|
|
166
194
|
let body;
|
|
167
195
|
try {
|
|
168
196
|
body = await res.json();
|
|
@@ -226,7 +254,7 @@ var BlockbookClient = class _BlockbookClient {
|
|
|
226
254
|
}
|
|
227
255
|
async broadcast(rawTx) {
|
|
228
256
|
const data = await this.get(`/v2/sendtx/${rawTx}`, (v) => isRecord(v));
|
|
229
|
-
if (data.error) throw new Error(typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed");
|
|
257
|
+
if (data.error) throw refusal(new Error(typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed"));
|
|
230
258
|
if (!data.result) throw new import_core3.NetworkError("Blockbook returned no txid", { provider: this.base });
|
|
231
259
|
return data.result;
|
|
232
260
|
}
|
|
@@ -274,7 +302,11 @@ var EsploraClient = class {
|
|
|
274
302
|
async request(path, init) {
|
|
275
303
|
const url = `${this.base}${path}`;
|
|
276
304
|
const res = await fetchWithTimeout(this.fetchFn, url, init, this.timeoutMs, this.base);
|
|
277
|
-
if (!res.ok)
|
|
305
|
+
if (!res.ok) {
|
|
306
|
+
const reason = refusedStatus(res.status) ? (await res.text().catch(() => "")).slice(0, 200) : "";
|
|
307
|
+
const err = new import_core4.NetworkError(`Esplora request failed: ${res.status} ${res.statusText} (${url})${reason ? `: ${reason}` : ""}`, { provider: this.base });
|
|
308
|
+
throw refusedStatus(res.status) ? refusal(err) : err;
|
|
309
|
+
}
|
|
278
310
|
return res;
|
|
279
311
|
}
|
|
280
312
|
async json(path, check) {
|
|
@@ -379,6 +411,7 @@ function createClient(config) {
|
|
|
379
411
|
const attempts = list.length + (config.retries ?? 3);
|
|
380
412
|
const call = (method) => async (...args) => {
|
|
381
413
|
let last;
|
|
414
|
+
let answered = true;
|
|
382
415
|
for (let i = 0; i < attempts; i++) {
|
|
383
416
|
const c = list[i % list.length];
|
|
384
417
|
const fn = c[method];
|
|
@@ -387,9 +420,11 @@ function createClient(config) {
|
|
|
387
420
|
return await fn.apply(c, args);
|
|
388
421
|
} catch (e) {
|
|
389
422
|
last = e;
|
|
423
|
+
answered &&= isAnsweredRefusal(e);
|
|
390
424
|
}
|
|
391
425
|
}
|
|
392
|
-
|
|
426
|
+
const err = new import_core5.NetworkError(`All BTC clients failed for ${method}`, { cause: last });
|
|
427
|
+
throw answered && last !== void 0 ? refusal(err) : err;
|
|
393
428
|
};
|
|
394
429
|
const out = {};
|
|
395
430
|
for (const m of METHODS) out[m] = call(m);
|
|
@@ -610,11 +645,28 @@ function describeTransaction(tx, inputValues, ctx) {
|
|
|
610
645
|
});
|
|
611
646
|
const outputs = [];
|
|
612
647
|
const warnings = /* @__PURE__ */ new Set();
|
|
648
|
+
const scriptOf = (address) => {
|
|
649
|
+
try {
|
|
650
|
+
return import_base2.hex.encode(decodeAddress(address, ctx.network).script);
|
|
651
|
+
} catch {
|
|
652
|
+
return void 0;
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
const ownScript = scriptOf(ctx.ownAddress);
|
|
656
|
+
const intendedScript = ctx.intended ? scriptOf(ctx.intended.to) : void 0;
|
|
657
|
+
let recipientSeen = false;
|
|
613
658
|
for (let i = 0; i < tx.outputsLength; i++) {
|
|
614
659
|
const out = tx.getOutput(i);
|
|
615
660
|
const value = out.amount ?? 0n;
|
|
616
661
|
const address = tx.getOutputAddress(i, ctx.network);
|
|
617
|
-
const
|
|
662
|
+
const script = out.script ? import_base2.hex.encode(out.script) : void 0;
|
|
663
|
+
const mine = script !== void 0 && script === ownScript;
|
|
664
|
+
let role;
|
|
665
|
+
if (ctx.intended === void 0) role = mine ? "change" : "recipient";
|
|
666
|
+
else if (!recipientSeen && script !== void 0 && script === intendedScript && value === ctx.intended.value) {
|
|
667
|
+
role = "recipient";
|
|
668
|
+
recipientSeen = true;
|
|
669
|
+
} else role = mine ? "change" : "unknown";
|
|
618
670
|
if (out.script && value < dustLimit(scriptKind(out.script))) warnings.add("dust-output");
|
|
619
671
|
outputs.push({ address, value, role });
|
|
620
672
|
}
|
|
@@ -1045,8 +1097,7 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
|
|
|
1045
1097
|
fee = built.fee;
|
|
1046
1098
|
}
|
|
1047
1099
|
this.checkMax(fee, (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
|
|
1048
|
-
|
|
1049
|
-
return { hash: txid || hash, fee };
|
|
1100
|
+
return { hash: await this.broadcast(client, rawHex, hash), fee };
|
|
1050
1101
|
}
|
|
1051
1102
|
/** Native BTC only (`token` is rejected). Subject to `transferMaxFee`. */
|
|
1052
1103
|
async transfer(o, writeOptions = {}) {
|
|
@@ -1055,8 +1106,21 @@ var WalletAccountBtc = class _WalletAccountBtc extends WalletAccountReadOnlyBtc
|
|
|
1055
1106
|
const client = this.requireClient("transfer");
|
|
1056
1107
|
const built = await this.signSpend({ to: o.recipient, value: o.amount });
|
|
1057
1108
|
this.checkMax(built.fee, (0, import_core9.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
|
|
1058
|
-
|
|
1059
|
-
|
|
1109
|
+
return { hash: await this.broadcast(client, built.tx.hex, built.tx.id), fee: built.fee };
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Send once through the client. Only a node's refusal means nothing went out; "already in the
|
|
1113
|
+
* mempool / block chain" means an earlier attempt delivered it; anything else may have landed and
|
|
1114
|
+
* is a {@link BroadcastUnknownError} with the txid computed before sending (round 3, M12).
|
|
1115
|
+
*/
|
|
1116
|
+
async broadcast(client, rawHex, txid) {
|
|
1117
|
+
try {
|
|
1118
|
+
return await client.broadcast(rawHex) || txid;
|
|
1119
|
+
} catch (e) {
|
|
1120
|
+
if (isAlreadyKnown(e)) return txid;
|
|
1121
|
+
if (isAnsweredRefusal(e)) throw e;
|
|
1122
|
+
throw new BroadcastUnknownError(txid, e);
|
|
1123
|
+
}
|
|
1060
1124
|
}
|
|
1061
1125
|
/** The same address as a {@link WalletAccountReadOnlyBtc}. */
|
|
1062
1126
|
toReadOnlyAccount() {
|