Brings the whole project into git (previously only the on-ramp feature and a few parallel files were tracked). Includes: wallet core (src/), CashScript contracts/, the Capacitor shell (shells/), scripts/, tests, and the docs corpus (wiki/, snapshots/, Parameters/, Archive/, KnowledgeBase text). Excluded via .gitignore: node_modules/, dist/, .env (secrets), testnet WIF key files (.chipnet-*.json, devWallets.ts), KnowledgeBase/RAW media (~9.6 GB), transcripts/ (session logs), and machine-local config (.claude/*.local, *.lock). Secret-scanned before commit — no credentials in the tree. Note: src/swapMath.ts, src/swapBuilder.ts, contracts/OpenMintToken.cash are legacy (OP_RETURN era) but still imported; committed as-is, flagged for a separate removal once de-referenced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.5 KiB
JavaScript
91 lines
3.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* chipnet-doublespend-recheck.mjs — poll the two conflicting Test-2 txids until
|
|
* a block settles the race, then report the winner and confirm the loser was
|
|
* dropped. Companion to chipnet-doublespend-test.mjs.
|
|
*
|
|
* Run: node --import tsx scripts/chipnet-doublespend-recheck.mjs <txidA2> <txidB2>
|
|
* Env: POLL_SECONDS=2400 (default 40 min)
|
|
*/
|
|
import { ElectrumClient } from '@electrum-cash/network';
|
|
|
|
const POLL_SECONDS = Number(process.env.POLL_SECONDS ?? '2400');
|
|
const [idA, idB] = process.argv.slice(2);
|
|
if (!idA || !idB) {
|
|
console.error('usage: ... chipnet-doublespend-recheck.mjs <txidA2> <txidB2>');
|
|
process.exit(1);
|
|
}
|
|
const HOSTS = ['chipnet.bch.ninja', 'chipnet.imaginary.cash'];
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
async function rpc(client, method, ...params) {
|
|
try {
|
|
const res = await client.request(method, ...params);
|
|
if (res instanceof Error) return { ok: false, error: res.message ?? String(res) };
|
|
return { ok: true, value: res };
|
|
} catch (err) {
|
|
return { ok: false, error: err?.message ?? String(err) };
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const clients = [];
|
|
for (const h of HOSTS) {
|
|
const c = new ElectrumClient('Deviant-DSrecheck', '1.4.1', h, { disableBrowserVisibilityHandling: true });
|
|
await c.connect();
|
|
clients.push([h, c]);
|
|
}
|
|
console.log(`Polling up to ${POLL_SECONDS}s for the race to settle.`);
|
|
console.log(` txA2=${idA}`);
|
|
console.log(` txB2=${idB}`);
|
|
|
|
const deadline = Date.now() + POLL_SECONDS * 1000;
|
|
let result = null;
|
|
while (Date.now() < deadline) {
|
|
for (const [host, c] of clients) {
|
|
for (const [tag, id] of [['A2', idA], ['B2', idB]]) {
|
|
const g = await rpc(c, 'blockchain.transaction.get', id, true);
|
|
if (g.ok && g.value && typeof g.value === 'object' && (g.value.confirmations ?? 0) >= 1) {
|
|
result = { tag, id, host, conf: g.value.confirmations, block: g.value.blockhash };
|
|
}
|
|
}
|
|
}
|
|
if (result) break;
|
|
await sleep(20000);
|
|
process.stdout.write('.');
|
|
}
|
|
console.log('');
|
|
|
|
if (!result) {
|
|
console.log(`No block within ${POLL_SECONDS}s — race still pending. Re-run later.`);
|
|
} else {
|
|
const loserId = result.tag === 'A2' ? idB : idA;
|
|
const loserTag = result.tag === 'A2' ? 'B2' : 'A2';
|
|
// Probe the loser on both nodes.
|
|
let loserState = 'UNKNOWN to node (dropped from mempool)';
|
|
for (const [, c] of clients) {
|
|
const lg = await rpc(c, 'blockchain.transaction.get', loserId, true);
|
|
if (lg.ok && lg.value && typeof lg.value === 'object') {
|
|
loserState = `${lg.value.confirmations ?? 0} conf`;
|
|
}
|
|
}
|
|
console.log('==================== RACE SETTLED ====================');
|
|
console.log(` WINNER : tx${result.tag} ${result.id}`);
|
|
console.log(` ${result.conf} conf, block ${result.block ?? '(n/a)'} (seen on ${result.host})`);
|
|
console.log(` LOSER : tx${loserTag} ${loserId}`);
|
|
console.log(` ${loserState}`);
|
|
console.log('');
|
|
if (String(loserState).startsWith('UNKNOWN')) {
|
|
console.log(' ✅ Exactly ONE confirmed; the conflicting double-spend was DROPPED.');
|
|
console.log(' 0-conf double-spend FAILED — first-seen held across the two-node race.');
|
|
} else {
|
|
console.log(' ⚠️ Loser still present — inspect manually (unexpected).');
|
|
}
|
|
console.log('=====================================================');
|
|
}
|
|
|
|
for (const [, c] of clients) await c.disconnect();
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch((e) => { console.error('FAILED:', e?.message ?? e); process.exit(1); });
|