Skip to content

x402 phase 2: exact/EIP-3009 payments on the Ethereum reference path, fully local demo loop #34

Description

@hellno

Why

The flagship: the agent hits HTTP 402, Deckard validates the payment demand against policy, signs an EIP-3009 TransferWithAuthorization (EIP-712), the agent retries and gets the resource — keys never leave the machine, the client pays no gas (the settler does), and the facilitator cannot alter amount or destination (worst case is non-settlement, never theft). Self-custodial, policy-bounded machine payments on Ethereum.

Canonical scheme spec: https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md · protocol v2: https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md

Corrected 2026-07-10 (codebase re-verified): #34 is the x402/MPP linchpin — the whole track (#207/#208/#209/#210) is gated on it. It is a LARGE net-new build but fully unblocked and de-risked: every stated dependency is on main, and spike #206 (PR #211) proved the exact crypto byte-for-byte. Spike-forced corrections and a split/sequencing plan added below.

Ready to start — dependencies all landed (verified 2026-07-10)

Spike-forced corrections (from #206 / PR #211 — the spec had these wrong)

  • Use protocol v2 / CAIP-2 network ids; emit v2, decode v1 and v2.
  • EIP-712 domain name is "USDC" (version "2"), NOT "USD Coin".
  • The KAT lives as a JSON fixture (docs/research/x402-spike/eip3009-kat.json) — re-encode it as a Rust #[test] inside this issue (digest 0x89d2449c…, sig 0x3e1236af…).
  • propose_payment must copy the Send propose auto-allow-within-budget + guardrail-downgrade path (daemon.rs:683), NOT the always-NeedsApproval propose_message path.
  • Do not sign as an anvil default account (EIP-7702 delegation routes USDC to EIP-1271 and rejects) — the spike run.sh documents the working setup.
  • The x402-rs facilitator binary is a demo prerequisite surfaced by demo-check (a just demo sibling process on the fork), never a build dependency.

In scope

  • deckard-core: hand-rolled EIP-3009 TransferWithAuthorization via alloy sol! mirroring cow_types.rs:26-59 + eip712_signing_hash sign + the Rust KAT. (No wire files — this PR can run in parallel with the browser milestone.)
  • deckard-contract: PaymentRequirements v1/v2 decode + ProposePayment/SignPayment wire variants + CAP_X402_EXACT. (Wire files — rebase this on Connections: daemon-held dapp sessions, listed in the app, revocable per site #199's SignerRequest delta; land one enum change first, then the other. See Connections: daemon-held dapp sessions, listed in the app, revocable per site #199 "Wire sequencing".)
  • policy: per-asset token_budgets (per-tx + daily + spent-today, UTC rollover) + asset_allow (default-deny) + max_validity_secs + evaluate_payment untrusted-input checks (asset ∈ allow, amount ≤ budget, payTo rendered, validity ≤ max, network == chain). Any miss → typed Deny (Freeze the Deny-reason vocabulary in deckard-contract #28). Mainnet guardrail unchanged.
  • daemon: propose_payment (auto-allow-within-budget + guardrail-downgrade) + per-asset SpendStore + bearer-nonce idempotent retry-reuse.
  • MCP: deckard_x402_sign, capability-gated.
  • Demo fixtures (toy 402 server + x402-rs facilitator on the fork) + demo-check probes.
  • Acceptance X3–X10 headless against the fixtures (within-budget pay → settle; hostile asset → Deny; amount > budget → Deny; validity > max → Deny; retry reuses same auth; daily budget exhausted → Deny until rollover; chain-1 → NeedsApproval; transcript scan clean).

Suggested split (3 PRs)

  1. core (EIP-3009 struct + sign + KAT) — no wire files, parallelizable with browser work.
  2. policy + daemon + wire — rebases on Connections: daemon-held dapp sessions, listed in the app, revocable per site #199's enum delta.
  3. mcp + fixtures + demo-check.

Not in scope

upto/batch-settlement, Permit2, Base/hosted facilitators, the private Railgun leg + ERC-7710 settlement, general ERC-20 beyond the allowlisted demo asset.

Definition of done

  • All in-scope items runnable headless; X3–X10 green.
  • cargo fmt --all --check clean; just check green (both feature configs); cargo test --workspace green.
  • No new deps (hand-roll the x402 structs — alloy-sol-types is already compiled for cow_types); anything else approved before merge.
  • THREAT-MODEL.md delta merged (typed-data signing surface; bearer-authorization residual risk).

Ordering

Unblocked today. #32 (sign-in) is NOT a blocker (its message seam already shipped — see #32). Sequence the x402 track: #34#208#209 (+#207 for GUI beats); #210 is a near-free follow-on to #208.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions