Skip to content

feat(mpp): implement x402 payment-offer + signed payment-proof core#35

Open
Pattermesh wants to merge 1 commit into
mainfrom
pattermesh/arka-mpp-x402
Open

feat(mpp): implement x402 payment-offer + signed payment-proof core#35
Pattermesh wants to merge 1 commit into
mainfrom
pattermesh/arka-mpp-x402

Conversation

@Pattermesh

Copy link
Copy Markdown
Contributor

What

The MPP / x402 machine-payments module was entirely TODO (src/mpp/mod.rs only parsed a 402, with pay() / session() / pay_and_retry() stubbed out). This PR implements the core that arka's pitch depends on: typed payment offers, cryptographically signed payment proofs, and the full HTTP-402 flow — aligned with the x402 pattern and wire-compatible with the switchboard Python reference impl.

How

src/mpp/types.rs (new) — the wire types and crypto:

  • PaymentOffer + PaymentOfferBuilder (server side): build a 402 offer for amount/currency/recipient/chain, with an auto-generated 128-bit nonce and optional TTL. OfferEnvelope { accepts: [...] } serializes to the camelCase X-Payment-Required header.
  • PaymentProof (client + verifier side):
    • sign(wallet, offer, tx_hash) builds a proof bound to the offer (copies recipient/amount/currency/nonce, stamps the payer from the wallet) and EIP-191 (personal_sign) signs a domain-separated canonical message (x402-proof-v1\n…) over every economically meaningful field.
    • verify(offer) / verify_at(offer, now): checks chain/recipient/amount/currency/nonce bind to the offer, the offer hasn't expired, and the signature recovers to exactly the declared payer. Any tampered field diverges the recovered address and is rejected.
    • to_header / from_header for the X-Payment-Proof header.

src/mpp/mod.rs — wires it into the flow:

  • MppClient::pay_and_retry(url, wallet, settle): request → on 402, pick the first offer → settle via a pluggable Settle impl (closure or trait) → sign proof → retry with X-Payment-Proof. Client self-verifies before sending.
  • Settle trait (blanket impl for Fn(&PaymentOffer) -> Result<String>) keeps settlement (native/ERC-20/escrow/mock) decoupled from the protocol.
  • PaymentReceipt::from_proof for verifier-side accounting.

Tests (12 new, no network)

  • offer build (fields, unique auto-nonces), envelope header roundtrip
  • proof sign + verify roundtrip, proof header roundtrip
  • rejection cases: tampered amount, wrong payer, forged signature, expired offer, nonce mismatch
  • in-process offer→settle→prove→verify→receipt roundtrip

Verification — all CI gates green

cargo fmt -- --check            # clean
cargo check --all-features      # clean
cargo clippy -- -D warnings     # clean
cargo test                      # 31 unit + 1 integration passed; 0 failed

Note: this branch also carries a one-line fix in src/mcp/mod.rs for a pre-existing clippy -D warnings failure (the question_mark lint under rust 1.96, introduced by an earlier merge) that was already breaking the CI gate independent of this work. The fix is clippy's own suggested rewrite (let id = id?;) and preserves behavior.

🤖 Generated with Claude Code

Replaces the all-TODO MPP module with a real, tested HTTP-402 / x402
implementation — arka's machine-payments differentiator.

- PaymentOffer + PaymentOfferBuilder (server side): build a 402 offer
  with amount/currency/recipient/chain, auto-generated nonce, and TTL;
  OfferEnvelope serializes to the camelCase X-Payment-Required header,
  wire-compatible with the switchboard Python reference impl.
- PaymentProof (client + verifier side): construct a proof bound to an
  offer and EIP-191-sign it with the agent wallet; verify() recovers the
  signer, checks it matches the declared payer, and that every
  offer-bound field (chain/recipient/amount/currency/nonce) and the
  expiry hold — rejecting tampered amount, forged/wrong signer, expired
  offer, and nonce mismatch.
- MppClient.pay_and_retry(): the full flow — request -> 402 -> pluggable
  Settle -> sign proof -> retry with X-Payment-Proof. PaymentReceipt for
  accounting.

12 new unit tests cover offer build, proof construct/verify roundtrip,
header roundtrips, and four distinct rejection cases.

Also fixes a pre-existing clippy `-D warnings` failure in src/mcp/mod.rs
(question_mark lint under rust 1.96) that blocked CI on the branch.

cargo fmt/check --all-features/clippy -D warnings/test all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant