Before you run this: what can actually move, and what cannot
The escrow is deployed on Base Sepolia (chainId 84532) and settles in Circle test USDC — funds with no real-world value. Treat everything here as a demonstration of a mechanism, not a production payments rail. The Grader Bazaar Skill is still being hosted and published; this page describes the intended workflow once it is equipped.
The mechanism is real, and you should understand it before pointing it at anything that matters.
When Grain settles an order, an autonomous on-chain transaction either releases USDC to
the seller (a pass verdict) or refunds the buyer (a fail verdict).
In a production deployment on mainnet that would move real value, autonomously, the moment a
signed verdict lands. Settlement is one-shot and irreversible — once an order
leaves the Funded state it cannot be re-settled or rolled back.
Two rules make this safe to operate, and Grain enforces both in code rather than asking you to trust it:
- Never fabricate. The grader signs a verdict only over the actual sha256 of the GLB it judged and the real predicate results. It never invents a pass, a balance, or a transaction hash. If you reproduce a settlement, read the state on-chain — never assert a value you have not verified.
- Confirm before you go live. Funding an order (
fundOrder) is the explicit human/upstream gate: it pulls USDC from the buyer and commits the buyer, seller, and grader write-once. Submitting the verdict afterward is intentionally autonomous (the signed verdict is the gate), so the place to stop and confirm is before you fund a live order, not after.
Verdict authority and custody are different powers held by different code paths. The grader's key can do exactly one thing — sign a binary pass/fail over a pre-funded order. Funds always move to the buyer or seller recorded at funding time; never to the signer, never to whoever submits the transaction, never to an address supplied at settlement. So even a fully compromised grader key cannot exfiltrate a single cent — the worst it can do is mis-grade between two parties you already agreed on. There is no owner, admin, pause, upgrade, setter, or withdraw function: a test asserts the contract's entire public surface is just five functions.
See it in action: grading a bad crate, then a good crate
Call grade_asset with { assetUri, profileId: "game-ready-prop@1" }
(assetUri is a URL or path to the GLB).
You get back { pass, results[], predicateDigest, assetHash, verdict }, where
results is one row per predicate (each { id, label, pass, actual, expected,
evidence }), run in fixed id-sorted order. The profile game-ready-prop@1 runs
in mode all — every predicate must pass.
Round 1 — a bad crate (over budget, open seams, no declared param)
A raw fal Rodin export: over the triangle budget, with seams the exporter left open, and — since
Rodin emits no editable parameters — missing the parameter the buyer requires.
pass: false → the signed verdict
is fail → GrainEscrow refunds the buyer.
| id | label | pass | actual* | expected |
|---|---|---|---|---|
manifold | Watertight | ✗ false | 312 boundary edges | ≤ 0 boundary, 0 non-manifold |
named_params | Named parameters | ✗ false | [] | ["accent_color"] |
tri_count | Triangle budget | ✗ false | 41,980 | ≤ 10,000 |
valid_glb | Valid GLB | ✓ true | 0 errors | ≤ 0 errors |
Round 2 — a good crate (decimated to budget, welded watertight, param attached)
After Grain decimates to budget, welds the mesh watertight, and attaches the declared
accent_color parameter, every row flips to pass.
pass: true → the signed verdict
is pass → GrainEscrow releases payment to the seller.
| id | label | pass | actual* | expected |
|---|---|---|---|---|
manifold | Watertight | ✓ true | 0 boundary edges | ≤ 0 boundary, 0 non-manifold |
named_params | Named parameters | ✓ true | ["accent_color"] | ["accent_color"] |
tri_count | Triangle budget | ✓ true | 9,874 | ≤ 10,000 |
valid_glb | Valid GLB | ✓ true | 0 errors | ≤ 0 errors |
* id, label and expected
are exact; actual values are illustrative of the real evidence shape. Rows render in
id-sorted order — that ordering is what makes predicateDigest reproducible.
Optional: the semantic row
If you also pass intent: "a game-ready wooden crate prop", an opt-in fifth predicate
semantic_match renders the asset to fixed views, CLIP-scores it against the intent
plus distractors, and is folded in as a hard gate — it passes only if the intent
is the top match at or above the threshold (default 0.6). It is deterministic on a
single pinned grader (cross-grader agreement requires pinning the render + CLIP
model versions); the 4-predicate floor stays the disputeless core.
| id | label | pass | actual* | expected |
|---|---|---|---|---|
semantic_match | Matches intent | ✓ true | topMatch: "a game-ready wooden crate prop", intentScore: 0.91 | "a game-ready wooden crate prop" is the top match @ ≥ 0.6 |
The calibration behind that threshold (default
0.6; max-F1 T ≈ 0.5–0.6) comes from honest-v0, an
N=5 benchmark of visually-distinct fal Rodin assets (crate, barrel, vase, helmet,
chair) — it proves the method and the deterministic floor, not the hard/confusable
cases; a larger, harder set is roadmap.
When an escrowId was supplied, the result also carries
verdict: { escrowId, result: "pass", assetHash, escrowAddress: 0xEEb1…78d9, chainId: 84532,
graderSignature } — the exact tuple settleOnAttestation consumes (full escrow
address in §06). The verdict is decided by deterministic code (and the opt-in CLIP check), never by
an LLM guessing; the Grader Mind's Brain only narrates a verdict it did not decide.
Before you start
Grain orchestrates three Minds in one Circle (Requester → Generator → Grader). The platform has no Builder API to set a Mind's DNA, Tenets, or Skills, so setup is part console, part conversation. Everything below runs on testnet — no real money moves.
- A Minds (by Animoca) account. Builder beta at build.hellominds.ai; sign-in is email-based.
- Awaken each Mind via the Concierge. There is no "create Mind" API and no UI form for DNA/Tenets — you "Launch a Mind," the Concierge emails you, and you describe the role in natural language. Awaken three: a Requester, a Generator, and a Grader. Set the role DNA at creation — role-locked Minds work reliably, whereas re-tasking a default "companion" Mind by chat is inconsistent (it tends to defend its identity and refuse).
- Durably seed each role. The sanctioned path is conversational: message the Mind to invoke its own internal
TENET_Updatetool, framing the Grain role as an added operating tenet (not an identity replacement). Identity-level reframing ("you ARE the Grader") trips a guardrail and is refused. - Equip the Grain Skill. The deterministic grader publishes as a Bazaar Skill (a multi-step playbook over Tools), distinct from the Mind's DNA. Build it by describing the outcome to the Mind in chat; edit it later via
REGISTRY_Update/SKILL_Update. (Publish is in progress in this build.) - Add Connections for credentials. Secrets — the grader signing key, any API keys — live platform-side in Connections. The Mind never holds the key, and the private acceptance-profile thresholds (the moat) stay out of chat and out of the public repo.
- Put all three Minds in ONE Circle. Circles are UI-only — there is no Circle-management API.
- Fund cognition on every Mind. Each reply spends cognition; a 0-balance Mind silently times out. Known bug: topping up a brand-new Mind immediately after creation can charge without minting credits — fund after the wallet registers, and hard-refresh (the balance isn't real-time).
- A funded buyer key. Holds Base Sepolia test USDC to call
fundOrderand escrow an order againstGrainEscrow(0xEEb15426d656dCfb76AC058D1A1e66c821cB78d9). - A gas-paying keeper key. Holds Base Sepolia ETH to submit
settleOnAttestation; it has zero fund authority. The Mind's own Base wallet (an EOA) cannot broadcast settlement. - A chosen acceptance profile. The bundled one is
game-ready-prop@1(≤ 10k triangles, watertight/manifold, valid GLB, a required named paramaccent_color; plus an optionalsemantic_matchcheck). The public predicate code is worthless without the private profile thresholds — keep those gitignored.
What Minds can do (and what they can't)
A Mind = Soul + Brain. The Soul holds durable DNA (permanent values, set at awakening), Tenets (editable rules; invariant ones are Guardrails), Memory, State, and a Wallet. The Brain is reasoning, auto-routed across hosted vision-capable models and separate from the Soul — so a Mind keeps its role even when the underlying model changes. Grain adds one thing the platform doesn't have: a deterministic judge.
General Minds capabilities
- Persistent identity + memory — proven live: the Grader Mind, seeded once via
TENET_Update, restated its role and acceptance criteria on a later, separate call. - Natural-language interface — you drive a Mind by messaging it; in Grain this carries the legible "why it was rejected" narration (flavor on top of the deterministic verdict, never the verdict itself).
- Equippable Skills — behavior is a layer separate from DNA: multi-step playbooks over Tools, published to the Bazaar, equippable by other Minds, editable conversationally after publish.
- Autonomous, background operation on a cognition budget — Grain leans on this: settlement releases/refunds on the signed verdict with no human approval.
- Email, Telegram, and Circles — Mind-to-Mind messaging genuinely works (a Generator Mind delivered a unique token to the Grader in a shared Circle, confirmed on the receiver side).
What your Mind can do with Grain
- Grade a delivered GLB against a named profile by calling
grade_asset, and get a per-predicate report —tri_count,manifold,valid_glb,named_params— each with the measuredactual, theexpectedthreshold, andevidence. - Re-grade the same GLB + profile anytime and get a byte-identical
predicateDigest— reproducible and timestamp-free. - Optionally add the opt-in
semantic_matchcheck by supplying anintent, folding "is this actually the right thing?" into the verdict as a hard gate. - Sign an EIP-191 verdict bound to an escrow order so
GrainEscrow.settleOnAttestationcan release (pass) or refund (fail) on-chain — without trusting the counterparty, because the sign-only key can never pay itself. - What it does NOT yet judge: production-readiness (LODs/UVs/PBR budgets), fidelity-to-reference, provenance/IP, parametric editability, and aesthetic taste — these are roadmap, not built. The same rails take a new predicate set per vertical, but only 3D is proven today.
- No Builder API to set DNA / Tenets / Skills — provisioning is Concierge-at-creation or conversational
TENET_Updateonly. - Mind↔Mind traffic is invisible to the Builder API and there's no Circle-management API — which is exactly why Grain ships an observable hybrid controller rather than relying on minds-native messaging.
- The Mind's on-chain wallet can't settle — it's an EOA with no broadcast path; an external keeper submits the transaction.
- Cognition balance (the Mind's thinking-spend) isn't real-time or API-readable — you can't poll it to auto-throttle a Mind's own compute. This is separate from the order budget: the USDC a buyer escrows is set at
fundOrderand fully readable on-chain, so Grain's spend guardrails live there (much as Superior reads trading capital from its own API — not from Minds cognition). - Conversational role-seeding is unreliable on default "companion" Minds — reliable roles must be set as DNA at awakening.
Step-by-step guide
Six moves mirroring the connect → prompt → preview → approve → execute → monitor pattern, adapted to Grain's request → generate → grade → settle loop.
Grain takes no live settlement without an explicit signed gate — here the gate is the grader's EIP-191 verdict, not a human click — but the Mind still surfaces the full evidence before anything settles, and the human gate sits upstream at funding.
Equip the Grain Skill
grade_asset / generate_asset / settle tools are available; calls the hosted grader through the Connection and never holds the key.State the request, budget, and profile
game-ready-prop@1."profileId selects which deterministic predicates fire and their thresholds.Generate a candidate
generate_asset produces a GLB (live path: fal Rodin v2.5) and attaches the declared editable params so named_params is meaningful.assetHash the verdict commits to — are deterministic (params sorted).Grade (signed verdict)
grade_asset parses once, runs the predicates in sorted order (+ semantic_match if an intent is given), composes pass/fail (AND, or weighted per profile), computes predicateDigest, and signs the verdict when an escrowId is supplied.{ pass, results[], predicateDigest, assetHash, verdict }. No escrowId → verdict: null (grade-only).Review the pass/fail + evidence
Confirm settlement (gated), then monitor
settle submits settleOnAttestation(escrowId, result, assetHash, graderSignature): a pass releases test USDC to the seller, a fail refunds the buyer.OrderReleased / OrderRefunded on Base Sepolia.Base & USDC settlement: how a signed verdict releases money on-chain
Grain's settlement layer is a single verification-gated contract, GrainEscrow,
deployed on Base Sepolia (chainId 84532) at
0xEEb15426d656dCfb76AC058D1A1e66c821cB78d9, settling
USDC (Circle test USDC 0x036CbD53842c5426634e7929541eC2318f3dCF7e,
6 decimals). The lifecycle is three moves:
- Fund. The buyer calls
fundOrder(escrowId, seller, grader, amount, specHash). This pullsamountUSDC in and records the order write-once:buyer = msg.sender, theseller, thegrader(the only signer whose verdict counts for this order), and thespecHash. - Grade + sign. Off-chain, the grader signs the verdict. The signature is EIP-191
personal_signover the digest below; the runtime reproduces it byte-for-byte, so the off-chain signature is exactly what the contract recovers. - Settle. Anyone — in practice a keeper — calls
settleOnAttestation(escrowId, result, assetHash, graderSignature). The contract recovers the signer and requires it to equal the order's recordedgrader, else it reverts withbad grader sig. On pass it paysseller; on fail it refundsbuyer.
digest = keccak256(abi.encode(
"Grain settleOnAttestation",
escrowId, uint8(result), assetHash,
address(this), // this exact contract
block.chainid // this exact chain (84532)
))
// signed EIP-191 personal_sign; recovered signer must == order.grader
- The keeper pays gas and has zero authority. Recipients are fixed on-chain, so it can't redirect funds; the runtime treats a reverted-but-included transaction as a hard failure (it waits for the receipt and throws unless
status === 'success'). - The signature can't be replayed. The digest binds the verdict to this order, this result, this exact asset (
assetHash), this contract, and this chain — a pass can't be replayed as a fail, asset A's signature can't settle asset B, and another instance or chain is rejected. Each binding has a dedicated test. - Hardened.
ReentrancyGuard+ checks-effects-interactions (state committed before transfer) + one-shot (theStatus.Fundedguard means a settled order can't settle twice).
Four autonomous rounds (fail → refund, pass → release, fail → refund, pass → release) ran end-to-end against this deployment with no human in the loop, plus the full Hardhat suite covering the custody boundary, signature binding, one-shot, and reentrancy. (An existence proof of the loop, not a reliability statistic; the contract has tests + a live testnet run, not a third-party audit.)
Mainnet path (roadmap, not built). On
mainnet the same contract would point at bridged USDC; no mainnet deployment exists. The settlement
layer is written chain-neutral behind a SettlementAdapter seam, but the EVM adapter is
the only implementation — a Solana adapter is an explicit TODO.
Operating it: verify on-chain first, keep the audit trail, then revoke and stop
Everything you need to manage a live connection is verifiable on-chain — read first, trust nothing you haven't checked.
Verify status by on-chain reads
Read orders(escrowId) and the status byte: 0 None, 1 Funded,
2 Released, 3 Refunded. To confirm a settlement actually happened, watch
the events — OrderReleased and OrderRefunded each carry escrowId,
the recipient, the amount, the assetHash, and the settler (the keeper).
Don't report a settlement off the keeper's optimism; confirm it landed in state and in an event.
Keep the audit trail
assetHash— the sha256 of the exact GLB bytes the grader judged, baked into the signed digest and recorded in the event, so any payout is provably tied to one specific artifact.predicateDigest— a sha256 over the sorted, timestamp- and evidence-excluded predicate results, so re-grading the same asset later proves you got the same verdict.- Keep the
(escrowId, assetHash, predicateDigest, tx hash)tuple per order — that is your durable record.
Verify the grader, then revoke and stop
The order records the grader address whose signature is the only one honored. The public signer in
this deployment is 0xB89e1Aba9c23f8227f29d3727570526e3Dd7C5Ee — the public
address; the private key never leaves the grader and is never in source or logs. Because the
contract has no owner, pause, or withdraw, you don't "turn off" the escrow — you
stop feeding it: stop the keeper (no keeper → no settlement), stop funding new orders, and fund any
future order with a rotated grader address. Drain funded orders first — stopping the keeper strands
an already-funded order until a valid grader signature settles it. Released/Refunded orders are
final. There is no sweep and no admin escape hatch — which is the point: no admin attack surface.
Troubleshooting
Never report a pass/fail or "settled" state you have not verified against a signed deterministic verdict and an on-chain receipt. If the grader has no deterministic tool evidence, it must not emit a verdict; if the settle transaction hasn't confirmed success, it must not report "settled."
| Symptom | Likely cause | What the Mind should check | Fix |
|---|---|---|---|
| A Mind never replies / a step hangs (no narration) | Cognition balance is 0 (silent timeout), or a fresh-Mind top-up charged without minting credits (balance is not real-time) | Hard-refresh the builder UI to read the real balance; was the top-up done immediately after creating the Mind? | Fund cognition and hard-refresh; if a fresh-Mind top-up didn't mint, fund again after the wallet registers. Narration is flavor-only — a Mind timeout must never block settlement (the signed verdict is the sole gate). |
| Verdict won't settle (tx reverts) | Bad/missing grader signature, wrong chain or escrow address, or the order is already settled / not settleable | Did grade_asset return a non-null signed verdict (an escrowId was supplied)? Do escrowAddress + chainId match (0xEEb1…78d9 / 84532)? On-chain order status? |
Re-grade WITH the correct escrowId for a fresh signature; don't retry a settle on a Released/Refunded order. A returned tx hash ≠ success — a non-success receipt is a hard failure. |
| Asset fails unexpectedly | Graded against the wrong profile, or declared params missing (named_params) — not a topology defect |
The per-predicate evidence: named_params declared/required/missing; tri_count vs budget; manifold boundary/non-manifold edges; valid_glb errors. |
Confirm the right profileId, then attach the required params / re-run conformance (weld + simplify to budget). A genuine non-manifold asset should fail — that's the grader correctly saying "no." |
semantic_match fails or is borderline |
The asset doesn't match the intent, or the score is near the threshold (calibrated T ≈ 0.5–0.6; less-distinctive shapes like the barrel score ~75% — figures from the honest-v0 N=5 benchmark, which proves the method + floor, not the hard/confusable cases) | topMatch, intentScore, and the per-label scores in the evidence. |
If topMatch is a distractor, the fail is correct; if near-margin, treat it as low-confidence and say so. Do not raise the threshold to force a pass. |
| Skill not callable / tool errors | The Skill isn't equipped, or the Connection holding the grading key is missing/misconfigured | Is "Grain 3D Conformance Grader" equipped? Is the Connection attached and valid? Are secrets in Connections (not chat)? | Attach/repair the Connection, then re-grade a known profile. Skill edits apply to NEW sessions — start a fresh session to pick up a fixed version. |
| Skill behaves like an old version after an edit | Published Skills are live but versioned — in-flight sessions keep their version | Are you in the same session where the edit was made? | Start a new session to pick up the updated Skill (REGISTRY_Update / SKILL_Update apply to new sessions). |