@rpp402/facilitator
v0.1.0
Published
x402-compatible facilitator core for RPP402: verify and settle signed payment authorizations on Robinhood Chain, non-custodially.
Maintainers
Readme
@rpp402/facilitator
Published under MIT to the @rpp402 scope on npm.
npm install @rpp402/facilitatorThe verify-and-settle core for running an x402-compatible facilitator on Robinhood Chain. It lets an HTTP resource server accept a signed, gasless payment authorization from a buyer and have it settled on-chain in RPP402's settlement asset - without the server touching a blockchain node or the facilitator ever holding funds.
For the exact scheme it relays an EIP-3009 transferWithAuthorization, so value moves buyer → seller directly and the facilitator only pays gas. Signature recovery and the on-chain submission are injected (bring your own viem/ethers and relayer), which keeps this package pure, dependency-free, and fully testable.
Usage
import { verifyPayment, settlePayment, supported, type FacilitatorConfig } from "@rpp402/facilitator";
import { recoverTypedDataAddress } from "viem";
const config: FacilitatorConfig = {
network: "robinhood",
chainId: /* Robinhood Chain id */ 0,
// Recover the signer of the TransferWithAuthorization (viem shown; ethers works too).
recoverSigner: (typedData, signature) =>
recoverTypedDataAddress({ ...typedData, signature: signature as `0x${string}` }),
// Optional: check nonce reuse and payer balance on-chain.
// chain: { balanceOf, isAuthorizationUsed },
};
// POST /verify
const verification = await verifyPayment(request, config);
// -> { isValid: true, payer: "0x…" } | { isValid: false, invalidReason: "…" }
// POST /settle (verifies first, then relays transferWithAuthorization)
const settlement = await settlePayment(request, {
...config,
settle: async (payload, requirements) => {
const tx = await relayTransferWithAuthorization(payload, requirements); // your funded relayer
return { transaction: tx };
},
});
// -> { success: true, transaction: "0x…", network: "robinhood", payer: "0x…" }
// GET /supported
supported([{ network: "robinhood", scheme: "exact" }]);What it checks
verifyPayment runs, in order: x402 version, scheme, network, recipient (payTo), amount, validAfter/validBefore timing (with clock tolerance), nonce format, and signature recovery. Supply a ChainReader to also reject replayed nonces and payers with insufficient balance. settlePayment re-verifies and only then calls your injected settle - it never settles a payload that fails verification.
Non-custodial by design
The only component that touches a funded key is the settle function you inject. Because the exact scheme uses transferWithAuthorization, funds move directly from payer to payTo; the relayer just submits the transaction and pays gas. Keep it that way - it's both the security boundary and the regulatory posture.
Learn more
- x402 standard: https://x402.org
- Protocol RFCs: https://rpp402.com/docs
- SDK:
@rpp402/sdk
