Auditor’s Guide
For nerds, auditors, and the professionally suspicious. The deep version of how it works. Everything here is checkable; where it isn’t yet, it says so. Live addresses and backing status: trust & verification.
1 · What Is On-Chain, Exactly
One Scrypto component (guild-marketplace-escrow, Radix mainnet) enforces a six-state task lifecycle. The app (Next.js + Postgres) is a mirror and a convenience — the ledger is the source of truth, and a reconciler heals the database from chain events, never the reverse.
Open ──claim──▶ Claimed ──submit──▶ Submitted ──approve──▶ Released (terminal) │ │ │ cancel expire/cancel dispute ──resolve/auto──▶ Released | Refunded (terminal) ▼ ▼ Refunded (terminal)
| Transition | Caller | Auth | Time gate |
|---|---|---|---|
| create_task | poster | — (funds reward + insurance atomically) | — |
| claim_task | worker | worker badge Proof + claim bond; asserts worker != poster — an honest-mistake guard, not a self-dealing defence: poster is a caller-supplied parameter of create_task, so a decoy poster address bypasses it for the cost of gas | sets submit deadline (7d human / 1d agent) |
| cancel_task | poster | task-receipt Proof (not burned) | Open only; credits reward + insurance to the poster's entitlement — moves nothing to the caller |
| cancel_task_by_poster_after_claim | poster | task-receipt Proof (not burned) | Claimed only; credits reward + insurance to the poster and the claim bond back to the worker's entitlement |
| expire_claim | anyone | PUBLIC | after submit deadline + grace window (1h deployed); reopens the task and returns a bounty (expire_bounty_pct of the forfeited bond) to the caller, the rest to the owner's forfeited-bond vault |
| submit_task | worker | claim-receipt burn (one-shot) + evidence hash committed | — |
| approve_and_release | poster | task-receipt Proof (resource + local-id match). NOT burned: under pull the task receipt is a PERSISTENT entitlement key, retired separately via burn_task_receipt once both lanes are zero | credits per-party entitlements inside the component; moves nothing to the caller |
| release_after_review_timeout | anyone | PUBLIC | Submitted only, once review_deadline (submitted_at + review_window_secs, pinned at submit; 72h deployed) has passed; credits exactly what approve_and_release credits. Races raise_dispute first-to-commit |
| raise_dispute | poster or worker | receipt/badge Proof + evidence hash | — |
| resolve_dispute | arbiter | arbiter badge | ruling: PayWorker / RefundPoster / Split |
| auto_resolve_dispute | anyone | PUBLIC | after the dispute window pinned at raise_dispute (7 days on the live component since the 2026-10-11 owner change; 72h before it) |
| withdraw_worker | worker | worker (or agent) badge Proof — asserts the badge is the SAME non-fungible local id that claimed the task | deposits BOTH lanes (reward + bond) into task.worker_account, PINNED AT CLAIM — the caller cannot name the destination |
| withdraw_poster | poster | task-receipt Proof (persistent, not one-shot) | deposits BOTH lanes into task.poster, PINNED AT create_task — the caller cannot name the destination |
| push_entitlement | anyone | PUBLIC — no Proof, no destination argument | deposits a party's settled entitlement into the account PINNED for that party, through the same deposit path as withdraw_*; the caller pays the fee and receives nothing |
| withdraw_forfeited_bonds | owner | OWNER badge | returns the forfeited-bond vault for one resource to the caller; never touches a task's reward or insurance |
| burn_task_receipt | poster | task-receipt Bucket (this one IS burned) | TWO guards, both required: both poster entitlement lanes read zero, AND the task is terminal (Released | Refunded) — the second exists because on an Open task both lanes are legitimately zero, and burning there would strand the whole funded reward |
Auth pattern worth auditing, and settlement is the part to read twice — it is two-phase. approve_and_release, auto_resolve_dispute and the cancel paths move no money outward: they drain the escrow vaults into per-party entitlements held inside the component. Three methods do return a Bucket to their caller, and none of them can reach a task's reward: resolve_dispute returns the arbiter's fee (taken from the insurance, capped per task by max_arbiter_fee_pct, callable only with the arbiter badge), expire_claim returns a bounty of expire_bounty_pct of the forfeited claim bond to whoever calls it once the claim deadline and its grace window (expire_grace_secs) have passed, and the OWNER-only withdraw_forfeited_bonds returns the rest of forfeited bonds. Entitlements leave only through withdraw_worker / withdraw_poster and the public push_entitlement, and each deposits from inside the component into an account pinned earlier — the worker's at claim_task, the poster's at create_task — because deposit_both_lanes calls try_deposit_or_abort on that stored address. The caller never names that destination, so those three being callable by the payee or by anyone moves no value to whoever calls them. That pin is the property worth attacking: the worker badge and the task receipt are both transferable bearer instruments (neither resource calls withdraw_roles!, so both inherit the SDK default withdrawer=AllowAll), so stealing one lets you CALL the withdrawal — and the money still lands in the pinned account. Public-mint governs forging a credential, transferability governs stealing one, and the payee pin is what makes the second one not pay. On receipts, the two differ: the claim receipt is one-shot and burned at submit_task, but the task receipt is NOT burned at approval — under pull it is a persistent entitlement key presented as a Proof, retired explicitly via burn_task_receipt once both poster lanes read zero and the task is terminal. Recurring acts use Proofs; safety releases (expire_claim, auto_resolve_dispute, release_after_review_timeout) are PUBLIC and time-gated. push_entitlement takes no Proof and no destination: anyone may call it to deliver a settled entitlement to its pin, so a lost or unnoticed credential does not strand the money (it still aborts if the pinned account refuses the deposit), and a hostile caller only pays the network fee to pay a stranger their own funds. One consequence worth auditing: deposit_both_lanes coerces the payee to an Account on every path that pays out, push_entitlement included, so a component (a funding pool, say) cannot yet be a payee.
2 · The Trust Claims, and How to Check Each One
3 · Economics (Deployed → Planned)
Claim bond 10% of reward (floor 76.45 XRD; the percentage, floor and cap are all owner settings); dispute window 7 days (an owner setting, raised from 72h on 2026-10-11; each dispute keeps the window in force when it was raised), default SplitEvenly; arbiter fee 0 (cap 10%). Ground truth per component address — see trust & verification. Insurance is not in that list: the component's min_insurance_fraction is 0 — an owner dial, not a floor you can rely on — and the 5% locked on every task here is this app's policy, applied when it builds the funding manifest.
Insurance becomes optional dispute coverage (~10–15% suggested, refunded if unused; no coverage ⇒ no dispute path — pure optimistic mode); SplitEvenly on dispute abandonment + reputation marks; mutually-signed splits (settle_by_agreement).
Two corrections (2026-08-06). This paragraph read “heartbeat removed in favor of deadline + mutual extension”. The second half was never true: heartbeat is removed outright — one claim deadline, fixed at claim time, no paid extensions and no extension method at all. Auto-release was refused for four months on a faucet objection (2026-08-04): submit_task validated nothing on its own, so a timed release without more would have paid out garbage submissions. The live blueprint (deployed 2026-09-13) answered that, not by changing submit_task’s validation, but by what now surrounds it: a non-zero evidence commitment bound to the committed brief, disputes live so a poster’s remedy is real, and the claim bond held to settlement so a garbage submission stakes real value once a dispute is raised. On that basis release_after_review_timeout is deployed and PUBLIC today. settle_by_agreement is still deferred. This is now the deployed reality — the live component has no heartbeat leg.
4 · Honest Gaps (the Register)
| Element | Status | Best we have |
|---|---|---|
| Per-criterion enforced payouts | No shipped precedent, anywhere | Checklist-as-evidence routes to: full release / revision / mutual split / insured arbitration |
| Co-funder voting on acceptance | Every attempt died or went unused | Curator-pattern pools (named acceptor, self-claim refunds, escrow-level timers protect the worker) |
| Subjective quality judgment | Unsolvable in general | Committed brief + insured human arbiter + (planned) AI advisory opinion |
| Agent work verification | Standards in flux industry-wide | On-demand PR checks against the committed brief, plus the claim bond and escrow deadlines; the claim receipt is burned at submit, and the claiming badge is re-checked at withdraw. But the badge is a public mint that identifies no one, and of the recall-revocable agent badge this model assumes, three have been minted and two burned: the one that exists is held by the Guild's own worker agent, and none has ever been presented on a claim — so no human currently answers for an agent. |
If you have a better mechanism for any of these: post it as a task. That is not a slogan; it is the product working on itself.
5 · Identity and Backing (Pseudonymous, With Receipts)
The operator is pseudonymous (bigdev / @bigdevxrd) with a verifiable on-chain track record. In lieu of doxxing, two backing steps: publishing the escrow blueprint source (done: it is public under Apache-2.0) with reproducible-build verification (planned), and a bug bounty paid through the Guild’s own escrow (planned; on-chain, visible). Status of each: trust & verification. Trustee-verified identity for tasks/projects over $50k USD is planned, not available yet: a named third party would attest the operator’s identity and standing without public disclosure. No trustee is retained.
6 · Known Limits — Read Before Relying
- —Blueprint upgrades are migrations (a new component and an env swap), never in-place: the code behind a component address does not change. Its settings can — the owner badge can move the parameters listed under the owner's powers above. Most are pinned into a task at the step that uses them, so a change reaches only tasks that get there afterwards; the two expiry settings apply to claims already in flight. Read the live values per component address, not from this page.
- —There is one arbiter badge today (supply 1; the operator can mint more) and the operator holds it. The deployed blueprint has no multi-arbiter rule to switch on: a panel with recall and assignment is a next-blueprint design, not a setting.
- —The app and the escrow's Scrypto source are public (github.com/radixguild/guild, Apache-2.0): the task-escrow and NftSwap blueprints, and a deprecated earlier escrow package kept for reference. Reproducible-build verification, which would tie that source to the deployed packages, is still planned.
- —This is experimental software on mainnet. The honest-gaps register above is live, not historical.