diff options
| author | Adam Soltys <[email protected]> | 2026-06-25 13:28:45 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-25 13:28:45 -0700 |
| commit | 96b0ebcf370d7d88fcf515238ce6b9f363ec8265 (patch) | |
| tree | 4d0e113a868b604f535c71b9ea5602c9da7cc531 /tools | |
| parent | a2bb363e2c86dc845e6748e046c8d489e656c584 (diff) | |
| download | coinosv3-96b0ebcf370d7d88fcf515238ce6b9f363ec8265.tar.gz coinosv3-96b0ebcf370d7d88fcf515238ce6b9f363ec8265.zip | |
Credit payments instantly from the watcher push (no REST on critical path)
The watcher push now carries the tx data (matched outputs + confirmed flag), so
the app credits an incoming payment the moment the push arrives — no mempool.space
round-trip. This removes the rate-limited REST reconcile from the path a user
actually waits on, and lets us back the frontier poll way off when the socket is
live (30s backstop vs the old 3s receive-tab hammer), cutting the 429s to a
trickle.
- watcher: notification includes { txid, confirmed, outputs:[{vout,value}] }.
- wallet: _applyTxNotification credits the receive coin + a pending history row
optimistically (receives only; change reconciles in the background), advances
the frontier, updates the balance, and schedules a background reconcile for
full accuracy off the critical path.
- poll intervals relaxed now that the WS does the real work instantly.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/zmq-watcher.js | 40 |
1 files changed, 26 insertions, 14 deletions
diff --git a/tools/zmq-watcher.js b/tools/zmq-watcher.js index 55cb399..80d8f88 100644 --- a/tools/zmq-watcher.js +++ b/tools/zmq-watcher.js @@ -121,25 +121,37 @@ const startSub = (host, port, topic, onMessage) => new Promise((resolve) => { const TX_OPTS = { allowUnknownOutputs: true, allowUnknownInputs: true, disableScriptCheck: true }; -// Push a notification to every client watching a scripthash this tx pays to. -const notifyForTx = (raw) => { +// Push a data-carrying notification to every client watching a scripthash this +// tx pays. The notification includes the matched outputs (vout + value) and the +// confirmed flag, so the client can credit the payment WITHOUT a REST round-trip +// — keeping the rate-limited explorer off the critical path. `confirmed` is true +// when the tx came from a block (rawblock), false from the mempool (rawtx). +const notifyForTx = (raw, confirmed) => { let tx; try { tx = Transaction.fromRaw(raw, TX_OPTS); } catch { return; } const txid = tx.id; - const hit = new Set(); + const byScript = new Map(); // scripthash -> [{ vout, value }] for (let i = 0; i < tx.outputsLength; i++) { - let script; - try { script = tx.getOutput(i).script; } catch { continue; } - if (!script) continue; - const sh = scripthashOf(script); - if (subs.has(sh)) hit.add(sh); + let o; + try { o = tx.getOutput(i); } catch { continue; } + if (!o || !o.script) continue; + const sh = scripthashOf(o.script); + if (!subs.has(sh)) continue; + if (!byScript.has(sh)) byScript.set(sh, []); + byScript.get(sh).push({ vout: i, value: Number(o.amount) }); } - for (const sh of hit) { + for (const [sh, outputs] of byScript) { const set = subs.get(sh); if (!set) continue; - // Status param is the txid — the client ignores its value and just refreshes. - const note = JSON.stringify({ jsonrpc: '2.0', method: 'blockchain.scripthash.subscribe', params: [sh, txid] }) + '\n'; - for (const ws of set) { try { ws.send(note); } catch {} } + const note = JSON.stringify({ + jsonrpc: '2.0', + method: 'blockchain.scripthash.subscribe', + params: [sh, txid], + data: { txid, confirmed: !!confirmed, outputs }, + }) + '\n'; + let sent = 0; + for (const ws of set) { try { ws.send(note); sent++; } catch {} } + console.log(new Date().toISOString(), 'notify', sh.slice(0, 12), 'tx', txid.slice(0, 12), confirmed ? '(conf)' : '(mempool)', '->', sent); } }; @@ -180,7 +192,7 @@ const handleRawBlock = (raw) => { const size = rawTxSize(raw, offset); const txRaw = raw.subarray(offset, offset + size); offset += size; - try { notifyForTx(txRaw); } catch {} + try { notifyForTx(txRaw, true); } catch {} } }; @@ -194,7 +206,7 @@ const retry = async (host, port, topic, handler) => { } }; -retry(BC_HOST, RAWTX_PORT, 'rawtx', notifyForTx); +retry(BC_HOST, RAWTX_PORT, 'rawtx', (raw) => notifyForTx(raw, false)); retry(BC_HOST, RAWBLOCK_PORT, 'rawblock', handleRawBlock); // ---- minimal Electrum WS server ------------------------------------------- |
