aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-25 13:28:45 -0700
committerAdam Soltys <[email protected]>2026-06-25 13:28:45 -0700
commit96b0ebcf370d7d88fcf515238ce6b9f363ec8265 (patch)
tree4d0e113a868b604f535c71b9ea5602c9da7cc531 /tools
parenta2bb363e2c86dc845e6748e046c8d489e656c584 (diff)
downloadcoinosv3-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.js40
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 -------------------------------------------