-
Notifications
You must be signed in to change notification settings - Fork 103
Description
Context
I've been building privacy-preserving machine-to-machine payment infrastructure using the x402 protocol (HTTP 402 Payment Required) across multiple chains. My existing Miden work:
| Repo | Description |
|---|---|
| privagent-miden | Trustless atomic agent payments — native SWAP notes + custom MASM escrow (HTLC) |
| x402-chain-miden | Rust facilitator for x402 on Miden — P2ID verification, STARK TransactionVerifier |
| x402-miden-agent-sdk | TypeScript SDK — AI agents create Miden wallets + handle x402 payments (WASM) |
| x402-miden-middleware | One-line Express/Hono paywall middleware for Miden x402 |
| x402-miden-cli | npx create-miden-agent scaffolding tool (3 templates) |
Note: These repos represent our current exploration — they are not production-ready. They were built to test different payment patterns on Miden (public P2ID, SWAP, HTLC escrow). Based on feedback from this discussion, we plan to update the architecture to align with the team's recommended approach (likely private notes + Note Transport as described below).
I've also built privacy payment protocols on other chains — PrivAgent (ZK proofs on Base) and MARC Protocol (FHE on Zama/Ethereum) — which informs the design considerations below.
The Problem
On EVM chains, x402 works by having a facilitator read public on-chain state (ERC-3009 transferWithAuthorization, event logs). On Miden, this approach conflicts with the privacy model.
Current state of our implementations:
x402-chain-midenuses public P2ID notes so the facilitator can verify payment viaGetNotesById. Works, but no privacy.privagent-midenuses native SWAP notes (NoteType::Public) and custom escrow notes (MASM HTLC, also public). Trustless and atomic, but still public.
Both defeat Miden's core privacy guarantee. After researching the codebase, we understand why making this private is non-trivial:
What We've Learned (and why we need input)
After reading the miden-client sync internals and miden-node RPC proto, we identified the core challenge:
-
Private notes are not discoverable via
sync_state()alone. The node returns onlyNoteId+ metadata for private notes. TheNoteScreenerdiscards untracked private notes because it can't screen them without full details (note_screener.rs->NoteUpdateAction::Discard). -
The Note Transport Network is the canonical private note delivery mechanism. The sender must push full
NoteDetailstotransport.miden.io, and the recipient fetches by tag match. This is integrated intosync_state()when enabled. -
There is no
GetTransactionByIdorCheckTxInclusionRPC (Rethink transaction sync endpoint #1605 is open —TransactionHeaderdoesn't map to protocol definition). TX inclusion can only be checked indirectly viaSyncTransactions(requires account_id + block range) orCheckNullifiers. -
NoteTag uses 14 MSB of account ID prefix (
with_account_target), giving ~1/16384 collision rate — intentional for privacy. Tags are advisory, not protocol-validated.
Proposed Flow: Private P2ID + Note Transport
Based on the above, here's what we believe a Miden-native x402 flow should look like:
Agent Server (runs miden-client)
| |
|-- GET /api/premium ---------------->|
| |
|<-- 402 Payment Required ------------|
| { recipient, amount, note_tag } |
| |
|-- Create PRIVATE P2ID note -----+ |
| (tag = server's account tag) | |
| Client-side STARK proof | |
| | |
|-- Submit proven TX to network | |
| | |
|-- Push note to Note Transport --+ |
| (transport.miden.io) |
| |
|-- Retry request with header: ------>|
| { tx_id, note_id, block_num } |
| |
| Server calls sync_state()
| -> Note Transport fetch
| -> Finds private note by tag
| -> Verifies: note targets me,
| amount >= required,
| TX is in a block
| |
|<-- 200 OK (access granted) ---------|
Alternative (lower latency): Agent includes note_details_hex directly in the payment header instead of relying on Note Transport. Server computes NoteId from the details and verifies it matches the on-chain commitment via GetNotesById. Faster, but larger request payload.
Design Questions
These are specific to implementation gaps we've encountered:
1. Note Transport for x402 — latency and reliability
When the agent pushes a note to Note Transport and the server fetches via sync_state(), what's the expected propagation delay? For x402, sub-second verification is ideal. If Note Transport adds significant latency, the header-based delivery alternative (agent sends note_details_hex directly) may be more practical. Is there guidance on which approach the team considers more idiomatic?
2. Verifying payment amount from private notes
After the server receives the private note details (via Transport or header), it can read the NoteAssets to verify the payment amount. But to confirm the note is actually committed on-chain, the server needs to:
- Compute
NoteIdfrom the details - Call
GetNotesByIdwith that ID - Verify the returned commitment matches
Is this the correct verification path? Or is there a more direct way to confirm a private note's on-chain inclusion given its details?
3. Transaction inclusion check
Currently there's no GetTransactionById endpoint (#1605 is open). For x402, the server needs to verify the payment TX was included in a block. The options we see:
SyncTransactionswith the agent's account_id + block range (but server may not know agent's account_id)CheckNullifiersfor the note's nullifier (only works after consumption, not creation)- Trust
GetNotesByIdreturning the note as "committed" as sufficient proof
Is GetNotesById returning a committed note with inclusion proof sufficient to consider the payment verified? Or should we wait for a TX-level inclusion check?
4. Note attachment field for encrypted details
We noticed NoteMetadata has an attachment field that's always public. Could this carry encrypted note details (encrypted to the recipient's public key) as an alternative to Note Transport? This would make private note discovery possible via on-chain sync alone — the recipient decrypts the attachment to get full note details. Is this a supported/intended use case for attachments?
5. Timing and contribution path
We understand the note/account model may still be evolving. Before we invest in implementation:
- Is now the right time for external contributions on this topic? Or is the note model (particularly Note Transport, private note discovery, and the attachment mechanism) still changing in ways that would make this premature?
- If the timing is right, would the most useful contribution be an RFC, a prototype PR (e.g. a
CheckNoteInclusionendpoint), or improvements to our existing external tooling? - If it's too early, we're happy to wait and revisit once the relevant primitives stabilize. We'd appreciate any guidance on what to watch for.
What We Can Contribute
We have 5 existing repos with working Miden payment infrastructure (SWAP, escrow HTLC, facilitator, agent SDK, middleware) — 166 tests across them. These are exploratory and would be updated based on this discussion's outcome. We're ready to contribute:
- RFC draft for Miden-native x402 payment verification
- Implementation — prototype PRs for any useful RPC endpoints
- Documentation — integration guides based on real building experience
- Test coverage upstream
Happy to discuss further or hop on a call. Would love to contribute upstream in whatever way is most useful to the team.