Feat/batch settlement go sdk#2164
Feat/batch settlement go sdk#2164CarsonRoscoe wants to merge 17 commits intox402-foundation:batched-settlementfrom
Conversation
|
@CarsonRoscoe is attempting to deploy a commit to the Coinbase Team on Vercel. A member of the Team first needs to authorize it. |
e5a2208 to
79d71bb
Compare
|
Nice to see Go parity landing for batch-settlement. From the paid MCP provider side, the main compatibility risk is not the happy path. It is whether TS and Go clients leave operators with the same channel evidence after deposit, voucher signing, corrective 402 recovery, claim, refund, and final settlement. We added an AgentPay MCP compatibility recipe that tracks the PR #2164 shape:
Docs/test proof is in our AgentPay MCP PR: up2itnow0822/agentpay-mcp#8 Direct commit: up2itnow0822/agentpay-mcp@b241396 This should give paid MCP providers a concrete checklist for verifying multi-SDK batch-settlement before they rely on repeat-call vouchers in production. |
f61da35 to
4b31597
Compare
53c87ba to
74c051a
Compare
…h_settlement_evm_*
BREAKING CHANGE: every facilitator-emitted batch-settlement EVM rejection token
now starts with `invalid_batch_settlement_evm_` (mirroring the
`invalid_exact_evm_*` shape already used by the exact mechanism), aligning the
SDK 1:1 with CDP Accounts API enums (`x402VerifyInvalidReason` /
`x402SettleErrorReason`). Inner `_invalid_` segments collapse into the leading
envelope so values stay short:
batch_settlement_evm_invalid_scheme -> invalid_batch_settlement_evm_scheme
batch_settlement_evm_invalid_deposit_payload -> invalid_batch_settlement_evm_deposit_payload
batch_settlement_evm_permit2_invalid_spender -> invalid_batch_settlement_evm_permit2_spender
batch_settlement_evm_eip2612_invalid_format -> invalid_batch_settlement_evm_eip2612_format
Resource-server-emitted abort reasons (`batch_settlement_channel_busy`,
`batch_settlement_refund_*`, `batch_settlement_cumulative_amount_mismatch`)
keep their `batch_settlement_*` sibling prefix — they are NOT facilitator
output and intentionally live in their own namespace so cdp-facilitator can
route facilitator vs server reasons separately without substring ambiguity.
Exported Go symbols (`ErrPermit2InvalidSpender`, `ErrEip2612InvalidFormat`,
etc.) are unchanged; only the wire string values move.
The `facilitator.ErrMaxClaimableTooLow` constant is now aliased from
`batched.ErrCumulativeBelowClaimed` so the corrective-recovery client check
in `client/scheme.go` and the facilitator emitter share a single source of
truth and can never drift.
Downstream consumers (notably cdp-facilitator's `MapBatchSDKReasonToCDP`)
must coordinate: drop the legacy `batch_settlement_evm_*` translation in
favor of identity passthrough, or ship a deprecation shim during the
migration window.
Tests:
- constants_test.go pins the new `invalid_batch_settlement_evm_` prefix on
facilitator-mirroring constants and confirms the sibling-prefix discipline
on `ErrCumulativeAmountMismatch`.
- facilitator/errors_test.go inventories every exported facilitator
constant for non-empty + canonical-prefix + uniqueness, pins the exact
19 tokens covered by the CDP wire contract, and adds
`TestNoLegacyBatchSettlementEvmPrefix` as a guardrail against
accidental reintroduction of the old prefix.
Verification: full Go test suite green; all e2e + example Go modules build
clean; integration tests pass.
Co-authored-by: Cursor <cursoragent@cursor.com>
Audit of `go/mechanisms/evm/batched/facilitator/errors.go` (56 constants)
plus the resource-server emission sites uncovered three orphan facilitator
constants and six hard-coded server abort literals that should be tracked:
Orphans removed or wired up:
- ErrMissingEip712Domain — DELETED. Unreachable in the batch-settlement
scheme: voucher EIP-712 hashing uses a fixed `BatchSettlementDomain`,
not payer-supplied `requirements.extra.{name,version}` like the exact
EIP-3009 path. The constant was carried for parity with exact but had
no path to emit. errors.go now carries an explanatory comment so a
future patch that re-introduces a payer-supplied domain field can add
it back at the same time as the emitter.
- ErrChannelNotFound — WIRED. Added `state.Balance.Sign() == 0` check in
`verifyVoucherFields` so a non-existent or fully-drained channel emits
a dedicated reason instead of falling through to ErrMaxClaimableTooLow.
Mirrors TS `verifyVoucher` (voucher.ts:62) byte-for-byte.
- ErrPermit2AllowanceRequired — WIRED. After a standard-path Permit2
deposit simulation reverts, the new helper
`diagnosePermit2AllowanceShortfall` reads on-chain
`allowance(payer, Permit2)` and emits the dedicated reason when the
allowance is below the deposit amount. Falls back to the generic
`ErrDepositSimulationFailed` on any RPC error or sufficient allowance.
Mirrors exact's `CheckPermit2Prerequisites` diagnosis pattern.
Server abort literals promoted to exported constants under
`batched/errors.go`'s sibling-prefix block (these are resource-server
output, NOT facilitator output, so they keep the `batch_settlement_*` and
`missing_*` namespaces — never `invalid_*`):
- ErrChannelBusy = "batch_settlement_channel_busy"
- ErrMissingChannel = "missing_batch_settlement_channel"
- ErrChargeExceedsSignedCumulative = "batch_settlement_charge_exceeds_signed_cumulative"
- ErrRefundNoBalance = "batch_settlement_refund_no_balance"
- ErrRefundAmountInvalid = "batch_settlement_refund_amount_invalid"
- ErrRefundAmountExceedsBalance = "batch_settlement_refund_amount_exceeds_balance"
Wire values are unchanged — both Go and TS resource servers continue
emitting the same strings. The promotion just makes the inventory
trackable and drift-resistant: server/hooks.go, client/refund.go's
non-recoverable classifier, server/hooks_test.go, and the integration
test all now reference the constants instead of hard-coding the literals.
Tests:
- constants_test.go now asserts the sibling-prefix discipline on every
server-emitted constant (must start `batch_settlement_*`, must NOT
carry the `invalid_` envelope) plus the special `missing_*` envelope
on ErrMissingChannel.
- facilitator/errors_test.go inventory drops ErrMissingEip712Domain
with a comment pointing at errors.go's rationale.
- All previously hard-coded literal assertions in tests now reference
the canonical batched.Err* constants so a future rename trips the
tests instead of leaking through to wire consumers.
Verification: full Go test suite green; all 5 Go e2e modules and 3
example modules build clean; zero hard-coded reason literals remain in
non-test batched Go code; zero orphan constants.
Co-authored-by: Cursor <cursoragent@cursor.com>
| @@ -0,0 +1,205 @@ | |||
| # Batch-Settlement EVM Scheme (`go/mechanisms/evm/batched`) | |||
There was a problem hiding this comment.
Rename dir
| # Batch-Settlement EVM Scheme (`go/mechanisms/evm/batched`) | |
| # Batch-Settlement EVM Scheme (`go/mechanisms/evm/batch-settlement`) |
|
Remove examples/go/clients/batch-settlement/.!19032!batch-settlement and examples/go/servers/batch-settlement/.!19036!batch-settlement and examples/go/clients/batch-settlement/batch-settlement etc |
| @@ -0,0 +1,20 @@ | |||
| EVM_PRIVATE_KEY=0x... | |||
There was a problem hiding this comment.
seems TS examples use .env-local, while go uses .env-example
| SchemeBatched = "batch-settlement" | ||
|
|
||
| // BatchSettlementAddress is the deployed x402BatchSettlement contract address (CREATE2, all chains). | ||
| BatchSettlementAddress = "0x8f79473b50d67733349191d7349FE45977d44AF7" |
There was a problem hiding this comment.
Update to latest canonical
|
|
||
| // BuildChannelConfig constructs a ChannelConfig from payment requirements and scheme config. | ||
| func (c *BatchedEvmScheme) BuildChannelConfig(requirements types.PaymentRequirements) batched.ChannelConfig { | ||
| receiverAuthorizer := requirements.PayTo |
There was a problem hiding this comment.
Shouldn't fallback to payTo
3899e53 to
7351504
Compare
| // SkipHandler directive: bypass downstream handler, settle inline using the | ||
| // directive body. Used for refund acknowledgements where there is no resource | ||
| // response to return. | ||
| if result.SkipHandler != nil { |
There was a problem hiding this comment.
SkipHandler does not bypass the downstream handler in gin/echo, only for nethttp
| if h, ok := schemeServer.(BeforeVerifyHookProvider); ok { | ||
| if hook := h.BeforeVerifyHook(); hook != nil { | ||
| s.beforeVerifyHooks = append(s.beforeVerifyHooks, hook) | ||
| } | ||
| } |
There was a problem hiding this comment.
Scheme-provided server hooks are appended into the global hook list here, so a hook from one registered scheme can run for unrelated schemes/networks. TS stores scheme hooks separately and selects only the matched scheme. In TS hooks run in this order: manual -> matched network/scheme -> declared extensions aligning with #2109.
There was a problem hiding this comment.
Hook order is something to double check, should it be manual app defined hooks first or last? Now thinking probably last makes more sense?
| // Use signer address as payerAuthorizer for EOA path | ||
| payerAuthorizer = c.signer.Address() |
There was a problem hiding this comment.
Client example fails to make paid request when EVM_VOUCHER_SIGNER_PRIVATE_KEY is set.
Go signs the voucher with the voucher key, but commits the channel to the payer key as payerAuthorizer.
| // Use signer address as payerAuthorizer for EOA path | |
| payerAuthorizer = c.signer.Address() | |
| if c.config.VoucherSigner != nil { | |
| payerAuthorizer = c.config.VoucherSigner.Address() | |
| } else { | |
| payerAuthorizer = c.signer.Address() | |
| } |
|
Go still misses the equivalent changes from #2109 adding extension hook adapters and making paymentRequirements and payload deepreadonly. Go batch-settlement implementation mutates extra and payload in the hooks, what would break when the above is added. TS added explicit |
|
|
||
| ## Client Usage | ||
|
|
||
| Register `BatchedEvmScheme` with an `x402Client`. The client handles deposit, voucher signing, channel-state recovery, and corrective 402 resync transparently. |
There was a problem hiding this comment.
Probably want to rename the class names as well
| Register `BatchedEvmScheme` with an `x402Client`. The client handles deposit, voucher signing, channel-state recovery, and corrective 402 resync transparently. | |
| Register `BatchSettlementEvmScheme` with an `x402Client`. The client handles deposit, voucher signing, channel-state recovery, and corrective 402 resync transparently. |
Description
Tests
Checklist