A Rust-first, Docker-only prototype for agentic payments on the Logos Execution Zone (LEZ). The prototype is a hybrid of three real-world designs:
- Stripe Link Wallet for Agents — owner-approved per-transaction spend requests, explicit approval flow, scoped one-time payment credentials, auditability. The owner reviews a meaningful free-text reason; the credential is signed, scoped, and time-bound.
- x402 v2 — HTTP
402 Payment Requiredchallenge-response transport withPAYMENT-REQUIRED,PAYMENT-SIGNATURE, andPAYMENT-RESPONSEheaders; replay protection via nonce. - Logos LEZ — CAIP-2-style network identifier
logos:lez-testnet,exactpayment scheme, build-and-sign-without-broadcast wallet split (today: mocked; upstream change required).
The two research documents that shaped this design — Stripe Link and
x402-on-LEZ — are committed verbatim under
spec/research/. Read those first for the wider
context.
- Mock backend: working end-to-end.
- Real LEZ integration: attempted, documented, not landed.
RealLezBackendexists as a typed stub; seespec/lez-integration-notes.mdandreports/lez-integration-attempt.mdfor the exact upstream blockers.
An agent composes a PaymentRequest (≥32-character reason). The
owner reviews it and produces a signed PaymentPayload. The agent
retries an HTTP GET to the merchant with the payload in the
PAYMENT-SIGNATURE header. The merchant calls into a facilitator
which verifies the payload against a PaymentBackend (the
MockLezBackend today, the real LEZ sequencer in a future life) and
settles. Replay fails with a stable nonce_replayed error code.
See spec/architecture.md for the sequence
diagram and crate-level dependency graph. See
spec/protocol.md for the wire-level field-by-field
spec.
Everything runs inside Docker. The host needs only docker and
docker compose.
# Build the dev image (multi-arch: linux/amd64, linux/arm64).
docker compose build
# Run the full test suite.
docker compose run --rm demo just test
# Run the CLI demo (in-process, no HTTP).
docker compose run --rm demo just demo-cli
# Run the HTTP demo end-to-end (boots an axum merchant + facilitator
# on an ephemeral port, drives the 402 -> retry -> 200 -> replay-fail
# flow over real TCP).
docker compose run --rm demo just demo-httpThe lez-agent-pay binary supports:
# 1. Owner / agent keypairs.
lez-agent-pay owner create --out owner.json
lez-agent-pay agent create --out agent.json
# 2. Compose a spend request.
lez-agent-pay request create \
--merchant-name "Mock Merchant" \
--merchant-url "http://merchant:8080/premium" \
--pay-to merchant_demo_account \
--amount 10000 \
--asset native \
--reason "Fetching the premium dataset for the user's research task." \
--agent-file agent.json \
--out request.json
# 3. Owner approves (or declines).
lez-agent-pay owner approve --request-file request.json \
--owner-file owner.json \
--out payment-payload.json
lez-agent-pay owner decline --request-file request.json
# 4. Drive an HTTP request manually.
lez-agent-pay http get --url http://merchant:8080/premium # 402
lez-agent-pay http get --url http://merchant:8080/premium \
--payload payment-payload.json # 200
# 5. End-to-end demos.
lez-agent-pay demo cli
lez-agent-pay demo httpcrates/
core/ PaymentRequest, PaymentPayload, WalletSigner, MockLezBackend
cli/ lez-agent-pay binary + subcommands + demos
merchant-server/ axum HTTP merchant (lib + bin)
facilitator/ verify/settle wrapper around PaymentBackend (lib + bin)
lez-adapter/ PaymentBackend trait choice: MockLezBackend vs RealLezBackend stub
tests/ CLI + HTTP integration tests (root-level by design)
spec/ Architecture, protocol, lez-integration notes, follow-ups
spec/research/ Original Stripe Link + x402-on-LEZ research docs (committed verbatim)
reports/ Final run report, test results, LEZ integration attempt log
examples/ Sample payment-request.json, payment-payload.json, payment-required.json
Real:
- The HTTP transport (axum + reqwest, real TCP, two integration tests exercise it).
- The x402 v2 header dance (
PAYMENT-REQUIRED,PAYMENT-SIGNATURE,PAYMENT-RESPONSE) carrying base64(JSON) bodies. - The 12-entry stable error-code list, surfaced both in JSON bodies
and in the
x-payment-errorresponse header. - ed25519 signatures over a domain-separated SHA-256 digest; tamper testing covers signature, amount, recipient, network.
- Nonce replay protection enforced inside the mock backend's settlement step.
Mocked:
- Balances and consumed nonces live in an in-memory
Mutex<HashMap>, not on a LEZ sequencer. PaymentPayload.payload.signed_txisNone. Real LEZ will populate it with the base64-borsh of a buyer-signedNSSATransaction::Public.- The signature path uses
ed25519-dalek+SHA-256 rather thannssa::WitnessSet::for_message(...)over an LEZ public transaction.
The WalletSigner and PaymentBackend traits are sized to receive the
real implementation. See
spec/lez-integration-notes.md for
the upstream wallet API gap (build_signed_transfer_transaction
doesn't exist yet) and the circuits-repo blocker that prevented us
from cargo check'ing the LEZ tree on this Pi.
| Where it comes from | Where it lives in this repo | |
|---|---|---|
| Owner-approved spend-request UX, ≥100-char reason | Stripe Link Wallet for Agents | crates/core/src/flow.rs (with a 32-char threshold for tests; raise to 100 per follow-up) |
HTTP 402 + PAYMENT-REQUIRED / PAYMENT-SIGNATURE / PAYMENT-RESPONSE |
x402 v2 | crates/merchant-server/src/lib.rs |
exact scheme, logos:lez-testnet network |
x402_research_lez.md §3-4 |
crates/core/src/lib.rs constants + crates/core/src/mock_backend.rs |
| Build-and-sign-without-broadcast wallet split | x402_research_lez.md §6.1 |
upstream gap; tracked in spec/follow-up-tasks.md |
| Stable error-code list | x402_research_lez.md §4.6 |
crates/core/src/errors.rs |
This is a prototype. It does not solve, attempt, or block on:
- Production wallet security (mock signer uses an unprotected seed in a JSON file)
- Real Stripe Link integration (only the semantics are mirrored)
- Real card payments
- Full x402 conformance suite (field names use snake_case rather than the upstream camelCase; see follow-ups)
- Private LEZ payments /
exact-private(Phase 2 ofx402_research_lez.md§5) - Mobile push approval UX (the owner step is a CLI today)
- MCP server / browser extension / polished UI
These belong in spec/follow-up-tasks.md.
Apache-2.0.