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);});