Skip to content

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 ​

ImportRuntimeContents
@0xcurvy/payments-sdkBrowser and NodeRe-exports browser-safe helpers, types and constants
@0xcurvy/payments-sdk/intentBrowser and NodeRequest parse / sign / verify
@0xcurvy/payments-sdk/transportBrowser and NodeFragment and URL helpers; CURVY_CHECKOUT_URL
@0xcurvy/payments-sdk/chainBrowser and NodeCurvy's built-in networks; receipt discovery hints; portal prediction (internal and unstable)
@0xcurvy/payments-sdk/contractsBrowser and NodeABIs
@0xcurvy/payments-sdk/economicsBrowser and NodeFee reads, quotes, minimum amounts
@0xcurvy/payments-sdk/x402Browser and Nodex402 wire types, broadcaster and facilitator clients, payer helper, EIP-712 types, parsers, header codec
@0xcurvy/payments-sdk/x402/merchantNode onlycreateX402Merchant: the merchant side of exact and curvy-transfer in one object
@0xcurvy/payments-sdk/merchant/keysBrowser and NodeWell-known signer document; receiving-keys value (encodeReceivingKeys, parseReceivingKeys); generateCheckoutSigningKey
@0xcurvy/payments-sdk/merchantNode onlyinitialize, createPaymentRequest, verifyPayment, serializePaymentRecord / parsePaymentRecord

Merchant (/merchant, Node only) ​

ts
import {
  initialize,
  createPaymentRequest,
  buildPaymentRequest,
  verifyPayment,
  PaymentVerificationError,
  serializePaymentRecord,
  parsePaymentRecord,
} from "@0xcurvy/payments-sdk/merchant";
ExportRole
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.
PaymentVerificationErrorThrown 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_VERSIONAlso re-exported from /merchant/keys and the root entry

initialize config fields:

FieldRequiredDefaultDescription
receivingKeysone 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.
recipientone 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.
environmentyes—"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.
networknothe 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.
tokensnoevery token Curvy takes on the networkThe 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.
merchantOriginyes—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.
confirmationsyes—Blocks, counting the shield block, before bound verifyPayment returns paid instead of confirming. It must be a positive safe integer.
paidWhenno"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.
ttlSecondsno600Request 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.
checkoutCompletePathno/checkout/completeAbsolute path on merchantOrigin where Curvy sends the customer after a shield: {merchantOrigin}{path}#txHash=…. It is always included in the EIP-712 payload.

Signatures:

ts
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.

ts
import {
  acceptedTokens,
  buildPaymentIntentTypedData,
  parsePaymentIntent,
  parseSignedPaymentIntent,
  paymentIntentTypes,
  signPaymentIntent,
  verifyPaymentIntent,
} from "@0xcurvy/payments-sdk/intent";
ExportRole
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 / parseSignedPaymentIntentCanonicalize untrusted input
acceptedTokens(intent)Every token the request takes: intent.tokens, or [intent.token] when it takes one
buildPaymentIntentTypedData / paymentIntentTypes / describedPaymentIntentTypes / multiTokenPaymentIntentTypesEIP-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 paymentId or recovery;
  • non-canonical decimals (leading zeros);
  • an amount outside [1, 2^256);
  • an ownerHash outside [1, r), where r is the BN254 scalar field;
  • ephemeralKeyX or ephemeralKeyY at or above the BN254 base field;
  • a viewTag outside uint16;
  • a chainId or expiry that is not a positive safe integer;
  • a tokens list that doesn't start with token, lists a token twice, or has fewer than 2 or more than 8 tokens.

An omitted checkoutCompletePath becomes /checkout/complete.

Transport (/transport) ​

