From 3ecb137cc4300e60f00fbb575441f34e01839a8e Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 15 Jun 2026 12:56:36 -0700 Subject: Apply WS tx payload instantly; faster reconcile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mempool.space WebSocket push already contains the transaction, so parse it on arrival to add the incoming UTXO and bump pending immediately, instead of waiting on a debounced re-scan. The scan still follows (400ms) to reconcile spends/confirmations. Removes the ~3s our handler added on top of mempool.space's own push latency. (Measured: mempool.space itself takes ~8s to see a coinos-broadcast tx due to p2p propagation — the real fix for incoming-payment latency is the sender pushing directly to the explorer.) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wallet.js | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/wallet.js b/src/wallet.js index 2f9fed7..0d24d61 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -490,6 +490,8 @@ export class Wallet { msg['block-transactions'] || msg['txConfirmed'] ) { + // Update instantly from the pushed tx data, then reconcile via a scan. + this._ingestWs(msg); this._scheduleRefresh(); } }; @@ -514,6 +516,44 @@ export class Wallet { } } + // Optimistically apply transactions from a WS push so an incoming deposit + // shows the instant mempool.space pushes it — no extra round-trip. The + // debounced scan that follows reconciles everything (spends, confirmations). + _ingestWs(msg) { + const txs = []; + const multi = msg['multi-address-transactions']; + if (multi && typeof multi === 'object') { + for (const addr of Object.keys(multi)) { + const g = multi[addr] || {}; + for (const t of g.mempool || []) txs.push(t); + for (const t of g.confirmed || []) txs.push(t); + } + } + const single = msg['address-transactions']; + if (Array.isArray(single)) txs.push(...single); + + let changed = false; + const have = new Set(this.utxos.map(utxoId)); + for (const tx of txs) { + if (!tx || !Array.isArray(tx.vout)) continue; + tx.vout.forEach((vo, i) => { + const a = vo && vo.scriptpubkey_address; + if (!a || !this.addrMap.has(a)) return; + const id = `${tx.txid}:${i}`; + if (have.has(id)) return; + const p = this.addrMap.get(a); + const confirmed = !!(tx.status && tx.status.confirmed); + this.utxos.push({ txid: tx.txid, vout: i, value: vo.value, address: a, chain: p.chain, index: p.index, confirmed }); + have.add(id); + if (confirmed) this.confirmed += vo.value; + else this.pending += vo.value; + changed = true; + }); + } + if (changed) this.emit(); + return changed; + } + _scheduleReconnect() { if (!this._wsWant) return; clearTimeout(this._wsRetry); @@ -528,7 +568,7 @@ export class Wallet { await this.scan({ silent: true }); this.retrack(); } catch {} - }, 800); + }, 400); } stopRealtime() { -- cgit v1.2.3