Repository navigation
v0.4.0
·
28 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
Breaking
- Custom non-
ReplayKeyExtractorschemes lose in-process replay protection. The 0.3.x BC fallback (customSchemeContractimplementations validating EIP-3009-shapedpayload.authorizationoneip155:*networks) is removed. Such schemes now defer replay protection entirely to the facilitator's on-chain nonce check. To restore in-process protection, implementX402\Schemes\ReplayKeyExtractor::replayKey()on your custom scheme — seeUPGRADING.mdfor a migration diff. Built-inEvm\ExactScheme,Evm\Permit2Scheme, andUpto\UptoEvmSchemeare unaffected (they implement the interface since 0.3.0). The 0.3.1 deprecation hop the spec called for did not ship — adopters with custom EIP-3009-shaped schemes upgrade directly from 0.3.x to 0.4.0 and need to plan theReplayKeyExtractormigration before the bump. PaymentResponseCacheconstructor adds an optionalresourceResolver:parameter for cache-identity scoping, and the cache-key format changed (see Notes below). 0.3.x cached snapshots are unreadable by 0.4.0 readers — purge the cache namespace before deploying, or accept a one-time miss window during the upgrade.UPGRADING.mddocuments both paths.
What's new
X402\Server\IdempotencyKeyBuilder— transport-agnostic builder for paid-response idempotency cache keys. PSR-15 (PaymentResponseCache) and JSON-RPC (laravel-x402-mcp) consumers share the derivation. Usesjson_encode(notimplode) so caller-supplied scope strings can't collide via embedded delimiters; throws on emptybindingBytesso a transport adapter that forgets to validate the EIP-3009 signature field can't ship a forge-resistance hole. Schema versioned via an internalv: 1marker so the key shape can evolve deliberately.X402\Support\DecimalCompare::compare()— extracted from three near-identicalcompareAmount()private methods onEvm\ExactScheme,Evm\Permit2Scheme, andUpto\UptoEvmScheme. Spaceship comparison of stringified non-negative decimals; side-steps PHP-int overflow on USDC / EIP-3009 / Permit2 amount fields.X402\Server\InvokeResourceResolver— null-default + return-type guard for theClosure|ResourceResolver|nullshapePaymentEnforcerandPaymentResponseCacheboth accept. Centralises the resolution path so the two middlewares can't drift on edge cases.PaymentResponseCachecross-route scoping. The cache key now binds HTTP method + resolved resource alongside the signed-authorisation header bytes, so a paid response onGET /premium-Acannot replay ontoGET /premium-Bwhen the same X-PAYMENT header is reused.PaymentSignaturedoes not bind the resource it was signed against, so the cache key has to do that work itself. The new optionalresourceResolver:constructor argument controls how the resource is derived; default =$request->getUri()->getPath()(matchesPaymentEnforcer's default). Pass a custom resolver only when pricing-equivalent URIs should also share cached responses — pricing-collapse and content-collapse are different invariants.
Bug fixes
IdempotencyKeyBuildernon-injective serialisation — the inlinedimplode('|', …)PaymentResponseCacheused in 0.3.x let two different scope tuples collapse to the same preimage when any segment contained|(URIs and JSON-RPC method names can both legally contain it). The extracted helper switches to a structuredjson_encodepayload with explicit keys + length-prefixed string encoding via JSON quoting.- PSR-15 idempotency key ignored the protected resource —
PaymentSignature::fromHeader()only decodes(scheme, network, payload)and the cache key derived only from the header bytes, so two endpoints accepting the same X-PAYMENT header produced the same cache entry. The new method + resource scope closes the cross-route replay window.
Notes
PaymentEnforcer::guardReplay()reduced from ~50 lines to ~20. The hardcoded EIP-3009 normalisation (Constants::TRANSFER_METHOD_EIP3009,eip155:prefix matching) is gone — replay-key extraction is now entirely scheme-owned viaReplayKeyExtractor.- Cache-key format changed in two ways: serialisation switched from
implodetojson_encode, and the PSR-15 path now mixes in HTTP method + resolved resource. Both invalidate 0.3.x on-disk format.UPGRADING.mddescribes a per-prefix bump or one-time miss-window strategy for adopters with persistent caches. - Eight new tests across
IdempotencyKeyBuilderTest,DecimalCompareTest, and the existingPaymentEnforcerTest/PaymentResponseCacheTest— covering the BC fallback fall-through, cross-route replay, cross-method replay, resolver alignment, delimiter-bearing scope collisions, and empty-bindingBytesrejection. - No DTO shape change. No
NonceStoreContractchange. No behaviour change for built-in schemes (Evm\ExactScheme,Evm\Permit2Scheme,Upto\UptoEvmScheme,Evm\Erc7710Scheme, Stellar / SvmExactScheme).
Full Changelog: 0.3.0...0.4.0