Sharing notes from a production ERC-8183 deployment on Base mainnet (live since 2026-04). Repo with addresses, tests, and migrations: [ GitHub - mrocker/CardZero: The first universal payment wallet for AI Agents. USDC on Base. · GitHub ]
We deployed a self-implementation of the spec (`CardZeroJobs`, [`0xb28a0cca5ac28466f3d175f35b97aa104d4c4ba8`]) wired into our own ERC-8004 IdentityRegistry / ReputationRegistry pair. 133 contract tests, 4-EOA role separation (deployer / registrar / attestor / evaluator), real mainnet E2E (5 txs, splits enforced 93%/5%/2%).
Quick takes on the four open questions @miratisu raised:
### 1. Async off-chain computation in the evaluator
**Yes, this is the real product.** Three eval rule types in production:
- `manual` — human attests via UI (off-chain queue + UI button + on-chain `evaluatorComplete`)
- `json_schema` — submitted deliverable URI is fetched, validated against client-declared JSON schema, complete/reject decision made off-chain, on-chain attestation
- `http_check` — provider exposes a callback URL, evaluator pings it with a per-job nonce, expects 200 + body shape match
All three run in a 2-min cron loop. The evaluator EOA has `EVALUATOR_ROLE` and signs the on-chain transition. Async is not optional — if you want anything beyond “trust the provider” you need it.
**What I’d want from the spec:** stay agnostic. Don’t try to formalize the eval mechanism — it’s properly application-layer. But please **keep the `evaluator` field as `address`** (not `bytes32` / DID), so a smart-contract evaluator can substitute later (e.g., a TEE-attested oracle, or a multisig of human reviewers). We chose a single EOA for v1; the address field gives us the upgrade path.
### 2. Should the evaluator write to ERC-8004 ReputationRegistry on completion?
**We do, and we’d argue yes — but as a separate role grant, not implicit.**
Our `CardZeroJobs.finalizeJob()` calls `ReputationRegistry.attest(provider, jobValue, scoringRulesHash)` after the funds split. The `Jobs` contract itself holds `ATTESTOR_ROLE` on the ReputationRegistry. The grant is a one-time admin op, not implicit in the spec.
Pros:
- Job-completion → reputation is the primary causal link in this market. Decoupling them produces stale reputation data.
- The on-chain link is auditable. `cardzero.ai/.well-known/agent/{address}` becomes meaningful.
Cons:
- It couples Jobs to a specific ReputationRegistry. We solved this with `setReputationAttestor()` (mutable, admin-gated).
- It introduces re-entrancy surface. We do the attest after `transfer` calls (CEI pattern violation actually) — be careful. Consider `nonReentrant` modifier or strict ordering.
**What I’d want from the spec:** make the link **optional but standardized**. e.g. `IJobs.setReputationRegistry(address)` and `Jobs` MUST call `attest()` after a successful `complete`. Implementations that don’t want it can leave the address as `0x0`.
### 3. “Agentic” vs “Escrow”
**Both. The protocol is escrow; the audience is agents.** Naming-wise, “Agentic Commerce” puts off non-agent users (we’d never use it for human-to-human even though the contract supports it), but “Escrow” loses the discoverability of the agent market.
Pragmatic suggestion: **make the spec name “Service Delivery Escrow” and call out “designed for autonomous agents but fully usable by EOAs/multisigs”** in the abstract. We get Google rank for both.
### 4. Linked / two-phase jobs
**We don’t have this yet.** Looked at it for parent-child task decomposition (e.g., a research agent commissions sub-jobs).
Honestly: **don’t make it base-spec.** Two reasons:
1. Linked-jobs has dozens of compositional variants (DAG, fan-out/fan-in, conditional, partial-completion). Picking one closes off the others.
2. You can build it as an **adapter contract** that holds child Job IDs and orchestrates funding/completion. The base ERC-8183 doesn’t need to know about it.
We’ll likely do this in v2 of `CardZeroJobs` as a separate `CardZeroLinkedJobs` contract. Keep the base spec minimal.
### One thing I think should be tightened in v2
`expiry` field. We caught a real bug going to production: `uint64` vs `uint256` mismatch in our chain.ts ABI bound (function selector mismatch → silent revert). The contract uses `uint256` but our SDK assumed `uint64`. After the fix (correctly `uint256`), behavior worked.
**Suggestion for the spec:** be explicit that `expiry` is `uint256 secondsSinceEpoch`, and add a SHOULD that wallets refuse to fund a job with `expiry < now + 86400` (1 day) — short expiries lead to client-rejected workflows that look like provider failures in reputation.
—
Happy to share contract source / tests / migration scripts if useful. We’re MIT-licensed, and the deployment ceremony doc is in the repo (`docs/deployment-ceremony-sprint8-9.md`).
Currently in beta. Before posting any production claims, I should note: we recommend per-wallet balances under \$100 USDC until external audit completes (planned via Code4rena).
— Nicholas (CardZero / mrocker)