# Verify it yourself The published records let you check specific claims independently; they cannot prove server-run reasoning or the truth of the operating ledger's inputs. Two different things are provable, by two different mechanisms, and this doc walks through both with commands you can run. 1. **The contracts are the code we say they are** - verified on Etherscan. 2. **The payouts are the numbers the rules produce** - recomputed from the published record and checked against what is anchored on chain. The first is "the machine is what it claims to be." The second is "the machine did the arithmetic right, and nobody changed it afterward." You can confirm each independently. --- ## 1. The contracts match this source For a version 3 closed proposal record, download the exact file linked by `/api/proposal/record?id=N` (`/api/proposal/record/frozen?id=N`). Verify its messages, signatures, tallies, hashes and outcome with: ```bash node scripts/verify-proposal.js proposal-N-closed-record.json node scripts/verify-proposal.js proposal-N-closed-record.json --rpc YOUR_RPC_URL --registry YOUR_REGISTRY_ADDRESS ``` `--rpc` and `--registry` must be given together, each with a value. A bare flag is an error, never a silent offline run; the offline output says so in its `mode` field. Before the vote closes, the pre-close commitment is a separate, chain-only check on the deliberation record (`proposal-N-record.json`): ```bash node scripts/verify-proposal.js --commitment proposal-N-record.json --rpc YOUR_RPC_URL --registry YOUR_REGISTRY_ADDRESS ``` It confirms the frozen terms hash to the document hash, the derived registry slot, both anchored hashes, and that `committedAtTime` lies inside the frozen voting window. The second command checks the chain and registry, current closed-record anchor, amendments, original registry tally fields, and the deliberation/prompt commitment timestamp against the frozen window. A superseded record fails current acceptance; use `--historical` explicitly to inspect it. Version 2 closed records require that flag because their earlier commitment does not bind voting terms. Version 1 lacks the signed evidence needed by this verifier. Historical files are never rewritten to invent missing evidence. Signature recovery proves who signed a message; ownership at cast time and server-run reasoning retain their stated trust limits. Once a contract is *verified* on Etherscan, its full Solidity source is shown next to the deployed bytecode, and Etherscan has confirmed they compile to the same thing. From then on, anyone can read exactly what the contract does - it is a check, not a promise. Verify every deployed contract from the deployment record in one command: ```bash ETHERSCAN_API_KEY=... npx hardhat run scripts/verify.js --network mainnet ``` (Use `--network sepolia` for the testnet deployment.) The script reads `contracts/deployments/-.json`, which carries each contract's address **and the exact constructor arguments it was deployed with**, and asks Etherscan to confirm each one. A free Etherscan v2 API key (from `etherscan.io/myapikey`) verifies on every network. What you should see afterward, on each contract's Etherscan page: - a green **Contract Source Code Verified** check, - the same four contracts the record lists: `Disorderly721`, `PayoutDistributor`, `ProposalRegistry`, `RoyaltyRouter`, - constructor arguments matching the record (treasury address, Merkle roots, the router wiring). If verification ever **fails**, that is itself the signal: it means the deployed bytecode does not match this source, and the script says so rather than hiding it. The compiler settings that must match are in `contracts/hardhat.config.js` (Solidity 0.8.24, optimizer 800 runs, `cancun`). The contracts deliberately know nothing about the token: `PayoutDistributor` has no `balanceOf`, no `ownerOf`, no token id. A contract that cannot read holdings cannot be paying anyone for holding - and once verified, you can see that for yourself in the source. --- ## 2. The payouts match the rules Every cycle and every settlement publishes a **record**: the full payout table, each line's basis, and the totals. Two things about that record are anchored on chain by `ProposalRegistry`: - **recordHash** - keccak256 of the canonical record. Change one number and it changes. - **merkleRoot** - the root of the `(cycleId, account, amount)` tree that `PayoutDistributor` was funded with. Every claim is checked against it. You can rebuild both from the record alone and compare: ```bash CHAIN_ID=11155111 RPC_URL=... PROPOSAL_REGISTRY=0x... PAYOUT_DISTRIBUTOR=0x... \ node scripts/verify-cycle.js path/to/record.json ``` It recomputes the Merkle root and the record hash from the amounts in the record, using the same pure functions the site publishes with, then reads the distributor and the registry and compares. Three outcomes, three exit codes: Use `CHAIN_ID=1` for mainnet and its deployed contract addresses. This trusted expectation must match the RPC's actual network and the record's chain when present; historical payout records without a chain field still require it. - `VERIFIED`, exit 0: the distributor is funded with this root and total, the registry holds this record hash, root and total, and both transactions are found. The published record is exactly what was funded and anchored. - `MISMATCH`, exit 1: an anchor was read and differs from the record. - `INCOMPLETE`, exit 2: an anchor is absent or could not be read, or the chain is not configured. Nothing is verified; the recomputed values are printed so you can compare them in Etherscan's read view (`cycles(cycleId)`) yourself. What this tool proves is the anchoring of the published amounts. It does not rerun the fee and commission arithmetic from the raw ledger; `cycle.js preview` and `settle preview` do that from the store, and `server/test-cycle.js` pins the rules. The same check, as one function, is what the supervisor and the manual record commands require before any payout is recorded (`server/chain-verify.js`). `cycleId` is the on-chain key inside the record: a small integer for a governance cycle, a large namespaced number for a recurring settlement. The same tool checks both. ### Check your own claim Your line in the record comes with a Merkle proof (in the `proofs` file published alongside). The distributor verifies it on chain when you claim: ``` PayoutDistributor.claim(cycleId, yourAddress, amountWei, proof) ``` The leaf is `keccak256(keccak256(abi.encode(cycleId, account, amount)))`, and the same `cycleId` is bound into it - so a proof from one distribution can never be replayed against another. If your amount or address were altered, the proof would not verify and the claim would revert. --- ## 3. The deliberation was committed inside the frozen voting window Version 2 deliberation commitments bind the document, dissents, window, quorum, prompt and complete ballot reasoning. Application acceptance requires the `ProposalRegistry.commitDeliberation` timestamp to be inside that frozen window. The contract requires a commitment before publication but does not enforce this off-chain deadline itself. Partial ballots may already be visible before the commit; this is not proof the server never saw any votes or fabricated reasoning. ``` ProposalRegistry.verifyDeliberation(registryId, deliberationHash) -> true ``` where `deliberationHash` is recomputed by `hashDeliberation()` from the published record. For the new format, `registryId` is the decimal uint256 SHA-256 of canonical `{domain:"disorderly-vote-window-v2",proposalId,documentHash}`. Read it from the version 3 closed record; never round it through a JavaScript Number. Each reopened window has a distinct slot. Historical commitments used the logical proposal id. The commit is stored as both a block number and a block timestamp (`deliberations(id).committedAt` / `.committedAtTime`), so its ordering relative to the wall-clock vote deadline in the frozen document is checkable on chain without a block-to-time lookup. ### The closed vote is anchored too After voting closes, the orchestrator writes `/deliberations/proposal---closed-record.json` - the frozen document hash, every ballot with its source (agent / holder-approved / holder), the council tally, the operator signal, the quorum bar in force and the outcome - and the Safe anchors its hash with the tally via `ProposalRegistry.publish(...)`: ``` recordHash = keccak256(canonical JSON of the closed record) // cycle-publish.hashRecord ProposalRegistry.verifyLatest(registryId, recordHash) -> true ProposalRegistry.records(registryId) -> forVotes, againstVotes, abstainVotes, councilCast, passed ``` Two verify functions, deliberately: `verify()` answers "was this hash EVER anchored for this id" - the original or any amendment - so a record that was later corrected still verifies there. `verifyLatest()` answers "is this the CURRENT record". If `amendmentCount(id) > 0`, a correction exists: read its `reasonHash`'s published explanation before trusting either copy. --- ## 4. The whole cycle is permanent, not just the vote Each cycle publishes a **bundle** to Arweave: every mid-cycle artifact embedded in full, each hashed over canonical JSON, with a `bundleHash` over the lot. `verifyCycleBundle` (in `server/cycle-publish.js`) rechecks every artifact hash and then the bundle hash, so a fetched bundle can be confirmed intact with one recomputation and no reference to our server. The per-agent **memory manifest** is published the same way, so observers can compare the recorded memory states. It cannot prove what memory was actually sent to an inference service. --- ## What makes the JS trustworthy The JavaScript runs off chain, so anchoring cannot cryptographically prove "this exact process ran." Two things stand in for that, and they are stronger than a promise: - **The outputs are independently recomputable.** The money math (`server/cycle.js`, `server/ledger.js`, `server/cycle-publish.js`) is written as deterministic pure functions on purpose: ledger in, payout table out, no clock, no randomness. Section 2 is you re-running that math and getting the anchored answer. You are not trusting the code - you are reproducing its result. - **The source is auditable.** When the repository is public, every line above can be read and re-run. Making the repo public is the remaining step that turns "recompute the result" into "and read exactly how it was produced."