ts
import {
  buildCheckoutCompleteUrl,
  buildCheckoutRetryUrl,
  buildCheckoutUrl,
  CURVY_CHECKOUT_URL,
  decodePaymentIntentFragment,
  encodePaymentIntentFragment,
} from "@0xcurvy/payments-sdk/transport";
ExportRole
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_URLhttps://app.curvy.box/checkout, Curvy’s hosted checkout page. Also exported from the root entry.
encodePaymentIntentFragment / decodePaymentIntentFragmentUnpadded 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) ​

ts
import {
  CURVY_NETWORKS,
  findNoteInReceipt,
  getCurvyNetwork,
  getDefaultCurvyNetwork,
} from "@0xcurvy/payments-sdk/chain";
ExportRole
CURVY_NETWORKSCurvy'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 ​

ts
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[];
}
environmentNetworkTokens
"mainnet"Arbitrum One (42161), real moneyUSDC (default), USDT
"testnet"Ethereum Sepolia (11155111), test moneyUSDC

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) ​

ts
import {
  buildMerchantKeySet,
  generateCheckoutSigningKey,
  parseMerchantKeySet,
} from "@0xcurvy/payments-sdk/merchant/keys";

const keySet = buildMerchantKeySet([{ address: signer.address, notAfter: "2027-01-01T00:00:00.000Z" }]);
ExportRole
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_VERSIONThe receiving-keys value (below)

The same key from the command line, on the backend:

sh
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 ​

ts
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:

  • VERSION is two lowercase hex digits. 01 is today's keys: S (secp256k1), V (BN254) and the BabyJubjub public key. 02 is reserved for a later protocol version that adds keys. 00 is never valid.
  • BODY is base64url (RFC 4648 §5, no padding) of the ASCII marker CRK, then S.x, S.y, V.x, V.y, BabyJubjub.x, BabyJubjub.y as 32-byte big-endian numbers, then a 4-byte checksum. A version 01 body is 199 bytes, and the whole value is 268 characters.
  • The checksum is the first 4 bytes of SHA-256 over the version digits, CRK and the 192 key bytes. It catches typos and cut-off copies, and it covers the version digits, so changing 01 by 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 ​

ts
import {
  feeBreakdown,
  minimumPaymentAmount,
  quotePayment,
  readChainFees,
} from "@0xcurvy/payments-sdk/economics";
ExportRole
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.
quotePaymentFeeBreakdown for a gross amount on a PaymentRail ("portal" or "direct")
feeBreakdownThe same breakdown from positional values (gross, depositFeeBps, portalDeployment, pendingNoteCommitment)
minimumPaymentAmountSmallest 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 ​

ts
import { createX402Merchant, createMemoryPaymentStore, toResponse } from "@0xcurvy/payments-sdk/x402/merchant";
ExportRole
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 / listPaymentsRead stored X402Payment records by payTo
X402Merchant.shield / confirmRetry 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?) / minimumPricePortal-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.minimumPortalUsdThe broadcaster's USD minimum per portal, when it reports one
X402Merchant.closeStop 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
ShieldRefusedErrorThrown by shield (and reported as an error event) when the broadcaster reaches a terminal state for the portal; retrying is pointless

createX402Merchant config fields:

