duly / scripts/verify-mainnet.mjs ·
raw #!/usr/bin/env node// Re-checks Duly's mainnet evidence (evidence/mainnet.js) against public RPCs, with// no keys, no account and nothing of ours in the path. Run: node scripts/verify-mainnet.mjs// Exits 0 only if every check passes. Needs Node 22 or later; takes about 20 seconds.import { MAINNET as E, NEARBLOCKS_TX, BLOCKSCOUT_TX } from '../evidence/mainnet.js';// Old transactions need archival nodes; current state comes from regular ones.const NEAR_ARCHIVAL = ['https://archival-rpc.mainnet.fastnear.com', 'https://archival-rpc.mainnet.near.org'];const NEAR_RPC = ['https://free.rpc.fastnear.com', 'https://rpc.mainnet.near.org', 'https://rpc.mainnet.fastnear.com'];const BASE_RPC = ['https://mainnet.base.org', 'https://base-rpc.publicnode.com', 'https://base.llamarpc.com'];const TRANSFER = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';const MAX_BYTES = 4 * 1024 * 1024;let passed = 0;let failed = 0;function check(what, ok, detail = '') { console.log(` ${ok ? 'PASS' : 'FAIL'} ${what}${detail ? ` (${detail})` : ''}`); if (ok) passed++; else failed++;}/** JSON-RPC with fallbacks: an endpoint that errors or times out hands over to the next. */async function rpc(endpoints, method, params) { let last; for (const url of endpoints) { for (let attempt = 0; attempt < 2; attempt++) { try { const res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), signal: AbortSignal.timeout(20000), }); const text = await res.text(); if (text.length > MAX_BYTES) throw new Error('response too large'); const body = JSON.parse(text); if (body.error) throw new Error(JSON.stringify(body.error).slice(0, 200)); return body.result; } catch (e) { last = e; } } } throw new Error(`${method} failed on every endpoint: ${last?.message}`);}const nearView = async (account, methodName, args = {}) => { const r = await rpc(NEAR_RPC, 'query', { request_type: 'call_function', finality: 'final', account_id: account, method_name: methodName, args_base64: Buffer.from(JSON.stringify(args)).toString('base64'), }); return JSON.parse(Buffer.from(r.result).toString());};const viewAccount = (account) => rpc(NEAR_RPC, 'query', { request_type: 'view_account', finality: 'final', account_id: account });const txStatus = (hash, signer) => rpc(NEAR_ARCHIVAL, 'EXPERIMENTAL_tx_status', { tx_hash: hash, sender_account_id: signer, wait_until: 'NONE' });const ok = (status) => status && ('SuccessValue' in status || 'SuccessReceiptId' in status);const events = (outcome) => outcome.outcome.logs .filter((l) => l.startsWith('EVENT_JSON:')) .map((l) => { try { return JSON.parse(l.slice('EVENT_JSON:'.length)); } catch { return null; } }) .filter(Boolean);/** 1000000 -> "1.00", 252579 -> "0.252579": exact, at least two decimals. */const usdc = (u) => { const n = BigInt(u); const frac = (n % 1_000_000n).toString().padStart(6, '0').replace(/0+$/, '').padEnd(2, '0'); return `${n / 1_000_000n}.${frac}`;};function innerCalls(tx) { const calls = []; for (const a of tx.transaction.actions) { if (a.Delegate) { const d = a.Delegate.delegate_action; for (const ia of d.actions) if (ia.FunctionCall) calls.push({ receiver: d.receiver_id, sender: d.sender_id, method: ia.FunctionCall.method_name }); } else if (a.FunctionCall) { calls.push({ receiver: tx.transaction.receiver_id, sender: tx.transaction.signer_id, method: a.FunctionCall.method_name }); } } return calls;}async function main() { console.log(`Duly mainnet evidence, re-checked from public RPCs (${new Date().toISOString()})\n`); console.log('1. The contract is the code this repository describes'); const acct = await viewAccount(E.contract); check(`${E.contract} runs code hash ${E.codeHash}`, acct.code_hash === E.codeHash, acct.code_hash); console.log('\n2. A wallet-free merchant and a limited agent key'); const m = await viewAccount(E.merchant); check('merchant account holds at most 1 yoctoNEAR (10^-24 NEAR)', BigInt(m.amount) <= 1n, `${m.amount} yoctoNEAR`); const keys = (await rpc(NEAR_RPC, 'query', { request_type: 'view_access_key_list', finality: 'final', account_id: E.agent })).keys; const fc = keys.map((k) => k.access_key.permission.FunctionCall).filter(Boolean); check(`${E.agent} has a function-call key limited to ${E.contract}: offer_discount, agent_sweep`, fc.some((p) => p.receiver_id === E.contract && [...p.method_names].sort().join() === 'agent_sweep,offer_discount'), `${fc.length} function-call key(s)`); console.log('\n3. Every NEAR transaction succeeded and does what the evidence says'); const settled = {}; for (const t of E.near) { let tx; try { tx = await txStatus(t.hash, t.signer); } catch (e) { check(`${t.label}`, false, e.message); continue; } const link = NEARBLOCKS_TX(t.hash); const signerOk = tx.transaction.signer_id === t.signer; const success = ok(tx.status); const ours = tx.receipts_outcome.filter((o) => o.outcome.executor_id === E.contract); const oursOk = ours.every((o) => !('Failure' in o.outcome.status)); let extra = true; let note = link; if (t.method) { const calls = innerCalls(tx); extra = calls.some((c) => c.method === t.method && c.receiver === E.contract && c.sender === E.merchant) && ours.length > 0 && oursOk; note = `${t.method} signed by the merchant, gas paid by ${t.signer}; ${link}`; } if (t.key === 'deploy') { const kinds = tx.transaction.actions.map((a) => Object.keys(a)[0]); extra = kinds.includes('DeployContract') && tx.transaction.receiver_id === E.contract; } if (t.key === 'account') { extra = tx.transaction.receiver_id === E.merchant && tx.transaction.actions.some((a) => a === 'CreateAccount' || a.Transfer || a.CreateAccount); } if (t.settles) { const pay = ours.flatMap(events).find((ev) => ev.standard === 'duly' && ev.event === 'invoice_payment' && ev.data?.[0]?.id === t.settles)?.data[0]; const inv = E.invoices.find((i) => i.id === t.settles); const split = (pay?.set_asides || []).reduce((s, x) => s + BigInt(x.amount), 0n); const delivered = tx.receipts_outcome .filter((o) => o.outcome.executor_id === E.usdc) .flatMap(events) .some((ev) => ev.event === 'ft_transfer' && ev.data?.some((d) => d.old_owner_id === 'intents.near' && d.new_owner_id === E.contract && d.amount === inv.amount)); extra = !!pay && pay.closed === true && pay.applied === inv.amount && split === BigInt(inv.setAside) && delivered && oursOk; note = pay ? `ft_on_transfer logged invoice_payment: ${usdc(pay.applied)} USDC applied, closed=${pay.closed}, ${usdc(split.toString())} set aside in the same receipt; ${link}` : `no invoice_payment event for ${t.settles}; ${link}`; settled[t.settles] = true; } check(t.label, signerOk && success && extra, note); } console.log('\n4. Every Base transaction succeeded and moved the USDC the evidence says'); const blocks = {}; for (const t of E.base) { let r; try { r = await rpc(BASE_RPC, 'eth_getTransactionReceipt', [t.hash]); } catch (e) { check(t.label, false, e.message); continue; } const moved = (r?.logs || []).find( (l) => l.address.toLowerCase() === E.baseUsdc && l.topics[0] === TRANSFER && `0x${l.topics[2].slice(-40)}` === t.to && (!t.from || `0x${l.topics[1].slice(-40)}` === t.from) && BigInt(l.data) === BigInt(t.amount), ); blocks[t.key] = r?.blockNumber; check(t.label, r?.status === '0x1' && !!moved, `${usdc(t.amount)} USDC to ${t.to.slice(0, 10)}..., ${BLOCKSCOUT_TX(t.hash)}`); } console.log('\n5. The contract state today matches'); for (const want of E.invoices) { const inv = await nearView(E.contract, 'get_invoice', { id: want.id }); const same = inv && inv.amount === want.amount && inv.paid === want.paid && inv.set_aside === want.setAside && inv.refunded === want.refunded && inv.status === want.status; check(`invoice ${want.id}: ${want.status}, ${usdc(want.amount)} USDC, ${usdc(want.paid)} paid, ${usdc(want.setAside)} set aside`, !!same || ((want.sample || want.leftOpen) && inv && inv.paid !== '0'), inv ? `${inv.status}, paid ${inv.paid}, set aside ${inv.set_aside}` : 'not found'); if ((want.sample || want.leftOpen) && inv && inv.paid !== '0') console.log(` (open invoice ${want.id} has since been paid by someone: ${inv.status})`); if (want.id === '1-3' && inv && blocks.pay3) { const b = await rpc(BASE_RPC, 'eth_getBlockByNumber', [blocks.pay3, false]); const secs = Number(inv.closed_at_ms) / 1000 - Number(BigInt(b.timestamp)); check(`invoice 1-3 closed ${secs.toFixed(1)} s after the Base block with the client's payment`, Math.abs(secs - E.secondsToClose['1-3']) < 1, `claimed ${E.secondsToClose['1-3']} s`); } } const totals = await nearView(E.contract, 'get_totals'); const held = await nearView(E.usdc, 'ft_balance_of', { account_id: E.contract }); check('the contract holds at least what it owes', BigInt(held) >= BigInt(totals.liabilities), `holds ${usdc(held)} USDC, owes ${usdc(totals.liabilities)}`); const total = passed + failed; if (total === 0) { console.log('\nNo checks ran. That is a failure.'); process.exit(1); } console.log(failed ? `\n${failed} of ${total} checks FAILED.` : `\nAll ${total} checks passed. Nothing here came from a Duly server.`); process.exit(failed ? 1 : 0);}main().catch((e) => { console.error(`verify-mainnet: ${e.message}`); process.exit(2);});