Route a purchase for authoritative payment verification, then fund approved refunds using the merchant’s accepted policy: reserve funds or bounded collection from its spendable wallet. The original payment remains final. Use the quickstart to set up the participants and run the flow.
Network and amounts#
Use Circle test USDC on Base Sepolia (chain ID 84532). Its token address is 0x036CbD53842c5426634e7929541eC2318f3dCF7e; verify it against Circle’s contract list. Obtain test USDC from Circle’s official faucet and test ETH for wallet gas. These assets have no financial value. Mainnet is unsupported.
API amounts are integer base units: 1 USDC is 1000000, and 0.02 USDC is 20000. Use the SDK’s parseTokenAmount and formatTokenAmount with six decimals. Inputs with excess precision are rejected instead of rounded. Confirm the program’s deployment_id and advertised funding_modes; its asset, execution contract and reviewer are fixed. Historical circle-usdc programs use reserve funding. Credentials and transaction journals cannot cross programs.
The historical circle-usdc V3 reserve is 0x6baBBCBCC6517E1b2C6872Ec01f323b98D91eC9d. For that deployment, use the published routing-usdc-trust.json to verify its code, token implementation, metadata and signing key. V4 programs require their separate deployment and routing-wallet-trust.json; never reuse the V3 trust file. See Contracts & trust for the complete authorization boundary.
Sandbox limits#
For testing, each program supports up to 20 purchases in total (shared across its account’s programs on the same deployment when successors are enabled), and each merchant wallet can have up to 32 open purchases per reserve onchain. Unpaid checkouts count too. These are sandbox limits, not production capacity.
Choose the funding mode#
- Direct wallet (
mode: 1) is for an identified merchant whose policy permits unsecured refund exposure. The merchant retains spendable proceeds, with no mandatory reserve or withholding. It must sign the bounded mandate and separately approve token allowance. Spending funds or revoking allowance can leave an approved refund unpaid. - Reserve (
mode: 0on V4; historical V3 policies omitmode) is for policies requiring more backing, including higher-risk merchants. Configure an upfront minimum, rolling withholding, or both. Available reserve pays approved refunds; agreed later routed payments can recover eligible debt. Partial backing is not a reimbursement guarantee.
Risk tier selects policy requirements; it is not collection authority or proof of KYB. Neither path accesses PayPal-held or other provider-held balances. Payment routing is separate from custody: direct mode forwards the full payment immediately, while reserve mode captures only the agreed amounts. Both preserve original payment finality and use the same buyer claim and designated-reviewer approval flow.
Purchase and refund flow#
Follow the quickstart for the complete flow and Integration flow for participant responsibilities. The sections below define the funding rules accepted before payment.
Funding semantics#
In reserve mode, both rates apply to gross integer base units and round up to one micro-USDC per payment, without fractional carry. Each payment funds the oldest eligible approved debt first, then rolling reserve, then merchant proceeds. Total capture cannot exceed the payment or the cumulative collection ceiling. Recovery transfers do not consume the reserve accumulation cap. Held reserve may also pay debt; funds are never allocated twice.
In payment_allocation, gross = merchant_net + captured. captured is withheld from this payment; recovered is all award payments during the transaction, including prior reserve, so it can exceed captured. At that transaction, reserve_after = reserve_before + captured - recovered. The snapshot’s reserve_balance is a later read, not necessarily that transaction’s closing balance. Refresh an older purchase after recovery.
The Routing OpenAPI defines every FundingPolicy field, unit and bound. In reserve mode, reserveCap limits held accumulation and collectionCap counts deposits and routed capture. In direct mode, collectionCap counts successful wallet collection. Neither counter resets after payouts, withdrawals or retries. upfront is the balance required at registration, not per-purchase collateral. Risk tier is provider input, not verified KYB or a guarantee.
Direct collection pays only the fixed beneficiary, which must differ from the merchant, and is capped by the unpaid award, remaining mandate cap, wallet balance and allowance. Set validUntil no more than 120 days ahead.
In reserve mode, only explicit deposits and payments deliberately routed through the enrolled contract can fund collection. Arbitrary external wallets, provider-managed balances and other merchant inflows are outside its authority.
Direct merchant-wallet mode#
The merchant keeps its payment proceeds spendable, with no prefunding or withholding. Approved refunds are collected from its wallet under a bounded signed funding mandate and separate USDC allowance. Insufficient funds or revoked allowance leave an unpaid obligation; collection is not guaranteed.
See API and SDK policy examples for setup and collection retries.
API and SDK policy examples#
These examples assume client 1.7.1 initialized with the access file, exact deployment trust file, durable journal and approved transaction budget described in its README. Use V4 policies only when that program advertises the corresponding mode. Amounts below are USDC base units. Choose one policy before enrollment; do not switch an enrolled merchant with open purchases.
import {routingHash} from './routing-client.mjs';
const common = {
registrar: client.info.registrar,
collectionCap: 10000000, purchaseLimit: 1000000,
exposureLimit: 2000000, decisionWindow: 3600,
recoveryWindow: 7200, basis: 'gross_round_up',
releaseRule: 'remaining_exposure',
validUntil: Math.floor(Date.now() / 1000) + 86400
};
const directTerms = {...common, mode: 1, riskTier: 'trusted',
upfront: 0, reserveCap: 0, rollingBps: 0, recoveryBps: 0};
const reserveTerms = {...common, mode: 0, riskTier: 'higher_risk',
upfront: 0, reserveCap: 2000000, rollingBps: 200,
recoveryBps: 10000};
const terms = directTerms; // Or reserveTerms, before enrollment.
const policy = {...terms, termsHash: routingHash(terms)};
const merchant = await client.merchant('merchant-1', merchantWallet, policy);
if (policy.mode === 1) {
await client.approveWalletFunding('allowance-1', merchant,
merchantWallet, 1000000); // Separate consent, capped at 1 USDC.
}The reserve example has zero upfront and 2% rolling capture; it is not fully collateralized. If you select a nonzero upfront, approve and deposit that amount before registering purchases. For a historical V3 program, omit mode and validUntil from its reserve policy and use its original trust file. Do not copy the V4 contract into an old program.
The equivalent terms-creation API is POST /v1/routing/programs/{program_id}/merchants, with a scoped bearer credential and stable Idempotency-Key. Its body is the following JavaScript object serialized as JSON:
const body = {
merchant_reference: 'merchant-1',
wallet: merchantWallet.address,
funding_policy: policy
};Creation alone grants no authority. The response contains the exact enrollment authorization; the merchant must sign it, submit the matching onchain enrollment, and confirm both through /merchants/{merchant_id}/enrollment. The SDK merchant helper performs these steps with the merchant signer. Keep private keys with their owner. See the Routing OpenAPI for complete schemas.
After the same purchase, buyer claim and designated-reviewer decision steps in either mode:
await client.refund('refund-attempt-1', purchase);
const result = await client.snapshot(purchase);
console.log({approved: result.approved, paid: result.paid,
outstanding: result.outstanding});Retry an uncertain first response with refund-attempt-1, not a new ID. After a confirmed completed attempt, check remaining debt and authority. An explicitly requested direct-wallet retry then calls await client.refund('refund-attempt-2', purchase) with a new ID. Available balance, allowance and unexpired deadlines still govern payment. For reserve recovery, a later eligible routed purchase follows the accepted withholding policy; a transfer into an unrelated external wallet does nothing.
Deadlines, withdrawal and exit#
Accepted purchases freeze their beneficiary, amount, policy version, terms and deadlines. Policy replacement is refused while purchases remain open. Programs support two merchants for a provider or one for a direct merchant; see Sandbox limits for purchase capacity.
Reserve withdrawals preserve remaining exposure. Stopping new purchases preserves already registered purchases. Purchase creation fixes the deadline upper bounds: claim deadline, then decisionWindow, then recoveryWindow. A timely claim preserves authority through the fixed decision deadline; it does not restart the clock. For V4, the first accepted reviewer decision’s expiry can shorten the collection deadline. Approved debt can be collected only through that accepted deadline and the mandate expiry. The contract method releaseExpired(merchant, purchaseId) releases expired authority and exposure but does not erase an approved unpaid amount. It requires a transaction; waiting alone does not close the purchase. An expired award does not revive on a later deposit.
Requests, signatures and retries#
Use https://sandbox.cleard.ai/v1/routing. Access & authentication describes account, service, wallet and reviewer permissions. API reference lists operations; Routing OpenAPI defines exact schemas and required scopes.
Writes use Content-Type: application/json. Program and wallet writes require an 8–100 character Idempotency-Key. After an uncertain response, retry the exact body and key. Access renewal revokes the previous credential; notification receivers deduplicate signed event IDs. Keep service credentials and private keys out of URLs, source files and logs.
Wallet personal-sign bytes are cleard.routing.v1.<kind>\n plus canonical JSON. Ed25519 record, decision and notification bytes use cleard.routing.v1.<kind>\0 plus canonical JSON. Canonical JSON sorts object keys recursively, preserves array order and supports finite JSON values. Reviewer private keys stay with the reviewer; only signatures reach the API. Decisions support positive approvals; rejection and review case management are not implemented.
recoverPurchase(merchant, purchaseId) is permissionless but can only collect an already-approved award for that purchase. It cannot authorize a new award or change the beneficiary.
Receipt accounting deduplicates by transaction hash and log index, including all recoveries within one payment. Out-of-order cumulative amounts require earlier receipts to be reconciled first. Changed canonical block hashes freeze the program for operator review. See Errors & retries for interrupted wallet submissions, durable journals and recovery steps.
Token failures and notifications#
An unknown token implementation or metadata change halts new execution. Circle pause or blacklist controls can prevent transfers. In reserve mode, reverted, false-return, missing-return and inexact transfers roll back the whole onchain action. Direct mode rolls back only its isolated transfer attempt and retains the award without consuming collection capacity; a failed refund is never recorded as paid. A blocked refund recipient can also halt a payment that would recover its debt. Read-only records remain available. Preflight checks cannot prevent a token upgrade between verification and mining.
Notifications go only to https://sandbox.cleard.ai/v1/routing/notification-receiver. Signed refund.collection_attempted events report collection attempts; refund.paid reports payment. Delivery is caller-driven and deduplicated by signed event ID. No external provider webhook or scheduler is enabled. See Refunds & recovery for dispatch and retry testing.
Continue an integration#
Use the same program, deployment trust and durable journal across browser and SDK activity. Access & authentication owns account discovery, renewal, successor limits and reviewer continuity. The client README covers explicit service-history reconciliation, offline reviewer signing and notification verification. See Errors & retries before retrying an uncertain or failed operation.