feat(x402-server): Casper Network support (CEP-18 via an x402 facilitator) - #54
Open
mssteuer wants to merge 4 commits into
Open
feat(x402-server): Casper Network support (CEP-18 via an x402 facilitator)#54mssteuer wants to merge 4 commits into
mssteuer wants to merge 4 commits into
Conversation
|
Someone is attempting to deploy a commit to the Functor Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Adds Casper Network as a supported rail to
@altananetwork/x402-server, so amerchant can charge for a resource in CEP-18 tokens (wCSPR being the obvious
one) on
casper:casper(mainnet) orcasper:casper-test(testnet).Today the seller side is EVM-only by construction.
buildChallengehardcodesnetwork = \eip155:${cfg.chainId}`(challenge.ts:19), the decoder validates every party as a 20-byte address and only parseseip155:out of the envelope (decode.ts:4, decode.ts:30-37),verifyPaymentbuildsviemEIP-712 typed data and checks it via ERC-1271 (verify.ts:88-133), andsettlePaymentencodes EVM calldata fortransferWithAuthorization/Permit2` (settle.ts). None of that survives contact with Casper: accounts areed25519/secp256k1 public keys and 32-byte account hashes, the asset is a CEP-18
contract package hash, and settlement is a Casper transaction rather than
calldata.
Rather than widening those
viem-typed paths — which would meanAddressbecoming
stringand a lot ofif (isCasper)inside the EVM code — this addsa parallel, self-contained rail in
src/casper.ts. The EVM modules are nottouched at all (
git diff --statshows zero changes to challenge/decode/verify/settle/merchant/tokens/types).
Verification and settlement are delegated over HTTP to an x402 facilitator for
Casper. CSPR.cloud runs a public one at
https://x402-facilitator.cspr.cloud;this PR implements its documented
/verify,/settleand/supportedrequest/response shapes
(docs). Practically
that means the merchant needs no Casper node connectivity and holds no Casper
key — the facilitator checks the EIP-712
TransferAuthorizationsignatureagainst the payer's public key and submits the CEP-18
transfer_with_authorizationtransaction, paying the gas.New public API (all re-exported from the package root):
createCasperX402MerchantcreateX402Merchant:challengeBody/requirePayment/guardbuildCasperChallenge,casperPaymentRequirementspaymentRequirementsthe facilitator validates againstdecodeCasperPaymentPAYMENT-SIGNATURE/X-PAYMENTenvelope → typed paymentcheckCasperPaymentcreateCasperFacilitatorfetchis injectableCASPER_MAINNET,CASPER_TESTNET,CASPER_FACILITATOR_URLA few details worth calling out, because they came out of the facilitator's
documented error codes rather than guesswork:
buildCasperChallengerefusesmaxTimeoutSeconds < 6andcheckCasperPaymentreturnsinsufficient_timewhen under 6 seconds ofvalidity remain — the facilitator rejects both (
insufficient_time).extra.name/extra.versionare always emitted: the facilitator rebuildsthe EIP-712 domain from them and answers
missing_token_name/missing_token_versionotherwise./settlealways replies HTTP 200 with asuccessboolean, so a failedsettlement is turned into a thrown error carrying
errorReason, and themerchant answers 402 with it.
(
network_mismatch,pay_to_mismatch,amount_mismatch, …) so a merchantsees one vocabulary regardless of which side rejected the payment.
network and I did not want to bake an unverified hash into the SDK.
Docs: new "Casper" section in
docs/pages/sdk/x402-server.mdxand the packageREADME.
How was this tested?
New unit tests in
packages/x402-server/src/casper.test.ts(20 tests) coveringthe challenge shape on both Casper networks, price clamping, the sub-6s
rejection, envelope decode + round-trip back into the facilitator
paymentPayload, rejection of malformed input (non-base64, aneip155:network, a 20-byte EVM address where an account hash belongs, a short nonce),
every local rejection reason, the facilitator client against a stubbed
fetchasserting the exact documented
/verifyand/settlebodies and headers, andthe merchant flow end to end (402 challenge → settle → receipt, verify-first
mode, replay refusal, settlement failure surfacing the facilitator's reason,
guard()readingPAYMENT-SIGNATURE).Two of those tests exist specifically to prove the EVM side is untouched:
buildChallengestill emitseip155:56withassetTransferMethod: "eip3009",and
decodeXPaymentstill rejects a Casper envelope.The facilitator is not contacted over the network in tests —
fetchisinjected — so the suite is hermetic. The request/response shapes asserted are
the ones documented at docs.cspr.cloud; I have not run a payment against
mainnet.
Per CONTRIBUTING.md the wallet package's tests were run one file at a time; no
wallet sources are touched by this PR.
Anything breaking?
No. Nothing existing changes behavior —
src/index.tsgains exports and noexisting export's type or semantics move. The EVM rail files are byte-identical
to
staging. The only non-additive edits are the version bumps required by thechangelog convention.
staging(see CONTRIBUTING.md)CHANGELOG.mdentry under the pending## [x.y.z] - Unreleasedsection, and the target version reflects the change (fix → patch, feature → minor; see CONTRIBUTING.md) — or this is internal-only (CI/docs/tooling)The pending section did not exist yet, so following CONTRIBUTING.md this PR
creates
## [0.9.0] - Unreleased(a feature starts a minor) and bumps thepublishable versions to match:
@altananetwork/sdkand@altananetwork/mcpto0.9.0(with mcp's sdk range following), and@altananetwork/x402-server—the package that actually changed — to
0.3.0, noted under the heading the way@altananetwork/mcp's divergent versions are noted in earlier entries. Happyto drop the bumps if you'd rather do them at release time.