@canopy-finance/x402-client
v0.1.0
Published
Buyer-side x402 client for the Canopy facilitator: build and post exact-permit2-v2 payments (EIP-712 Permit2 witness). Works in the browser and in Node. Zero runtime deps except viem (peer).
Maintainers
Readme
@canopy-finance/x402-client
Buyer-side x402 client for the Canopy facilitator. Build and
post exact-permit2-v2 payments — an EIP-712 Permit2 witness that binds both
the recipient and a stable serviceId into the payer's signature — without
hand-rolling the typed data.
- Works in the browser and in Node (uses
fetch+globalThis.cryptoonly). - Zero runtime dependencies except viem (peer).
- Signer-agnostic: pass any
signTypedData(viem wallet, ethers, injected wallet…). - ESM + CJS + type declarations.
The EIP-712 shape here is the single source of truth shared with the facilitator's verifier, so buyer and verifier can never drift.
Install
npm install @canopy-finance/x402-client viemThe four-step buyer flow
import {
getSupported,
permit2ApprovalTx,
createExactPermit2V2Payment,
post,
} from "@canopy-finance/x402-client";
import { createWalletClient, custom } from "viem";
const url = "https://facilitator.canopyfinance.io";
// 1. Discover the facilitator's advertised kind (validates schemeVersion = 2).
const kind = await getSupported(url);
// 2. (once per payer) Approve Permit2 to move the asset on your behalf.
const wallet = createWalletClient({ account, transport: custom(window.ethereum) });
await wallet.writeContract(permit2ApprovalTx(kind));
// 3. Build a signed payment. `serviceId` comes from the resource's 402
// response (paymentRequirements.extra.serviceId).
const body = await createExactPermit2V2Payment({
chainId: 4663,
kind,
owner: account.address,
payTo, // the seller's recipient address
amount: 10_000n, // atomic units (e.g. 0.01 USDG at 6 decimals)
serviceId, // bytes32 from the resource's 402
signTypedData: (args) => wallet.signTypedData({ account, ...args }),
});
// 4. POST to /verify, then /settle.
const verified = await post(url, "verify", body);
if (verified.isValid !== true) throw new Error("verify failed");
const settled = await post(url, "settle", body /*, apiKey */);Migration from v1 (exact-permit2)
The legacy exact-permit2 scheme is read-only. To integrate:
- Read
serviceIdfrom the resource's 402 (extra.serviceId). - Call
createExactPermit2V2Payment(the v1 creatorcreatePermit2Paymentnow throwsUnsupportedSchemeError). - Target a facilitator advertising
schemeVersion: 2/payloadVersion: 2.
Exports
getSupported(url)— fetch + validate theexact-permit2-v2kind.permit2ApprovalTx(kind, amount?)— viemwriteContractconfig for the one-time approval.createExactPermit2V2Payment(params)— build a signed, ready-to-POST payment body.post(url, "verify" | "settle", body, apiKey?)— POST helper.- Constants:
PERMIT2_TYPES,WITNESS_TYPE_STRING,SUPPORTED_SCHEME,SUPPORTED_SCHEME_VERSION,SUPPORTED_PAYLOAD_VERSION. - Errors:
UnsupportedSchemeError,UnsupportedSchemeVersionError,UnsupportedPayloadVersionError,MissingServiceIdError.
License
MIT
