@venables would love your read on the shape.
Request for comment. Nothing is built against this yet. I want maintainer input on shape and naming before any code lands. Efe Baran Durmaz (@EfeDurmaz16).
TL;DR
ACK-Pay can express exactly one payment shape: a fixed amount, paid in full. x402 and MPP already ship the rest as first-class schemes and intents: spend caps, sessions, subscriptions, metering, refunds. This RFC proposes a small, additive scheme model for ACK-Pay plus five schemes ported from that prior art, each with a spec page, a reference implementation, and a conformance suite. ACK keeps its lead on verifiable receipts and identity, and gains the settlement shapes the rest of the ecosystem already speaks.
The gap isn't trust. It's shape.
ACK already wins the two hard things its neighbors mostly lack: verifiable receipts (W3C VCs) and an agent-to-owner identity chain (ACK-ID). What it cannot say is "authorize up to X, settle less", "open a session and settle many micro-payments in one move", "charge on a cadence", "meter per unit", or "refund against a receipt".
None of those are exotic. x402 ships an explicit scheme registry (exact, upto, deferred, escrow). MPP ships an intent model (charge, session, subscription, vouchers). ACK-Pay ships one implicit shape: exact, paid in full.
An agent that can only pay a fixed amount in full is a demo, not an economy.
What ACK-Pay expresses today
PaymentOption (packages/ack-pay/src/schemas/valibot.ts:7-16):
export const paymentOptionSchema = v.object({
id: v.string(),
amount: v.union([v.pipe(v.number(), v.integer(), v.gtValue(0)), v.string()]),
decimals: v.pipe(v.number(), v.integer(), v.toMinValue(0)),
currency: v.string(),
recipient: v.string(),
network: v.optional(v.string()),
paymentService: v.optional(urlOrDidUri),
receiptService: v.optional(urlOrDidUri),
})
There is no scheme, and no way to carry spend-cap, session, cadence, metering, or refund semantics. The receipt, though, already has a sanctioned extension point: paymentReceiptClaimSchema.metadata (valibot.ts:34-37). Each scheme can use it to record what was actually settled.
Proposal: an additive scheme discriminator
Add an optional scheme tag to PaymentOption, defaulting to "exact" so every existing request and receipt keeps working untouched. Per-scheme parameters ride in a typed shape keyed on scheme (sketch, exact names open):
// default keeps today's behaviour: a fixed amount paid in full
scheme: v.optional(v.string(), "exact") // "exact" | "up-to" | "escrow" | "subscription" | "stream"
// params validated by a discriminated union on `scheme`; absent for "exact"
schemeParams: v.optional(/* per-scheme */)
This mirrors x402's scheme registry on purpose, so an ACK option and an x402 scheme map cleanly (see Interop). The receipt records the outcome (settledAmount, session id, subscription id, refunded-receipt id) in its existing metadata, so the receipt stays the single verifiable record.
The five schemes
| Scheme |
Port from |
What it does |
Shape (sketch) |
up-to / spend-cap |
x402 upto, MPP escrow cap, AP2 intent cap |
authorize a max, settle at or below it |
{ maxAmount, decimals, currency }; receipt: settledAmount ≤ max |
escrow (sessions + batch) |
x402 escrow/deferred, MPP voucher sessions |
open once, accrue signed vouchers, settle the aggregate in one move |
{ maxAmount, ... } + session id; receipt(s) reference the session |
subscription |
MPP subscription intent, AP2 renewals |
recurring charge on a cadence |
{ amount, interval, anchor? }; child requests linked by subscription id |
stream (metered) |
MPP per-unit vouchers |
pay-per-use over time |
{ unitAmount, unit, maxUnits? }; receipt records units |
| refunds |
MPP native, x402 escrow refund |
refund against a prior receipt |
references a prior receipt id, issues a linked refund receipt |
Refunds is the odd one out. It is not a PaymentOption shape, it is a receipt-linked operation, so it probably wants its own small request/receipt type rather than a scheme value. Flagged below.
Each scheme ships with conformance
Every scheme lands with coverage, not just code. The plan is a tools/conformance workspace package (mirroring tools/api-utils, wired into the turbo test pipeline) that drives the canonical flow end to end, createSignedPaymentRequest then 402 then verifyPaymentRequestToken then pay then createPaymentReceipt/signCredential then verifyPaymentReceipt, and asserts the cross-cutting properties (idempotency, replay, duplicate-order, approval, revocation) plus a per-scheme suite. For up-to, that means settling above the cap is rejected and the receipt's settledAmount is at or below the authorized max. demos/payments and examples/issuer are the reference flows it exercises.
(I have a thin version of this harness running locally against the existing exact flow already, as the foundation.)
Interop comes with the model
Because the scheme tags line up with x402's, an ACK Payment Service can act as an x402 Facilitator (the docs gesture at this, there is no code yet), and an AP2 interop adapter can bridge ACK-ID credentials and ACK-Pay receipts to AP2's mandate format. An interop.mdx page would map ACK, x402, MPP, and AP2 scheme by scheme, so no one is locked to one protocol. ACK's differentiator stays its verifiable receipts and identity. The schemes just let it speak everyone else's settlement.
Reach: the schemes need more than TypeScript
Agents get built in Python, Go, Rust, Ruby, not only TS. ACK is TypeScript-only today with no machine-readable spec, so the schemes would stay reachable by the JS slice only. The fix is a cross-language source of truth (JSON Schema plus a minimal OpenAPI for the Payment/Receipt service, generated from the existing valibot/zod schemas), then client+server SDK ports validated for parity by the same conformance harness. This is the model I built for solana-foundation/pay-kit (x402 and MPP across nine languages with a shared harness).
Versioning: additive, per-scheme
scheme defaults to "exact", so existing requests and receipts are unchanged.
- Each scheme is independently versionable and follows ACK's date-based spec versioning.
- New code follows ACK's dual-schema pattern (valibot plus zod v3/v4) and ships behind its own changeset.
Non-goals
- No change to ACK-ID or the receipt format beyond the existing
metadata extension point.
- No settlement engine. Schemes describe authorization and what the receipt records. Execution stays with the Payment Service, as today.
- Not absorbing x402 or MPP. The aim is interop, not replacement.
Open questions (where I want your call)
- Shape:
scheme tag plus discriminated schemeParams on PaymentOption, or a new request kind per scheme?
- Naming: mirror x402's identifiers (
exact/upto/escrow/deferred) for interop, or ACK-native names?
- Refunds: a dedicated request/receipt type, or a scheme?
- Receipt extension: is
metadata the right home for settledAmount/session/subscription refs, or should these be first-class receipt fields?
- Conformance home: is
tools/conformance right, and should it be published or internal-only?
- Order: if you'd take only some of these first, which schemes matter most to ACK's roadmap?
How I'd land it
Incrementally. One scheme per PR, each with a spec page, a reference implementation, and a conformance suite, reviewed before it opens, the same way I shipped the credential-verification fix in #110. I'd start with the scheme model plus up-to as a thin first PR once the shape here has your blessing.
AI-assisted (Claude Code), reviewed by me; I take responsibility for this proposal. Prior art: x402 schemes (github.com/coinbase/x402), MPP (tempo.xyz, mpp.dev), AP2 (ap2-protocol.org). I'm a core contributor to solana-foundation/pay-kit, where I built the equivalent polyglot scheme and conformance work for x402 and MPP.
@venables would love your read on the shape.
Request for comment. Nothing is built against this yet. I want maintainer input on shape and naming before any code lands. Efe Baran Durmaz (@EfeDurmaz16).
TL;DR
ACK-Pay can express exactly one payment shape: a fixed amount, paid in full. x402 and MPP already ship the rest as first-class schemes and intents: spend caps, sessions, subscriptions, metering, refunds. This RFC proposes a small, additive
schememodel for ACK-Pay plus five schemes ported from that prior art, each with a spec page, a reference implementation, and a conformance suite. ACK keeps its lead on verifiable receipts and identity, and gains the settlement shapes the rest of the ecosystem already speaks.The gap isn't trust. It's shape.
ACK already wins the two hard things its neighbors mostly lack: verifiable receipts (W3C VCs) and an agent-to-owner identity chain (ACK-ID). What it cannot say is "authorize up to X, settle less", "open a session and settle many micro-payments in one move", "charge on a cadence", "meter per unit", or "refund against a receipt".
None of those are exotic. x402 ships an explicit scheme registry (
exact,upto,deferred,escrow). MPP ships an intent model (charge, session, subscription, vouchers). ACK-Pay ships one implicit shape: exact, paid in full.An agent that can only pay a fixed amount in full is a demo, not an economy.
What ACK-Pay expresses today
PaymentOption(packages/ack-pay/src/schemas/valibot.ts:7-16):There is no
scheme, and no way to carry spend-cap, session, cadence, metering, or refund semantics. The receipt, though, already has a sanctioned extension point:paymentReceiptClaimSchema.metadata(valibot.ts:34-37). Each scheme can use it to record what was actually settled.Proposal: an additive
schemediscriminatorAdd an optional
schemetag toPaymentOption, defaulting to"exact"so every existing request and receipt keeps working untouched. Per-scheme parameters ride in a typed shape keyed onscheme(sketch, exact names open):This mirrors x402's scheme registry on purpose, so an ACK option and an x402 scheme map cleanly (see Interop). The receipt records the outcome (
settledAmount, session id, subscription id, refunded-receipt id) in its existingmetadata, so the receipt stays the single verifiable record.The five schemes
up-to/ spend-capupto, MPP escrow cap, AP2 intent cap{ maxAmount, decimals, currency }; receipt:settledAmount≤ maxescrow(sessions + batch)escrow/deferred, MPP voucher sessions{ maxAmount, ... }+ session id; receipt(s) reference the sessionsubscription{ amount, interval, anchor? }; child requests linked by subscription idstream(metered){ unitAmount, unit, maxUnits? }; receipt records unitsRefunds is the odd one out. It is not a
PaymentOptionshape, it is a receipt-linked operation, so it probably wants its own small request/receipt type rather than aschemevalue. Flagged below.Each scheme ships with conformance
Every scheme lands with coverage, not just code. The plan is a
tools/conformanceworkspace package (mirroringtools/api-utils, wired into the turbotestpipeline) that drives the canonical flow end to end,createSignedPaymentRequestthen 402 thenverifyPaymentRequestTokenthen pay thencreatePaymentReceipt/signCredentialthenverifyPaymentReceipt, and asserts the cross-cutting properties (idempotency, replay, duplicate-order, approval, revocation) plus a per-scheme suite. Forup-to, that means settling above the cap is rejected and the receipt'ssettledAmountis at or below the authorized max.demos/paymentsandexamples/issuerare the reference flows it exercises.(I have a thin version of this harness running locally against the existing
exactflow already, as the foundation.)Interop comes with the model
Because the scheme tags line up with x402's, an ACK Payment Service can act as an x402 Facilitator (the docs gesture at this, there is no code yet), and an AP2 interop adapter can bridge ACK-ID credentials and ACK-Pay receipts to AP2's mandate format. An
interop.mdxpage would map ACK, x402, MPP, and AP2 scheme by scheme, so no one is locked to one protocol. ACK's differentiator stays its verifiable receipts and identity. The schemes just let it speak everyone else's settlement.Reach: the schemes need more than TypeScript
Agents get built in Python, Go, Rust, Ruby, not only TS. ACK is TypeScript-only today with no machine-readable spec, so the schemes would stay reachable by the JS slice only. The fix is a cross-language source of truth (JSON Schema plus a minimal OpenAPI for the Payment/Receipt service, generated from the existing valibot/zod schemas), then client+server SDK ports validated for parity by the same conformance harness. This is the model I built for
solana-foundation/pay-kit(x402 and MPP across nine languages with a shared harness).Versioning: additive, per-scheme
schemedefaults to"exact", so existing requests and receipts are unchanged.Non-goals
metadataextension point.Open questions (where I want your call)
schemetag plus discriminatedschemeParamsonPaymentOption, or a new request kind per scheme?exact/upto/escrow/deferred) for interop, or ACK-native names?metadatathe right home forsettledAmount/session/subscription refs, or should these be first-class receipt fields?tools/conformanceright, and should it be published or internal-only?How I'd land it
Incrementally. One scheme per PR, each with a spec page, a reference implementation, and a conformance suite, reviewed before it opens, the same way I shipped the credential-verification fix in #110. I'd start with the scheme model plus
up-toas a thin first PR once the shape here has your blessing.AI-assisted (Claude Code), reviewed by me; I take responsibility for this proposal. Prior art: x402 schemes (github.com/coinbase/x402), MPP (tempo.xyz, mpp.dev), AP2 (ap2-protocol.org). I'm a core contributor to
solana-foundation/pay-kit, where I built the equivalent polyglot scheme and conformance work for x402 and MPP.