API surface
All public APIs are typed TypeScript exports. Browser and chain helpers are explicit: you pass viem clients on each call, and contract addresses wherever the SDK does not know them. It knows Curvy's own networks (see Curvy networks). SDK settings are bound once through initialize; there are no globals.
Entry points
| Import | Runtime | Contents |
|---|---|---|
@0xcurvy/payments-sdk | Browser and Node | Re-exports browser-safe helpers, types and constants |
@0xcurvy/payments-sdk/intent | Browser and Node | Request parse / sign / verify |
@0xcurvy/payments-sdk/transport | Browser and Node | Fragment and URL helpers; CURVY_CHECKOUT_URL |
@0xcurvy/payments-sdk/chain | Browser and Node | Curvy's built-in networks; receipt discovery hints; portal prediction (internal and unstable) |
@0xcurvy/payments-sdk/contracts | Browser and Node | ABIs |
@0xcurvy/payments-sdk/economics | Browser and Node | Fee reads, quotes, minimum amounts |
@0xcurvy/payments-sdk/x402 | Browser and Node | x402 wire types, broadcaster and facilitator clients, payer helper, EIP-712 types, parsers, header codec |
@0xcurvy/payments-sdk/x402/merchant | Node only | createX402Merchant: the merchant side of exact and curvy-transfer in one object |
@0xcurvy/payments-sdk/merchant/keys | Browser and Node | Well-known signer document; receiving-keys value (encodeReceivingKeys, parseReceivingKeys); generateCheckoutSigningKey |
@0xcurvy/payments-sdk/merchant | Node only | initialize, createPaymentRequest, verifyPayment, serializePaymentRecord / parsePaymentRecord |
Merchant (/merchant, Node only)
import {
initialize,
createPaymentRequest,
buildPaymentRequest,
verifyPayment,
PaymentVerificationError,
serializePaymentRecord,
parsePaymentRecord,
} from "@0xcurvy/payments-sdk/merchant";| Export | Role |
|---|---|
initialize(config) | Binds the environment (or network), the default tokens, the receiving keys, merchantOrigin, confirmations, paidWhen and TTL. Returns { chainId, tokens, aggregatorAddress, createPaymentRequest, verifyPayment }. |
createPaymentRequest(parameters) | Standalone: derives a note and returns an unsigned request. ttlSeconds is optional (default 600, at most 86400). description is optional: what the buyer is paying for, at most 120 characters of plain text, signed with the payment and shown at checkout and on the buyer's receipt. |
buildPaymentRequest(parameters) | Same as createPaymentRequest, but ttlSeconds is required |
verifyPayment(parameters) | Decides whether a stored request has been paid on chain. aggregatorAddress defaults to Curvy's aggregator on request.chainId and is required on a chain the SDK does not know. See Confirming payments. |
PaymentVerificationError | Thrown by verifyPayment when it cannot produce a status. It has a code. |
serializePaymentRecord(record) / parsePaymentRecord(value) | The one value to store per payment attempt: { payment, fromBlock, verification } (the signed package, the scan start and the latest verifyPayment result) as a versioned JSON string. Store it whole; parsePaymentRecord refuses unknown versions and fields. PAYMENT_RECORD_VERSION is 1 today. |
buildMerchantKeySet, parseMerchantKeySet, encodeReceivingKeys, parseReceivingKeys, RECEIVING_KEYS_VERSION | Also re-exported from /merchant/keys and the root entry |
initialize config fields:
| Field | Required | Default | Description |
|---|---|---|---|
receivingKeys | one of receivingKeys or recipient | — | Preferred. Your public receiving keys as one value, your public key for payments (CURVY_PAYMENTS_PUBLIC_KEY in the .env block of the web app's Payments setup, step 3), such as 01Q1JL…f57Q. initialize parses it with parseReceivingKeys and throws if it is edited, cut off or from a newer format version. The SDK derives a fresh one-time destination from the keys for every request. See Receiving keys. |
recipient | one of receivingKeys or recipient | — | The same public keys as { S, V, babyJubjubPublicKey }, each a decimal x.y string. Passing both, or neither, throws pass exactly one of receivingKeys (preferred) or recipient. Use only public material. |
environment | yes | — | "mainnet" (Arbitrum One, 42161, real money) or "testnet" (Ethereum Sepolia, 11155111, test money). There is no default, so nobody takes real money, or runs a test, by accident. Anything else throws. The SDK knows Curvy's contracts on both networks; see Curvy networks. |
network | no | the environment's network | { chainId, aggregatorAddress? } for any other chain: a staging or local Curvy deployment, or a Curvy network this SDK version does not know. chainId must be a positive safe integer. aggregatorAddress defaults to Curvy's aggregator on a chain the SDK knows, and is required on any other chain: without it, sdk.aggregatorAddress is undefined and verifyPayment throws INVALID_INPUT. A known chain must match the environment: environment: "mainnet" with chain id 11155111 throws. |
tokens | no | every token Curvy takes on the network | The tokens requests take, preferred first: symbols Curvy takes on the network ("USDC", and "USDT" on mainnet) or token addresses, each once, all with the same decimals. Any other symbol throws. The default is USDC and USDT on mainnet and USDC on testnet. On a chain the SDK does not know there is no default: pass addresses here, or tokens on every createPaymentRequest. See Tokens and other networks. |
merchantOrigin | yes | — | Your shop’s bare origin (https://shop.example): scheme and host only. It is signed into every request and is the base of the return URL. |
confirmations | yes | — | Blocks, counting the shield block, before bound verifyPayment returns paid instead of confirming. It must be a positive safe integer. |
paidWhen | no | "shielded" | When bound verifyPayment returns paid. "shielded": once the note has confirmations blocks (the money is safe in the vault). "committed": also once the note is in a CommittedNotes batch (the money is spendable); until then it returns confirming. Anything else throws. Bound at init; there is no per-call override. See When a payment counts as paid. |
ttlSeconds | no | 600 | Request lifetime. It sets expiry. It is also the buyer’s funding window: Curvy will not register or shield the payment after expiry. At most 86400 (24 h); initialize and createPaymentRequest refuse more. |
checkoutCompletePath | no | /checkout/complete | Absolute path on merchantOrigin where Curvy sends the customer after a shield: {merchantOrigin}{path}#txHash=…. It is always included in the EIP-712 payload. |
Signatures:
interface PaymentSDK {
readonly chainId: number; // requests are made for it; your RPC must serve it
readonly tokens: readonly Address[]; // the default tokens; empty on an unknown chain unless configured
readonly aggregatorAddress: Address | undefined; // undefined on an unknown chain unless configured
// amount in the tokens' base units (4 USDC is 4_000_000n); tokens default to sdk.tokens
createPaymentRequest(parameters: { amount: bigint; tokens?: string[]; description?: string }): Promise<PaymentIntent>;
verifyPayment(parameters: BoundVerifyPaymentParameters): Promise<PaymentVerification>;
}
type BoundVerifyPaymentParameters = Omit<VerifyPaymentParameters, "confirmations" | "paidWhen">;
// the bound verifyPayment uses sdk.aggregatorAddress unless you pass aggregatorAddress
type PaidWhen = "shielded" | "committed";
interface VerifyPaymentParameters {
publicClient: PaymentVerifyClient; // a viem PublicClient works
aggregatorAddress?: Address; // default: Curvy's aggregator on request.chainId; required on other chains
request: PaymentIntent; // exactly what createPaymentRequest returned, stored server-side
confirmations: number; // positive safe integer
paidWhen?: PaidWhen; // default "shielded"; "committed" also waits for the batch commit
allowBridgeShortfall?: boolean; // default true: on Arbitrum One up to 3% short counts (a payment bridged from another network); false wants it in full
txHash?: Hex; // untrusted hint
fromBlock?: bigint; // required when txHash is omitted; ignored with txHash
}
type PaymentStatus = "not_found" | "confirming" | "paid" | "underpaid" | "wrong_token";
interface PaymentVerification {
status: PaymentStatus;
payment: VerifiedPayment | null; // null only for not_found
}
interface VerifiedPayment {
txHash: Hex;
blockNumber: bigint;
confirmations: bigint;
noteId: bigint;
vaultTokenId: bigint;
token: Address | null; // which of the request's tokens the note is in; null for wrong_token
netAmount: bigint;
minimumNetAmount: bigint; // what amount yields after the vault fees, less up to 3% of it on Arbitrum One
shortfall: bigint; // how much less the note carries than the full amount would have: the bridge cost you absorbed
portalShield: boolean;
committed: boolean; // in a CommittedNotes batch (spendable); required for paid only with paidWhen "committed"
siblingNoteIds: bigint[]; // other notes seen under this request's ownerHash; only one can ever be spent
}
type PaymentVerificationErrorCode =
| "WRONG_CHAIN" | "TX_NOT_FOUND" | "REVERTED" | "NOT_A_SHIELD"
| "UNRELATED" | "MISSING_FROM_BLOCK" | "INVALID_INPUT";
interface PaymentVerifyClient
extends Pick<PublicClient,
"getChainId" | "getBlockNumber" | "getLogs" | "getTransaction" | "getTransactionReceipt" | "readContract"> {}The standalone createPaymentRequest takes { receivingKeys | recipient, amount, token, tokens?, chainId, merchantOrigin, checkoutCompletePath?, description?, ttlSeconds? }: exactly one of receivingKeys (preferred) or recipient, as in initialize. Here token is an address, tokens (optional) lists every token the request takes with token first, and chainId is passed directly. buildPaymentRequest and createX402Merchant take the same choice.
Request signing (/intent)
The EIP-712 export names keep PaymentIntent for wire compatibility.
import {
acceptedTokens,
buildPaymentIntentTypedData,
parsePaymentIntent,
parseSignedPaymentIntent,
paymentIntentTypes,
signPaymentIntent,
verifyPaymentIntent,
} from "@0xcurvy/payments-sdk/intent";| Export | Role |
|---|---|
signPaymentIntent(request, signer) | Returns { intent, signature }. signer is any (typedData) => Promise<Hex>, such as a viem account’s signTypedData or a KMS adapter. It checks only that the result is a 65-byte signature, not who signed it (see Signing with a KMS or HSM). It refuses a request whose expiry is more than 86400 seconds (24 h) away, however the request was built. |
verifyPaymentIntent(payment, { keySet, expectedChainId, expectedToken, nowSeconds? }) | Recovers the signer and checks it is in keySet and before its notAfter. Also checks expiry, chain and token, and refuses a request whose expiry is more than 24 h plus 5 minutes (clock skew) after nowSeconds. Returns { intent, signer }. |
parsePaymentIntent / parseSignedPaymentIntent | Canonicalize untrusted input |
acceptedTokens(intent) | Every token the request takes: intent.tokens, or [intent.token] when it takes one |
buildPaymentIntentTypedData / paymentIntentTypes / describedPaymentIntentTypes / multiTokenPaymentIntentTypes | EIP-712 payload: domain { name: "Curvy Payments", version: "1", chainId }. The version is part of the format: a later checkout can refuse a retired one. See What you sign. |
parsePaymentIntent rejects:
- unknown fields, such as
paymentIdorrecovery; - non-canonical decimals (leading zeros);
- an
amountoutside[1, 2^256); - an
ownerHashoutside[1, r), whereris the BN254 scalar field; ephemeralKeyXorephemeralKeyYat or above the BN254 base field;- a
viewTagoutsideuint16; - a
chainIdorexpirythat is not a positive safe integer; - a
tokenslist that doesn't start withtoken, lists a token twice, or has fewer than 2 or more than 8 tokens.
An omitted checkoutCompletePath becomes /checkout/complete.
Transport (/transport)
import {
buildCheckoutCompleteUrl,
buildCheckoutRetryUrl,
buildCheckoutUrl,
CURVY_CHECKOUT_URL,
decodePaymentIntentFragment,
encodePaymentIntentFragment,
} from "@0xcurvy/payments-sdk/transport";| Export | Role |
|---|---|
buildCheckoutUrl(signed) / buildCheckoutUrl(checkoutUrl, signed) | Puts the signed package in the fragment of Curvy’s checkout page, CURVY_CHECKOUT_URL, or of checkoutUrl: another checkout page, such as a staging app’s https://app.curvy.dev/checkout. Keeps that URL’s origin, path and query. |
CURVY_CHECKOUT_URL | https://app.curvy.box/checkout, Curvy’s hosted checkout page. Also exported from the root entry. |
encodePaymentIntentFragment / decodePaymentIntentFragment | Unpadded base64url JSON fragment codec |
buildCheckoutCompleteUrl(request, txHash) | {merchantOrigin}{path}#txHash=…: the return after a successful shield |
buildCheckoutRetryUrl(request) | {merchantOrigin}{path}#retry=<ephemeralKeyX>: the return that asks the merchant for a fresh attempt (see Fresh payment attempts) |
Chain (/chain, browser-safe)
import {
CURVY_NETWORKS,
findNoteInReceipt,
getCurvyNetwork,
getDefaultCurvyNetwork,
} from "@0xcurvy/payments-sdk/chain";| Export | Role |
|---|---|
CURVY_NETWORKS | Curvy's networks built into the SDK, as frozen CurvyNetwork objects: Arbitrum One and Ethereum Sepolia (see Curvy networks) |
getCurvyNetwork(chainId) | The CurvyNetwork for chainId, or undefined for a chain the SDK does not know |
getDefaultCurvyNetwork(environment) | The network an environment pays into: Arbitrum One for "mainnet", Ethereum Sepolia for "testnet". Throws for any other value. |
predictPortalAddress({ publicClient, portalFactoryAddress, ownerHash, recovery }) | Internal and unstable, for Curvy's checkout: PortalFactory.getEntryPortalAddress(ownerHash, recovery). Its inputs change when Curvy migrates portal factories. Merchants do not compute portal addresses; x402 merchants use createX402Merchant. |
findNoteInReceipt(receipt, [ephemeralKeyX, ephemeralKeyY], aggregatorAddress) | Returns { noteId, netAmount, token } or null. token is the vault token id. It matches only R, so it is a discovery hint, not proof of payment. |
verifyPayment is no longer exported from /chain or from the root entry. Import it from /merchant.
Curvy networks
type CurvyEnvironment = "mainnet" | "testnet"; // "mainnet" takes real money, "testnet" test money
interface CurvyDeployment {
aggregator: Address;
portalFactory: Address;
vault: Address;
}
interface CurvyCurrency {
address: Address;
symbol: string;
decimals: number;
vaultTokenId: string; // the token's id in the Curvy vault
}
interface CurvyNetwork extends CurvyDeployment {
chainId: number;
name?: string;
testnet?: boolean;
minPortalUsd?: number; // portals worth less than this many USD are failed instead of shielded
currencies: CurvyCurrency[];
}environment | Network | Tokens |
|---|---|---|
"mainnet" | Arbitrum One (42161), real money | USDC (default), USDT |
"testnet" | Ethereum Sepolia (11155111), test money | USDC |
The addresses are listed in Getting started. They are built into the SDK rather than read from a Curvy service at runtime, because whoever controls the aggregator address decides what counts as a payment. The aggregator and the vault are upgradeable proxies, so their addresses stay the same across upgrades. A CurvyNetwork has the shape of the portal broadcaster's GET /portal/networks/:chainId.
CURVY_NETWORKS, getCurvyNetwork, getDefaultCurvyNetwork and the types CurvyEnvironment, CurvyNetwork, CurvyCurrency and CurvyDeployment are exported from the root entry and /chain. /x402 also re-exports CurvyNetwork, CurvyCurrency and CurvyDeployment.
Merchant keys (/merchant/keys)
import {
buildMerchantKeySet,
generateCheckoutSigningKey,
parseMerchantKeySet,
} from "@0xcurvy/payments-sdk/merchant/keys";
const keySet = buildMerchantKeySet([{ address: signer.address, notAfter: "2027-01-01T00:00:00.000Z" }]);| Export | Role |
|---|---|
buildMerchantKeySet(signers, options?) / parseMerchantKeySet(value) | Produce and validate the /.well-known/curvy-payments.json document (version: 1, alg: eip712-secp256k1). notAfter is an ISO string or a Date. options.icon adds the optional checkout icon: an absolute .png or .webp path on the merchant origin, at most MAX_MERCHANT_ICON_PATH_LENGTH (256) characters. options.name adds the optional shop name checkout shows beside your domain: plain text, trimmed, at most MAX_MERCHANT_NAME_LENGTH (60) characters, no control or text-direction characters. |
generateCheckoutSigningKey() | Returns { privateKey, address }: a new random secp256k1 key used only to sign checkout requests. It holds no funds, pays no gas and is independent of any wallet or Curvy key. Keep privateKey in your secret store; publish address with buildMerchantKeySet. |
encodeReceivingKeys / parseReceivingKeys / RECEIVING_KEYS_VERSION | The receiving-keys value (below) |
The same key from the command line, on the backend:
npx @0xcurvy/payments-sdk@0.2.0-rc.3 create-signer [--out <file>]It prints the public address and writes the private key to an owner-only file (default curvy-checkout-signer.secret.json), refusing to replace an existing one. A KMS or HSM can hold the key instead (see Signing with a KMS or HSM).
Receiving keys value
import { encodeReceivingKeys, parseReceivingKeys, RECEIVING_KEYS_VERSION } from "@0xcurvy/payments-sdk/merchant/keys";
function encodeReceivingKeys(recipient: PaymentRecipient): string;
function parseReceivingKeys(value: string): PaymentRecipient; // { S, V, babyJubjubPublicKey } as decimal "x.y"
const RECEIVING_KEYS_VERSION: "01";The receiving-keys value is your three public receiving keys packed into one line, so you copy one thing from the web app instead of three. It is the CURVY_PAYMENTS_PUBLIC_KEY value in the web app's Payments setup, and what initialize({ receivingKeys }) and createX402Merchant({ receivingKeys }) take. It holds public keys only; packing is not encryption. It never contains a secret, a derivation index or a reference to a parent account, so a business account exports only its own public keys.
The format is VERSION + BODY:
VERSIONis two lowercase hex digits.01is today's keys:S(secp256k1),V(BN254) and the BabyJubjub public key.02is reserved for a later protocol version that adds keys.00is never valid.BODYis base64url (RFC 4648 §5, no padding) of the ASCII markerCRK, thenS.x,S.y,V.x,V.y,BabyJubjub.x,BabyJubjub.yas 32-byte big-endian numbers, then a 4-byte checksum. A version01body is 199 bytes, and the whole value is 268 characters.- The checksum is the first 4 bytes of SHA-256 over the version digits,
CRKand the 192 key bytes. It catches typos and cut-off copies, and it covers the version digits, so changing01by hand does not produce another valid value.
parseReceivingKeys is strict. It throws when the version is not two lowercase hex digits, is 00, or is any version other than 01 (that error names the version and says to upgrade @0xcurvy/payments-sdk); when the body has characters outside base64url, padding, or a non-canonical ending; when the length is wrong; when the CRK marker is missing (not a Curvy receiving-keys value); when the checksum does not match; and when a coordinate is zero, is not below its field modulus, or the point is not on its curve (S on secp256k1, V on BN254 G1, the BabyJubjub key on the BabyJubjub curve). encodeReceivingKeys applies the same key checks, so it refuses keys it could not parse back.
Economics
import {
feeBreakdown,
minimumPaymentAmount,
quotePayment,
readChainFees,
} from "@0xcurvy/payments-sdk/economics";| Export | Role |
|---|---|
readChainFees({ publicClient, token, chainId?, vaultAddress?, blockNumber? }) | Read ChainFees (depositFeeBps, portalDeployment, pendingNoteCommitment) for a token address from the vault, optionally at a past blockNumber. Pass chainId to use Curvy's vault on that network, or vaultAddress, which is required on a chain the SDK does not know. |
quotePayment | FeeBreakdown for a gross amount on a PaymentRail ("portal" or "direct") |
feeBreakdown | The same breakdown from positional values (gross, depositFeeBps, portalDeployment, pendingNoteCommitment) |
minimumPaymentAmount | Smallest gross amount whose net is at least minNetAmount (on-chain floor only) |
FeeBreakdown fields are decimal strings in token base units: depositFeeBps, percentageFee, portalDeployment, pendingNoteCommitment, totalFees, netAmount. ChainFees, FeeBreakdown, PaymentRail, feeBreakdown, minimumPaymentAmount, quotePayment and readChainFees are also exported from the root @0xcurvy/payments-sdk. See Fees and minimum amounts.
x402 merchant
import { createX402Merchant, createMemoryPaymentStore, toResponse } from "@0xcurvy/payments-sdk/x402/merchant";| Export | Role |
|---|---|
createX402Merchant(config) | Async. Reads the chain id, takes the Curvy addresses built into the SDK on Curvy's networks (from the broadcaster on any other chain), reads the broadcaster's minimum per portal, each token's vault id and EIP-712 domain (built in for Curvy's tokens), checks the facilitator's /supported (on otherNetworks too) if one is set, returns an X402Merchant |
X402Merchant.charge(request, { price, description?, mimeType?, resource? }) | One call per request: { status: "payment-required", response, payment, error? } or { status: "paid", payment, headers }. paid only once payTo holds the amount on chain, or, on one of otherNetworks, once the facilitator reports it settled |
X402Merchant.getPayment / listPayments | Read stored X402Payment records by payTo |
X402Merchant.shield / confirm | Retry the background steps by hand: register the funded portal with the broadcaster and read its status; one verifyPayment attempt (the /merchant check: the note must pay this request's owner, token and amount after fees) |
X402Merchant.fees(token?) / quote(price, token?) / minimumPrice | Portal-rail fee reads for one of your tokens (the first by default); minimumPrice is the highest on-chain floor among your tokens or the broadcaster's USD minimum in token units, whichever is higher |
X402Merchant.minimumPortalUsd | The broadcaster's USD minimum per portal, when it reports one |
X402Merchant.close | Stop background shield and confirmation work |
toResponse(response) | Turn a 402 result into a Fetch Response |
createMemoryPaymentStore({ maxEntries }) | The default X402PaymentStore; implement { get, put, list } for a database, plus claim(payTo, from, to) (atomic compare-and-set) when several instances share it |
ShieldRefusedError | Thrown by shield (and reported as an error event) when the broadcaster reaches a terminal state for the portal; retrying is pointless |
createX402Merchant config fields:
| Field | Required | Default | Description |
|---|---|---|---|
broadcaster | no | https://api.curvy.box (CURVY_BROADCASTER_URL) | Curvy portal broadcaster URL or BroadcasterClient. Shields every funded portal into your note, reports its minimum per portal, and reports the Curvy contract addresses on chains the SDK does not know |
facilitator | no | <broadcaster>/portal/x402, so https://api.curvy.box/portal/x402 (CURVY_FACILITATOR_URL) | The x402 v2 facilitator that settles exact (EIP-3009): Curvy's by default, or any other facilitator URL or FacilitatorClient. false offers only curvy-transfer |
schemes | no | ["exact", "curvy-transfer"], or ["curvy-transfer"] when facilitator is false | X402Scheme[]; exact needs a facilitator |
rpcUrl or publicClient | one | — | Chain access (X402MerchantClient is the subset of viem's PublicClient used) |
receivingKeys or recipient | one | — | Your public receiving keys: the one 01… value (CURVY_PAYMENTS_PUBLIC_KEY, preferred) or { S, V, babyJubjubPublicKey }. Passing both, or neither, throws |
tokens | no | every token Curvy takes there (USDC and USDT on Arbitrum One, USDC on Sepolia); required on other chains | Symbols Curvy takes on the chain or token addresses, preferred first, each registered in the Curvy vault (EIP-3009 for exact), all with the same decimals. The 402 offers each |
tokenDomains | no | built in for Curvy's tokens, else read on chain | { [address]: { name, version } } for tokens without version() |
otherNetworks | no | none | Chain ids where exact is also offered in USDC, bridged to Arbitrum One: Ethereum 1, Base 8453, Optimism 10, Polygon 137, Linea 59144 (X402_BRIDGED_TOKENS). Arbitrum One merchants only. Offered per price only while the bridge is quoted under 3%; the facilitator's settlement is the go-ahead there. See Taking payments on other networks |
addresses | no | built into the SDK on Curvy's networks; elsewhere from the broadcaster's GET /portal/networks/:chainId | aggregator, portalFactory, vault. Each one you pass comes first. On any other chain, pass them in production |
recovery | no | NO_RECOVERY_ADDRESS | Portal recovery address every payTo is derived with. The default can never reclaim funds |
shieldDeadlineSeconds | no | 86400 | How long the broadcaster keeps trying to shield a funded portal, and how long the SDK polls it |
enforceBroadcasterMinimum | no | true | Refuse prices below the broadcaster's minPortalUsd, treating the token as USD-pegged with the decimals the broadcaster reports. Set false only for non-USD tokens with your own guard |
settleTimeoutMs | no | 60000 | curvy-transfer: how long charge() waits for a presented transaction to be mined |
transferConfirmations | no | 1 | curvy-transfer: blocks a transfer needs before the resource is served |
confirmations | no | 12 | Blocks, counting the shield block, before confirm reports the payment confirmed (passed to verifyPayment) |
challengeTtlSeconds | no | 300 | Challenge and authorization lifetime |
merchantOrigin | no | request URL origin, else Host header | Your public origin; set it behind a proxy or when the framework passes a relative URL |
store | no | in-memory | X402PaymentStore |
onEvent | no | — | challenged, settled, shielded, confirmed, failed, error |
autoShield | no | true | Shield and confirm in the background after settlement |
confirmPollMs / confirmTimeoutMs | no | 2000 / 600000 | Background polling cadence and confirmation give-up time |
fetch | no | global fetch | Used for the broadcaster and facilitator clients created from URLs |
X402Payment fields: payTo, status (pending, settling = settlement requested and possibly landed, settled, shielded, confirmed, failed = refused by the facilitator with an empty portal, expired), amount, resource, createdAt, expiresAt, accepts (the 402 rows, one per scheme), note (your private payment reference), payer?, token? (the token on your network it counts in), paidOn? (the network it was paid on, when another), settleTxHash?, shieldTxHash?, noteId?, netAmount?, portalState? (the broadcaster's last reported state), error?. X402Merchant also exposes chainId, network, tokens (each { address, symbol?, decimals, vaultTokenId, domain? }), otherNetworks, addresses, schemes, recovery, broadcaster and facilitator?.
x402
import {
createBroadcasterClient,
createFacilitatorClient,
createX402Payer,
EXACT_SCHEME,
PAYMENT_REQUIRED_HEADER,
parsePaymentPayload,
TRANSFER_SCHEME,
x402Network,
} from "@0xcurvy/payments-sdk/x402";| Export | Role |
|---|---|
X402_VERSION | 2, the x402 protocol version the SDK speaks |
CURVY_BROADCASTER_URL / CURVY_FACILITATOR_URL | https://api.curvy.box / https://api.curvy.box/portal/x402: Curvy's production endpoints, the defaults of the clients and of createX402Merchant |
CURVY_FACILITATOR_PATH / facilitatorUrlFor(broadcasterUrl) | /portal/x402; the facilitator a given portal broadcaster serves |
EXACT_SCHEME / TRANSFER_SCHEME | "exact" / "curvy-transfer" |
ASSET_TRANSFER_METHOD / TRANSFER_METHOD | "eip3009", the token authorization payers sign for exact; "erc20-transfer", the curvy-transfer method |
NO_RECOVERY_ADDRESS | 0x…dEaD, the default portal recovery address nobody controls |
PAYMENT_REQUIRED_HEADER, PAYMENT_SIGNATURE_HEADER, PAYMENT_RESPONSE_HEADER | The x402 v2 header names |
x402Network / parseX402Network | chainId ↔ CAIP-2 eip155:<chainId> |
X402PaymentRequired, X402PaymentRequirements, X402PaymentPayload, X402ResourceInfo, X402VerifyResponse, X402SettleResponse, X402SupportedResponse, X402SupportedKind, Eip3009Authorization, ExactPaymentPayload, TransferPaymentPayload, CurvyDeployment | x402 v2 wire types, plus the Curvy contract addresses the broadcaster reports |
createBroadcasterClient({ url?, fetch?, timeoutMs?, headers? }) | network(chainId) (GET /portal/networks/:chainId), registerPayment (POST /portal/payments), status (GET /portal/status) on the portal broadcaster; throws BroadcasterError. Types: BroadcasterClient, CurvyNetwork, CurvyCurrency, PortalPaymentRegistration, PortalPaymentStatus, PortalPaymentState |
TERMINAL_PORTAL_FAILURES | Portal states the broadcaster never leaves (compliance_failed, expired, failed) |
createFacilitatorClient({ url?, fetch?, timeoutMs?, headers? }) | supported, verify, settle against an x402 v2 facilitator (Curvy's by default) on plain fetch; throws FacilitatorError on unexpected replies |
createX402Payer({ signer?, send?, maxAmount, fetch?, network?, asset?, now?, retryForMs?, retryEveryMs? }) | Agent-side fetch that pays one 402 up to maxAmount: exact with signer, curvy-transfer with send (exact preferred when both are offered); also pay(required). now is the clock for validBefore |
selectExactRequirements, createExactPayment, selectTransferRequirements, createTransferPayment, encodePaymentSignature, decodePaymentRequired, decodePaymentResponse, paymentRequiredFrom, paymentResponseFrom | Payer building blocks. Types: X402Signer, X402TransferSender, Eip3009TypedData |
parsePaymentRequired, parsePaymentRequirements, parseResourceInfo, parsePaymentPayload, parseExactPayload, parseTransferPayload, parseVerifyResponse, parseSettleResponse, parseSupportedResponse, parseCurvyDeployment | Validate untrusted JSON into the wire types |
requirementsEqual, canonicalJson | Compare requirement rows regardless of key order and address casing |
parseUint, parseAddress, parseHex | Low-level parse helpers |
transferWithAuthorizationTypes, eip3009Domain({ chainId, token, name, version }) | EIP-712 types and domain payers sign for exact |
encodeBase64Json / decodeBase64Json | Browser-safe codec for the PAYMENT-* headers |
See x402 and the Payments SDK.
Contracts (/contracts)
import {
aggregatorAbi,
pendingNotesAbi,
portalFactoryAbi,
vaultAbi,
} from "@0xcurvy/payments-sdk/contracts";pendingNotesAbi is the same ABI as aggregatorAbi. vaultAbi includes the getTokenId, depositFee and perTokenGasFees reads used for fees.
Constants
These are exported from the root entry only:
import {
DEFAULT_CHECKOUT_COMPLETE_PATH,
DEFAULT_PAYMENT_REQUEST_TTL_SECONDS,
MAX_PAYMENT_REQUEST_TTL_SECONDS,
} from "@0xcurvy/payments-sdk";
// "/checkout/complete"
// 600
// 86400RECEIVING_KEYS_VERSION ("01") is exported from the root entry, /merchant/keys and /merchant. CURVY_CHECKOUT_URL ("https://app.curvy.box/checkout") is exported from the root entry and /transport, and CURVY_NETWORKS from the root entry and /chain.
Types
The root entry exports these types: PaymentIntent, SignedPaymentIntent, PaymentRecipient, MerchantKeySet, PublishedSigner, PaymentNote, PaymentReceipt, PaymentReadClient, PaymentReceiptClient and PaymentPublicClient. The configuration and verification types (PaymentSDK, PaymentSDKConfig, BoundVerifyPaymentParameters, VerifyPaymentParameters, PaidWhen, PaymentVerification, VerifiedPayment, PaymentStatus, PaymentVerificationErrorCode, PaymentVerifyClient, PaymentRecord, RecipientParameters) come from /merchant. RecipientParameters is the receivingKeys-or-recipient choice shared by initialize, createPaymentRequest, buildPaymentRequest and createX402Merchant. ChainFees, FeeBreakdown and PaymentRail are exported from the root entry and /economics; CurvyEnvironment, CurvyNetwork, CurvyCurrency and CurvyDeployment from the root entry and /chain; the x402 types from /x402 and /x402/merchant.
Related packages
| Package | Role |
|---|---|
@0xcurvy/rs-core-wasm@0.1.0-rc.4 | Peer for /merchant and /x402/merchant note derivation and verification |
@x402/fetch, @x402/evm | Optional standard x402 client packages for paying agents; the merchant side needs none of them |
@0xcurvy/curvy-sdk | Wallet SDK. Do not use it for shop checkout. |
For agent payments (exact and curvy-transfer), see x402 and the Payments SDK. Everything x402 lives in @0xcurvy/payments-sdk/x402 and @0xcurvy/payments-sdk/x402/merchant; there is no separate x402 package to install.