transfer-factory method: the payer signs a TransferFactory_Transfer naming you as receiver, the facilitator relays it, and because you hold a standing TransferPreapproval it settles directly to your party in a single transaction. Canton Coin and registry tokens work identically — you pick the instrument via extra.instrumentId in your PaymentRequirements.
Before You Start
You need:- A Canton party ID (your receiver identity on Canton mainnet)
- A live
TransferPreapprovalfor Canton Coin with you as receiver (see Step 1) — this is what lets an incoming transfer settle in one transaction - A facilitator URL, party ID, and synchronizer ID (Step 2)
Step 1: Provision a TransferPreapproval
Before any agent can pay you, your party MUST hold a liveTransferPreapproval for the Canton Coin instrument (receiver = you). It is what lets an incoming TransferFactory_Transfer resolve direct — accepted automatically and settled in one transaction. Without it the transfer resolves to a two-step pending TransferInstruction, the facilitator cannot settle in one round-trip, and /settle is rejected with invalid_exact_canton_preapproval_missing (your callers get a 402 instead of the resource). A TransferPreapproval is time-bounded — renew it before expiry to keep the one-transaction path available.
Pick the path that matches how your party is hosted.
External-party merchant (relay-managed wallet)
Your party is an Ed25519 self-custody wallet managed through the relay. Provision the preapproval with the CLI — it self-provisions, no operator token needed:--admin is the DSO party id (get it from your Canton Scan API) and --days sets the validity before renewal. For a registry token, --admin is the registrar and --id the instrument id; the registrar must be trusted (CANTON_AGENT_REGISTRY_TRUSTED_PARTIES, with USDCx built in). One preapproval per instrument you want to receive.
Hosted-node merchant
If your party lives on your OWN Canton node/wallet, create theTransferPreapproval through that wallet/validator directly (receiver = your party) — no relay or CLI needed. There is no custom DAR to install: transfer-factory runs through the standard CIP-56 :TransferInstruction interface.
Step 2: Get Facilitator Parameters
CallGET /supported on your facilitator to get the values you need:
signers["canton:*"][0] is your feePayer, and extra.synchronizerId is your synchronizer. Get the DSO party id (for instrumentId.admin) from your Canton Scan API.
Step 3: Install
Step 4: Define PaymentRequirements
"10000000000" = 1 CC and "100000000" = 0.01 CC; a mismatch returns invalid_exact_canton_amount_mismatch.
Field notes:
feePayer: the facilitator party — the relayer that submits the payer-signed transfer and pays its traffic fee. Clients MUST NOT alter it.instrumentId: the token to charge. Canton Coin is{ admin: "<DSO-party>", id: "Amulet" }; a CIP-56 registry token (e.g. USDCx) is{ admin: "<registrar-party>", id: "<instrument-id>" }. To charge a registry token, set this and the matchingassetsymbol, and hold that token’sTransferPreapproval(Step 1).executeBeforeSeconds: relative deadline (seconds from request time) the client uses to compute the transfer’s absoluteexecuteBefore; after it, the signed transfer is no longer executable.memo(optional): a string the client stamps into the transfer’s metadata; the facilitator does not validate it.
These field names follow the x402-ENVELOPE convention.synchronizerIdmay be omitted; clients fall back to the value advertised byGET /supported.
Step 5: Add Middleware
Express
"METHOD /path" format, matched exactly against ${req.method} ${req.path}. Unregistered routes pass through ungated.
Next.js App Router
Step 6: Verify It Works
Call your gated endpoint without payment:amount, payTo, and feePayer values.
Security Notes
- Your
accepts[]is authoritative. The middleware ignores any amount the client claims. A client cannot request a 1 CC resource for 0.01 CC by tampering with the payment payload. - The proven payer is on-ledger. The facilitator binds the proven payer to the sender of the signed
TransferFactory_Transfer, not to anything the client puts in the payload, so a spoofedpayerfield cannot impersonate. - Settlement happens before your handler. If your handler throws after a successful
/settle, the payer already paid. Handle errors gracefully; do not retry settlement. - Keep
payTocorrect. CC goes to whateverpayTosays. If your party ID changes, updatepaymentReqAND provision aTransferPreapproval(Step 1) for the new party.