FieldRequiredDefaultDescription
broadcasternohttps://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
facilitatorno<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
schemesno["exact", "curvy-transfer"], or ["curvy-transfer"] when facilitator is falseX402Scheme[]; exact needs a facilitator
rpcUrl or publicClientone—Chain access (X402MerchantClient is the subset of viem's PublicClient used)
receivingKeys or recipientone—Your public receiving keys: the one 01… value (CURVY_PAYMENTS_PUBLIC_KEY, preferred) or { S, V, babyJubjubPublicKey }. Passing both, or neither, throws
tokensnoevery token Curvy takes there (USDC and USDT on Arbitrum One, USDC on Sepolia); required on other chainsSymbols 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
tokenDomainsnobuilt in for Curvy's tokens, else read on chain{ [address]: { name, version } } for tokens without version()
otherNetworksnononeChain 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
addressesnobuilt into the SDK on Curvy's networks; elsewhere from the broadcaster's GET /portal/networks/:chainIdaggregator, portalFactory, vault. Each one you pass comes first. On any other chain, pass them in production
recoverynoNO_RECOVERY_ADDRESSPortal recovery address every payTo is derived with. The default can never reclaim funds
shieldDeadlineSecondsno86400How long the broadcaster keeps trying to shield a funded portal, and how long the SDK polls it
enforceBroadcasterMinimumnotrueRefuse 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
settleTimeoutMsno60000curvy-transfer: how long charge() waits for a presented transaction to be mined
transferConfirmationsno1curvy-transfer: blocks a transfer needs before the resource is served
confirmationsno12Blocks, counting the shield block, before confirm reports the payment confirmed (passed to verifyPayment)
challengeTtlSecondsno300Challenge and authorization lifetime
merchantOriginnorequest URL origin, else Host headerYour public origin; set it behind a proxy or when the framework passes a relative URL
storenoin-memoryX402PaymentStore
onEventno—challenged, settled, shielded, confirmed, failed, error
autoShieldnotrueShield and confirm in the background after settlement
confirmPollMs / confirmTimeoutMsno2000 / 600000Background polling cadence and confirmation give-up time
fetchnoglobal fetchUsed 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 ​

ts
import {
  createBroadcasterClient,
  createFacilitatorClient,
  createX402Payer,
  EXACT_SCHEME,
  PAYMENT_REQUIRED_HEADER,
  parsePaymentPayload,
  TRANSFER_SCHEME,
  x402Network,
} from "@0xcurvy/payments-sdk/x402";
ExportRole
X402_VERSION2, the x402 protocol version the SDK speaks
CURVY_BROADCASTER_URL / CURVY_FACILITATOR_URLhttps://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_ADDRESS0x…dEaD, the default portal recovery address nobody controls
PAYMENT_REQUIRED_HEADER, PAYMENT_SIGNATURE_HEADER, PAYMENT_RESPONSE_HEADERThe x402 v2 header names
x402Network / parseX402NetworkchainId ↔ CAIP-2 eip155:<chainId>
X402PaymentRequired, X402PaymentRequirements, X402PaymentPayload, X402ResourceInfo, X402VerifyResponse, X402SettleResponse, X402SupportedResponse, X402SupportedKind, Eip3009Authorization, ExactPaymentPayload, TransferPaymentPayload, CurvyDeploymentx402 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_FAILURESPortal 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, paymentResponseFromPayer building blocks. Types: X402Signer, X402TransferSender, Eip3009TypedData
parsePaymentRequired, parsePaymentRequirements, parseResourceInfo, parsePaymentPayload, parseExactPayload, parseTransferPayload, parseVerifyResponse, parseSettleResponse, parseSupportedResponse, parseCurvyDeploymentValidate untrusted JSON into the wire types
requirementsEqual, canonicalJsonCompare requirement rows regardless of key order and address casing
parseUint, parseAddress, parseHexLow-level parse helpers
transferWithAuthorizationTypes, eip3009Domain({ chainId, token, name, version })EIP-712 types and domain payers sign for exact
encodeBase64Json / decodeBase64JsonBrowser-safe codec for the PAYMENT-* headers

See x402 and the Payments SDK.

Contracts (/contracts) ​

ts
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:

ts
import {
  DEFAULT_CHECKOUT_COMPLETE_PATH,
  DEFAULT_PAYMENT_REQUEST_TTL_SECONDS,
  MAX_PAYMENT_REQUEST_TTL_SECONDS,
} from "@0xcurvy/payments-sdk";
// "/checkout/complete"
// 600
// 86400

RECEIVING_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.

PackageRole
@0xcurvy/rs-core-wasm@0.1.0-rc.4Peer for /merchant and /x402/merchant note derivation and verification
@x402/fetch, @x402/evmOptional standard x402 client packages for paying agents; the merchant side needs none of them
@0xcurvy/curvy-sdkWallet 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.