Batch-settlement TS sdk#2061
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
e06c52c to
65d2d8f
Compare
65d2d8f to
d516921
Compare
|
Great work @phdargen. One thing worth pinning down before other SDKs copy it is the corrective-402 recovery contract. Right now the important path is: stale cumulative detected -> server returns authoritative voucher snapshot -> client replaces local state -> one retry only. A tiny vector set for that path would make the CAS/recovery behavior portable across SDKs without changing this PR. |
9a22a1c to
11c2c7f
Compare
69793a5 to
a681ab6
Compare
b364b1f to
44b9690
Compare
bcd623f to
1ef2dda
Compare
|
Great direction on batch-settlement. This is the right shape for repeat paid MCP calls where the client should not do a full settle path on every small request. One operator note from the AgentPay MCP side: batch settlement changes the audit surface from one payment event to channel lifecycle state. The checklist we are using for paid MCP integrations now includes:
We added the AgentPay MCP compatibility recipe and docs test here:
This is not a blocker for PR #2061. It is a production-readiness note for teams that will wire batch-settlement into paid MCP tools or streaming paid calls. |
ce7d65a to
f61da35
Compare
f61da35 to
4b31597
Compare
|
Updated to latest deployment @CarsonRoscoe |
|
Excited to see batch-settlement land. Planning to port to the Python SDK as a follow-up, following the same layout pattern as Anyone already on this @phdargen? If not, I'll open a draft PR over the next 1-2 weeks. Happy to coordinate on the corrective-402 wire contract before locking in Python-side behavior. Particularly want to lock down EIP-712 byte equivalence and the null/undefined/absent boundaries early, since those are where Python ↔ TS interop usually breaks (#1762 was one example). |
|
Hi all — opened #2199 as a draft PR1 of the planned Python port of The PR description outlines:
Happy to adapt the shape if you'd prefer something else. No rush on review of #2199 itself — flagging now mainly for strategy alignment. |
Batch-Settlement EVM Scheme — TypeScript SDK
Implements the
batch-settlementscheme for EVM in the TS SDK according to the specs outlined in #2051Scheme implementation: key design decisions
Unlike
exact/upto, this scheme is stateful (per-channel session, persisted), multi-payload (deposit, voucher, claim/refund/settle actions), and most paid requests do not trigger an onchain transaction at settle time. The implementation handles all of this transparently via lifecycle hooks the scheme registers on itself, so apps justregister(network, scheme):onBeforeVerify— detects stale client cumulative state, embeds the authoritative voucher snapshot inrequirements.extra, and aborts withbatch_settlement_stale_cumulative_amountso the middleware emits a corrective 402.onAfterVerify— persists the verified channel snapshot fromverifyResponse.extra(balance, totalClaimed, withdraw timer, refund nonce). Single point where onchain state is reconciled into the local session.onBeforeSettle— the central piece. For voucher payloads it skips the facilitator entirely: CAS-bumpschargedCumulativeAmount, validates against the signed cap, and returns{ skip: true, result }so the middleware responds immediately. If the client requested a cooperative refund, it rewrites the payload in place to arefundWithSignaturesettle action (claim of latest voucher + refund of unused balance), authorizes it locally with the receiver authorizer signer, and lets the facilitator submit it onchain.onAfterSettle— writes back the post-tx snapshot (deposit → updated balance, refund → session deletion).onPaymentResponse— on success, mirrorsresult.extrainto local channel state; on a corrective 402 it copies the server's authoritative voucher into local storage and returns{ recovered: true }, signaling the transport to retry transparently with a fresh voucher.Other notable choices:
InMemory*andFile*defaults. The server interface adds acompareAndSet(channelId, expected, next)primitive — the only correctness-critical piece, used byonBeforeSettleto serialize concurrent requests on the same channel.extra.receiverAuthorizerfrom the facilitator's/supported.scheme.createChannelManager(facilitator, network)runs a periodic tick that triggersclaim/settle/refundbased on configurable policies (interval, idle, threshold, pending withdrawal, shutdown). Reuses the same scheme storage and authorizer signer.verify*/settle*modules per action; each returns anextrasnapshot of post-tx channel state for the server to consume.Changes outside
mechanisms/evm/batch-settlementThese changes were necessary to host a stateful, multi-tx scheme cleanly. All are designed as general-purpose extension points that any current or future scheme can use; nothing is
batch-settlement-specific.core/types/mechanisms.ts—SchemeClientHooks/SchemeServerHooksAdded optional
schemeHooks?toSchemeNetworkClientandSchemeNetworkServer. When a scheme isregister()-ed onx402Client/x402ResourceServer, its declared hooks are auto-wired into the existing per-instance hook system.Why: any scheme with non-trivial protocol state needs to participate in the verify/settle lifecycle without forcing every consumer to manually call
server.onBeforeVerify(scheme.handleX)etc. Makes scheme integration a singleregister()call and keeps the existing user-level hook API unchanged.core/server/x402ResourceServer.ts—BeforeSettleHookskipoutcomeBeforeSettleHookcan now return{ skip: true, result: SettleResponse }. The middleware uses the provided response directly and runsonAfterSettle+ extensionenrichSettlementResponsehooks normally, but does not callfacilitator.settle().Why: any scheme where the resource server can confirm payment locally without a facilitator round-trip benefits — batch-settlement uses it for the common voucher case (0 gas, 0 RPC), but it equally supports off-chain receipt schemes, prepaid quota schemes, mock facilitators, etc.
core/client/x402Client.ts+core/http/x402HTTPClient.ts—OnPaymentResponseHookNew client hook fired after every paid request:
PaymentResponseContextis a discriminated bag withsettleResponse(success/failure),paymentRequired(corrective 402), orerror.x402HTTPClient.processPaymentResult(payload, getHeader, status)parses headers and surfaces a singlerecoveredflag to the transport. Also addedprocessResponse(response)returning a discriminatedx402PaymentResult(success/settle_failed/payment_required/error/passthrough) for app-level consumers.Why: schemes with client-side state need a deterministic place to react to what actually happened on the wire — either to update their model (success) or to repair it and request a retry (corrective 402). Generalizes to any scheme needing response-driven state (subscriptions, refresh tokens, off-chain receipt acks, etc.).
http/{fetch,axios}/src/index.ts— recovery pathBoth transport wrappers now call
processPaymentResult(...)after the paid request and, if a hook returns{ recovered: true }, regenerate the payload and reissue once. Recovery is bounded to a single retry to prevent loops. No behavior change for schemes that don't register the hook.core/types/facilitator.ts+core/http/httpFacilitatorClient.ts—extraonVerifyResponse/SettleResponseAdded optional
extra?: Record<string, unknown>(separate fromextensions) and parsed it in the HTTP facilitator's zod schemas.Why:
extensionsis reserved for the x402 extension framework.extrais the symmetric counterpart ofPaymentRequirements.extra— scheme-specific protocol data flowing back from the facilitator. Batch-settlement uses it for post-tx channel snapshots; any future scheme needing scheme-level metadata in a verify/settle response gets it for free.Also added the
amountfield to theSettleResponsezod schema (the field already existed on the type but wasn't being parsed), so dynamic-pricing schemes (upto,batch-settlement) round-trip correctly over HTTP.Tests / examples
examples/typescript/{servers,clients,facilitator}/batch-settlement[-streaming]/, including a streaming server with mid-stream voucher renewalLinks
Specs: #2051
x402BatchSettlement contract: #1950