From df92331dd2b838fdc5d8f6cbe1bf847663873df4 Mon Sep 17 00:00:00 2001 From: Harshal Bhangale Date: Mon, 1 Jun 2026 12:44:37 -0400 Subject: [PATCH 1/3] Add usdc charge payment method --- specs/methods/usdc/draft-usdc-charge-00.md | 1483 ++++++++++++++++++++ 1 file changed, 1483 insertions(+) create mode 100644 specs/methods/usdc/draft-usdc-charge-00.md diff --git a/specs/methods/usdc/draft-usdc-charge-00.md b/specs/methods/usdc/draft-usdc-charge-00.md new file mode 100644 index 00000000..c3fe0734 --- /dev/null +++ b/specs/methods/usdc/draft-usdc-charge-00.md @@ -0,0 +1,1483 @@ +--- +title: '"usdc" Payment Method for HTTP Payment Authentication' +abbrev: '"usdc" Payment Method' +docname: draft-usdc-charge-00 +version: 00 +category: info +ipr: noModificationTrust200902 +submissiontype: IETF +consensus: true +author: + - name: Harshal Bhangale + ins: H. Bhangale + email: harshal.bhangale@circle.com + org: Circle Internet Group, Inc. + - name: Huawei Gu + ins: H. Gu + email: hgu@circle.com + org: Circle Internet Group, Inc. + - name: Bhushit Agarwal + ins: B. Agarwal + email: bhushit.agarwal@circle.com + org: Circle Internet Group, Inc. + +normative: + RFC2119: + RFC8174: + RFC8785: + RFC9110: + I-D.httpauth-payment: + title: "The 'Payment' HTTP Authentication Scheme" + target: https://datatracker.ietf.org/doc/draft-ietf-httpauth-payment/ + author: + - name: Jake Moxey + date: 2026-01 + I-D.payment-intent-charge: + title: "'charge' Intent for HTTP Payment Authentication" + target: https://datatracker.ietf.org/doc/draft-payment-intent-charge/ + author: + - name: Jake Moxey + - name: Brendan Ryan + - name: Tom Meagher + date: 2026 + I-D.evm-charge: + title: EVM Charge Intent for HTTP Payment Authentication + target: https://paymentauth.org/draft-evm-charge-00.html + I-D.solana-charge: + title: Solana Charge Intent for HTTP Payment Authentication + target: https://paymentauth.org/draft-solana-charge-00.html + SIP-005: + title: Stacks Blocks, Transactions, and Accounts + target: https://raw.githubusercontent.com/stacksgov/sips/main/sips/sip-005/sip-005-blocks-and-transactions.md + SIP-010: + title: Stacks Fungible Token Standard + target: https://raw.githubusercontent.com/stacksgov/sips/main/sips/sip-010/sip-010-fungible-token-standard.md + SIP-018: + title: Stacks Signed Structured Data + target: https://raw.githubusercontent.com/stacksgov/sips/main/sips/sip-018/sip-018-signed-structured-data.md + CIRCLE-GATEWAY-INFO: + title: Circle Gateway GET /v1/info + target: https://developers.circle.com/api-reference/gateway/all/get-gateway-info + CIRCLE-GATEWAY-ESTIMATE: + title: Circle Gateway POST /v1/estimate + target: https://developers.circle.com/api-reference/gateway/all/estimate-transfer + CIRCLE-GATEWAY-TRANSFER: + title: Circle Gateway POST /v1/transfer + target: https://developers.circle.com/api-reference/gateway/all/create-transfer-attestation + CIRCLE-GATEWAY-TRANSFER-STATUS: + title: Circle Gateway GET /v1/transfer/{id} + target: https://developers.circle.com/api-reference/gateway/all/get-transfer-by-id + CIRCLE-GATEWAY-TRANSFER-SPEC: + title: Circle Gateway GET /v1/transferSpec/{transferSpecHash} + target: https://developers.circle.com/api-reference/gateway/all/get-transfer-spec + +informative: + CIRCLE-GATEWAY-FORWARDING: + title: Circle Gateway Forwarding Service guide + target: https://developers.circle.com/gateway/howtos/forwarding-service + CIRCLE-USDC-ADDRESSES: + title: Circle USDC Contract Addresses + target: https://developers.circle.com/stablecoins/usdc-contract-addresses + CIRCLE-XRESERVE: + title: Circle xReserve architecture + target: https://developers.circle.com/xreserve +--- + +--- abstract + +This document defines the `usdc` payment method for the `charge` +intent in the Payment HTTP Authentication Scheme +{{I-D.httpauth-payment}}. It gives merchants one USDC acceptance surface +across supported chain families while leaving chain-specific signing +and broadcast mechanics in the relevant chain profile. + +This version covers direct USDC charges on EVM and Solana by +profiling the existing PaymentAuth EVM and Solana charge +specifications. The EVM profile is intentionally limited to EIP-3009 +authorization credentials in v00. It also defines a direct Stacks +USDCx profile because Stacks USDCx is backed by USDC through xReserve +and is not covered by a generic MPP chain method today. + +This version also defines a Gateway Transfer charge profile for +cross-chain USDC payments through Circle Gateway. The merchant chooses +the destination chain where it wants to receive USDC and advertises the +Gateway source chains it accepts from payers. + +--- middle + +# Introduction + +HTTP Payment Authentication {{I-D.httpauth-payment}} defines a +challenge-response mechanism that gates access to resources behind +payments. This document defines `method="usdc"` for settled +`intent="charge"` payments. + +The method is a USDC-specific acceptance surface, not a new generic +chain method. Direct EVM USDC is mechanically close to +`method="evm"` with USDC selected, and direct Solana USDC is close to +`method="solana"` with the USDC SPL token mint in `request.currency`. +The value of this method is the merchant-facing USDC abstraction and the +USDC-specific rules around native USDC issuance, supported asset forms, +third-party lookalike assets, token controls, and receipts. + +This version keeps the base direct charge path small. Gateway Transfer +is an optional cross-chain profile for merchants that want USDC on one +destination chain while accepting payer funds from any advertised +Gateway source chain. + +## Scope of This Version + +Normatively specified: + +- EVM direct USDC charges, by reference to {{I-D.evm-charge}}. +- Solana direct USDC charges, by reference to {{I-D.solana-charge}}. +- Stacks direct USDCx charges using SIP-010 transfers and SIP-018 + challenge binding. +- Gateway Transfer charges through Circle Gateway that settle on the + merchant's selected destination network before a successful receipt is + returned. +- USDC-specific asset identity, token-control, replay, and receipt + requirements. + +## Relationship to Other Methods + +This document does not replace the EVM or Solana charge methods. It +profiles them for USDC. + +For EVM, the request and credential envelope inherits from +{{I-D.evm-charge}}. The `usdc` profile restricts the token to native +USDC, restricts the v00 credential payload to EIP-3009 +`authorization`, and adds USDC-specific asset identity and control +checks. Deployments that want +Permit2, raw transaction, or hash-based EVM settlement SHOULD advertise +`method="evm"` directly. + +For Solana, the request and credential semantics inherit from +{{I-D.solana-charge}}. The `usdc` profile restricts the token to the +native USDC SPL mint published by Circle and the legacy SPL Token +program in v00. + +For Stacks, this document defines USDCx on Stacks directly because it +is not covered by a generic MPP chain method today. + +For Gateway Transfer, this document defines a Circle Gateway-specific +cross-chain charge profile. The merchant sets the destination network +where it wants to receive USDC. The payer chooses one of the advertised +source networks, signs a Gateway authorization, and the server submits +that authorization to Circle Gateway. A successful charge receipt means +Gateway has completed settlement on the merchant's destination network. + +# Requirements Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and +"OPTIONAL" in this document are to be interpreted as described in BCP +14 {{RFC2119}} {{RFC8174}} when, and only when, they appear in all +capitals. + +# Terminology + +**Native USDC** +: USDC natively issued by Circle on a supported network. Circle + publishes the contract and mint addresses for each supported + deployment. + +**USDCx on Stacks** +: The SIP-010 token issued by the partner-deployed Stacks USDCx + contract and backed 1:1 by USDC controlled through Circle xReserve on + supported source chains. USDCx is not native USDC, so this profile + treats it as a distinct asset form. + +**Direct Charge** +: A payment where the server returns a successful receipt only after + the underlying chain transaction has reached the server's local + confirmation threshold. + +**Gateway Transfer** +: A charge profile where Circle Gateway moves native USDC from a + payer-selected source network to the merchant's selected destination + network before a successful receipt is returned. + +# Method Identifier + +The payment method identifier is: + +```text +usdc +``` + +# Supported Intents + +This v00 document supports: + +```text +charge +``` + +# Intent: "charge" + +For `intent="charge"`, the `request` auth-param contains the fields +defined by the charge intent plus `usdc` method details. The request +JSON MUST be serialized with JSON Canonicalization Scheme {{RFC8785}} +before base64url encoding. + +## Shared Request Fields + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `amount` | string | REQUIRED | Positive integer amount in USDC base units. | +| `currency` | string | REQUIRED | Profile-specific USDC token identifier. Direct profiles use chain-native identifiers. Gateway Transfer uses the literal `usdc` identifier. | +| `recipient` | string | REQUIRED | Chain-native recipient identifier. | +| `description` | string | OPTIONAL | Human-readable payment description. | +| `externalId` | string | OPTIONAL | Merchant reference identifier. | + +For EVM, `currency` follows {{I-D.evm-charge}} and is the native USDC +token contract address published by Circle {{CIRCLE-USDC-ADDRESSES}}. + +For Solana, `currency` follows {{I-D.solana-charge}} and is the +native USDC mint address published by Circle {{CIRCLE-USDC-ADDRESSES}}. + +For Stacks, `currency` MUST be the full USDCx SIP-010 {{SIP-010}} +asset identifier, `.::`. +`methodDetails.stacks` carries the same identity as parsed fields for +transaction verification. + +For Gateway Transfer, `currency` MUST be the case-sensitive literal +`usdc`. This is a method-defined asset identifier, not an ISO currency +code. The concrete source and destination token identities are resolved +through Circle Gateway discovery and estimate APIs or a conforming SDK. + +## Method Details + +`methodDetails.profile` selects the active `usdc` profile. Exactly one of +`methodDetails.evm`, `methodDetails.solana`, `methodDetails.stacks`, or +`methodDetails.gateway` MUST be present, and it MUST match +`methodDetails.profile`. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `profile` | string | REQUIRED | One of `evm`, `solana`, `stacks`, or `gateway`. | +| `evm` | object | CONDITIONAL | EVM details. Required for EVM direct USDC charge. | +| `solana` | object | CONDITIONAL | Solana details. Required for Solana direct USDC charge. | +| `stacks` | object | CONDITIONAL | Stacks details. Required for Stacks USDCx charge. | +| `gateway` | object | CONDITIONAL | Circle Gateway Transfer details. Required for Gateway Transfer charge. | + +## EVM Profile {#evm-profile} + +The EVM profile inherits {{I-D.evm-charge}}. The following restrictions +apply: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `chainId` | number | REQUIRED | Decimal EVM chain identifier. | +| `decimals` | number | REQUIRED | MUST be `6`. | +| `credentialTypes` | array | OPTIONAL | If present, MUST contain only `authorization` in v00. If absent, `authorization` is implied. | + +Servers MUST verify that `request.currency` is the native USDC token +contract published by Circle for `methodDetails.evm.chainId`. EVM +credentials for this profile MUST use `payload.type="authorization"`. The +authorization nonce MUST bind the selected challenge so a signed +authorization cannot be replayed across payment challenges or intents. +Servers MUST verify the EIP-3009 signature against the token contract's +actual EIP-712 domain. For v00, the token domain MUST match native USDC +for the selected chain and contract, including `chainId = +methodDetails.evm.chainId` and `verifyingContract = request.currency`. +Implementations MAY discover the domain through `eip712Domain()` where +available, `DOMAIN_SEPARATOR`, or a trusted native-USDC registry. +Deployments that require an alternate EIP-712 domain shape, including +salt-based domains, are out of scope for this profile. Because the +client signs an offchain EIP-3009 authorization, the server submits the +transaction and pays EVM gas. + +## Solana Profile {#solana-profile} + +The Solana profile inherits {{I-D.solana-charge}}. The following +restrictions apply: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `network` | string | REQUIRED | `mainnet`, `devnet`, or `localnet`. | +| `decimals` | number | REQUIRED | MUST be `6`. | +| `tokenProgram` | string | REQUIRED | MUST be the legacy SPL Token program ID in v00. | +| `feePayer` | boolean | OPTIONAL | Whether the server pays network fees. | +| `feePayerKey` | string | CONDITIONAL | Required when `feePayer=true`; absent otherwise. | + +v00 profiles native USDC on the legacy SPL Token program only. Servers +MUST reject credentials whose `tokenProgram` is not: + +```text +TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA +``` + +The Token-2022 program is out of scope in v00. The field remains in +the schema so a later revision can admit Token-2022 USDC without +changing the wire shape. + +Servers SHOULD verify that `tokenProgram` equals the owner program of +the mint account returned by Solana RPC. A mismatch MUST cause +credential rejection. + +Solana credentials for this profile MUST use `payload.type="transaction"`. +Unlike the base Solana charge profile, this profile also requires a +detached challenge-binding signature. The transaction signature alone +prevents network replay, but it does not prove that the transaction was +created for this exact `usdc` challenge. + +## Stacks Profile {#stacks-profile} + +Stacks uses SIP-005 {{SIP-005}} consensus-serialized transactions and +SIP-018 {{SIP-018}} challenge binding. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `network` | string | REQUIRED | `mainnet` or `testnet`. | +| `chainId` | string | REQUIRED | Decimal Stacks chain id, `1` for mainnet or `2147483648` for testnet. | +| `contractAddress` | string | REQUIRED | Stacks standard principal for the USDCx SIP-010 token contract. | +| `contractName` | string | REQUIRED | Contract name component of the SIP-010 token contract. | +| `assetName` | string | REQUIRED | Fungible-asset identifier inside the SIP-010 contract. | +| `functionName` | string | REQUIRED | MUST be `transfer`. | +| `decimals` | number | REQUIRED | MUST be `6`. | +| `feePayer` | boolean | OPTIONAL | Whether the server sponsors fees using Stacks sponsored transaction authorization. | +| `feePayerAddress` | string | CONDITIONAL | Required when `feePayer=true`; absent otherwise. | + +The Stacks USDCx mainnet token identity at publication time is: + +```text +SP120SBRBQJ00MCWS7TM5R8WJNTTKD5K0HFRC2CNE.usdcx::usdcx-token +``` + +The Stacks USDCx testnet token identity used by the examples is: + +```text +ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usdcx::usdcx-token +``` + +Servers MUST verify the advertised token tuple against a +Circle-recognized USDCx registry or an explicit implementation +allowlist. Until a public registry is available, a v00 allowlist entry +MUST include `network`, `chainId`, `contractAddress`, `contractName`, +`assetName`, `decimals`, and the xReserve control surface used for +issuance and redemption checks. A token identifier that appears only in +a partner-published registry MUST NOT be accepted if it contradicts the +Circle-recognized registry or allowlist. The parsed +`methodDetails.stacks` tuple MUST match `request.currency`. + +## Gateway Transfer Profile {#gateway-transfer-profile} + +The Gateway Transfer profile uses Circle Gateway to move native USDC +from a payer-selected Gateway source network to the merchant's chosen +destination network. The merchant receives USDC on `destinationNetwork`, +so the payer's source-network choice is constrained by +`acceptedSources` rather than by the merchant's settlement network. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `acceptedSources` | array | REQUIRED | CAIP-2 Gateway source networks the payer may use for this charge. | +| `destinationNetwork` | string | REQUIRED | CAIP-2 destination network where the merchant wants to receive USDC. | +| `maxFee` | string | REQUIRED | Absolute upper bound on the signed Gateway authorization fee cap, in USDC base units. | +| `maxFeeBps` | number | OPTIONAL | Additional ratio cap. When present, `authorizationFeeCap * 10000 <= request.amount * maxFeeBps`. | +| `credentialTypes` | array | OPTIONAL | If present, MUST contain only `gateway-transfer` in v00. If absent, `gateway-transfer` is implied. | + +Circle Gateway maps source and destination networks to Gateway domains, +token identifiers, wallet contracts, minter contracts, recipient setup +options, and signing bytes. Those details are resolved by Circle +Gateway APIs or a conforming SDK. They are not carried in +`methodDetails`. + +v00 allows any Circle Gateway-supported source and destination pair, +including EVM to Solana, Solana to EVM, and same-family transfers. +Servers MUST reject credentials for a route that Circle Gateway does +not support at authorization time. + +For v00, a Gateway Transfer credential selects one source network from +`acceptedSources`. `acceptedSources` is the merchant's accepted set; +`payload.sourceNetwork` is the payer's selected source for this +charge. + +For Solana destinations, `request.recipient` identifies the merchant's +Solana owner address. The signed Gateway authorization settles to a +USDC token account for that owner. Associated token account creation and +recipient setup options are Gateway authorization fields, not +PaymentAuth `methodDetails`. + +`maxFee` is separate from `amount`. It is a cap, not the fee paid. +The payer pays Gateway Transfer fees from the source depositor's +Gateway balance, in addition to the merchant amount. The server MUST +reject a Gateway authorization whose fee cap exceeds +`methodDetails.gateway.maxFee`. + +If `maxFeeBps` is present, the signed Gateway authorization fee cap +MUST also satisfy the ratio cap: +`authorizationFeeCap * 10000 <= request.amount * maxFeeBps`. When +both caps are present, the signed fee cap MUST satisfy both. + +The Gateway TransferSpec value MUST equal `request.amount`. Circle +Gateway validates that the source depositor has enough Gateway balance +to cover the TransferSpec value plus the fee charged for the accepted +transfer. Clients SHOULD present `amount + maxFee` as the payer's +worst-case spend. + +Servers verify the Gateway authorization against the PaymentAuth +request before submission. + +This draft relies on Circle Gateway for route discovery +{{CIRCLE-GATEWAY-INFO}}, fee estimation {{CIRCLE-GATEWAY-ESTIMATE}}, +signing material and submission {{CIRCLE-GATEWAY-TRANSFER}}, transfer +status {{CIRCLE-GATEWAY-TRANSFER-STATUS}}, and TransferSpec lookup +{{CIRCLE-GATEWAY-TRANSFER-SPEC}}. It does not copy Gateway attestation +bytes, contract ABI details, or SDK routing tables into the PaymentAuth +request. Circle Gateway validates Gateway encoding, signatures, Gateway +replay, route support, fee calculation, and transfer validity. + +### Gateway Transfer Salt Binding {#gateway-transfer-salt-binding} + +Each Gateway TransferSpec used for this profile MUST carry a +challenge-bound `salt`. In v00, the salt binding is: + +```text +keccak256(UTF-8 bytes of JCS({ + "id": , + "method": "usdc", + "realm": , + "intent": "charge", + "profile": "gateway", + "requestHash": , + "sourceNetwork": , + "destinationNetwork": , + "sourceDepositor": , + "sourceSigner": , + "recipient": , + "destinationRecipient": , + "amount": , + "maxFee": +})) +``` + +`requestHash` is `keccak256` of the UTF-8 bytes of the exact +JCS-canonicalized request JSON before base64url encoding. The server +MUST recompute this binding before treating the Gateway authorization +as valid for the selected challenge. + +All account values in the salt preimage use PaymentAuth-normalized +strings, not raw Gateway ABI byte strings. `sourceDepositor` is the +exact `credential.source` CAIP-10 account after normal CAIP-10 +validation. `sourceSigner` is the Gateway account whose signature +authorizes the transfer. It MAY equal `sourceDepositor` or be an +account that Circle Gateway accepts as an authorized delegate for that +depositor. +`destinationRecipient` is the chain account that receives the settled +funds, encoded as CAIP-10 on `destinationNetwork`. For EVM networks, +address comparisons and CAIP-10 account strings use the lowercase +20-byte `0x` address. For Solana networks, account strings use the +base58 public key. + +If the Gateway authorization carries recipient setup options, the +PaymentAuth verifier MUST confirm that those options are bound to +`request.recipient` before submitting the authorization. For Solana +destinations, this means any associated-token-account setup must use +`request.recipient` as the token account owner. + +The PaymentAuth verifier MUST inspect the Gateway authorization +material before submitting it to Circle Gateway. The verifier MUST be +able to read, either directly or through a conforming SDK, the signed +Gateway fields needed by {{verification-procedure}}. It MUST submit the +same signed authorization package it inspected. Because `salt` is part of the +signed Gateway TransferSpec, changing the salt changes the Gateway +authorization and causes Circle Gateway validation to fail. The client +does not supply `transferSpecHash` as a credential field; Circle +Gateway returns it later as receipt evidence for the transfer item that +was accepted. + +# Credential Schema + +EVM credentials inherit {{I-D.evm-charge}} authorization payloads, with +the nonce derivation below. Solana credentials inherit +{{I-D.solana-charge}} pull-mode transaction payloads and add the required +challenge-binding object defined in this section. Stacks and Gateway +Transfer define their profile-specific payloads below. + +For EVM authorization credentials, the EIP-3009 fields are carried +directly in `payload` as defined by {{I-D.evm-charge}}. `payload.nonce` +MUST be a `0x`-prefixed lowercase zero-padded 66-character hex string +representing exactly 32 bytes. The nonce MUST equal: + +```text +keccak256(UTF-8 bytes of JCS({ + "id": , + "method": "usdc", + "realm": , + "intent": "charge", + "requestHash": +})) +``` + +`requestHash` is `keccak256` of the UTF-8 bytes of the exact +JCS-canonicalized request JSON before base64url encoding. + +This is stricter than the base EVM charge profile. The base EVM profile +binds EIP-3009 authorization nonces to `challenge.id` and +`challenge.realm`. The `usdc` profile also binds the method, intent, and +request hash, so an authorization cannot move between `evm`, `usdc`, +`charge`, another intent, or a different request with the same amount and +recipient. + +For Solana transaction credentials, the credential object contains: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `challenge` | object | REQUIRED | Echo of the server challenge. | +| `payload` | object | REQUIRED | Solana payment payload. | +| `source` | string | REQUIRED | CAIP-10 account ID using `solana::`. | + +The Solana `payload.type` MUST be `transaction`. The payload carries: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `type` | string | REQUIRED | MUST be `transaction`. | +| `transaction` | string | REQUIRED | Base64-encoded serialized Solana transaction. | +| `challengeBinding` | object | REQUIRED | Detached challenge-binding signature. | + +The `challengeBinding` object contains: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `type` | string | REQUIRED | MUST be `challenge-binding-v00`. | +| `signature` | string | REQUIRED | Base64-encoded 64-byte Ed25519 signature. | + +The client MUST sign the following JCS-canonicalized binding message: + +```text +{ + "type": "challenge-binding-v00", + "method": "usdc", + "challenge": , + "source": , + "transactionBytesDigest": +} +``` + +The signing input is: + +```text +SHA-256("CIRCLE::USDC::ChallengeBindingV00") || +UTF-8(JCS(binding_message)) +``` + +The signature MUST verify under the same Ed25519 public key that +authorizes the Solana token transfer. `transactionBytesDigest` is used +only for challenge binding. It is not the Solana transaction signature. + +For Stacks, the credential object contains: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `challenge` | object | REQUIRED | Echo of the server challenge. | +| `payload` | object | REQUIRED | Payment payload. | +| `source` | string | REQUIRED | CAIP-10 account ID using `stacks::`. | + +The Stacks `source` principal MUST be a c32check-encoded standard +principal. Contract principals MAY appear as `request.recipient`, but +MUST NOT appear as `source`. + +The Stacks `payload.type` MUST be `transaction`. The payload carries: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `type` | string | REQUIRED | MUST be `transaction`. | +| `transaction` | string | REQUIRED | Base64-encoded Stacks consensus-serialized transaction. | +| `challengeBinding` | object | REQUIRED | SIP-018 challenge-binding signature. | +| `transactionFormat` | string | OPTIONAL | MUST be `stacks_transaction_v1` when present. | +| `signatures` | array | OPTIONAL | MUST be absent for Stacks. | + +The Stacks transaction MUST call the SIP-010 `transfer` function with +`amount`, `sender`, `recipient`, and optional memo arguments. It MUST +include a post-condition that pins a `SentEq` transfer of +`request.amount` for the advertised USDCx asset. + +`payload.challengeBinding.type` MUST be `challenge-binding-v00`. +`payload.challengeBinding.signature` MUST be a base64-encoded 65-byte +recoverable secp256k1 signature in `r || s || v` order, with `v` in +`{0x00, 0x01}`. This document defines the SIP-018 domain tuple for the +challenge-binding message. The `USDCPaymentAuth` name is a +draft-specific domain label, not a token name or a Circle registry +identifier. + +```text +domain = (tuple + (name "USDCPaymentAuth") + (version "usdc-charge-v00") + (chain-id uCHAIN_ID)) + +message: + binding = JCS-canonicalized challenge binding message +``` + +`CHAIN_ID` is `methodDetails.stacks.chainId` parsed as a base-10 integer +and encoded as a SIP-018 Clarity `uint`. It MUST NOT be encoded as a +`string-ascii` or `string-utf8` value. + +The challenge binding message is the following JCS-canonicalized JSON +object: + +```text +{ + "type": "challenge-binding-v00", + "method": "usdc", + "challenge": , + "source": "", + "transactionBytesDigest": "" +} +``` + +The recovered public key MUST derive to the principal in `source`. + +When present, `transactionFormat` MUST be +`stacks_transaction_v1`. `signatures` MUST be absent for Stacks +because origin and sponsor signatures are embedded in the transaction +auth field. + +## Gateway Transfer Credential + +For Gateway Transfer, the credential object contains: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `challenge` | object | REQUIRED | Echo of the server challenge. | +| `payload` | object | REQUIRED | Gateway Transfer payload. | +| `source` | string | REQUIRED | CAIP-10 source depositor account ID. | + +The Gateway Transfer `payload.type` MUST be `gateway-transfer`. The +payload carries the selected source for this charge. It does not repeat +the full `acceptedSources` list from the request. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `type` | string | REQUIRED | MUST be `gateway-transfer`. | +| `sourceNetwork` | string | REQUIRED | Selected CAIP-2 source network. MUST be one entry from `methodDetails.gateway.acceptedSources`. | +| `destinationNetwork` | string | REQUIRED | CAIP-2 destination network. | +| `maxFee` | string | REQUIRED | Maximum Gateway fee authorized by the payer, in USDC base units. | +| `authorization` | object | REQUIRED | Circle Gateway signed authorization package, or SDK-produced versioned object, for the selected source network. It MUST expose the signed fields required by {{verification-procedure}} to the verifier. | + +The Gateway authorization package represents one Gateway transfer for +the selected source network. Its internal encoding is Circle Gateway +versioned data, not a PaymentAuth extension point. When this document +names Gateway fields such as source depositor, source signer, +TransferSpec value, fee cap, destination recipient, recipient setup +options, or salt, it refers to their Gateway semantics. The wire object +MAY carry those values under versioned Gateway field names. + +The surrounding PaymentAuth credential binds that Gateway package to +`credential.challenge.id` and `credential.challenge.realm` by requiring +the signed TransferSpec salt to equal the challenge-bound salt defined in +{{gateway-transfer-salt-binding}}. `sourceSigner` is the account that +signs the Gateway authorization. It can be the same account as +`sourceDepositor`, or an account Circle Gateway accepts as an authorized +delegate for that depositor. If `sourceSigner` is a contract account, +Circle Gateway also applies its contract-signature validation rules. + +# Verification Procedure {#verification-procedure} + +All profiles MUST perform these common checks before chain-specific +verification: + +1. Decode and JCS-verify the request and credential envelopes. +2. Verify `method="usdc"` and `intent="charge"`. +3. Verify `credential.challenge` matches the selected challenge. +4. Verify the challenge has not expired. +5. Verify `methodDetails.profile` is present and exactly one matching + profile object is present. +6. Verify `amount` is a positive integer in base units. +7. Verify the selected token is a supported USDC asset form for the + selected profile. +8. Verify the payer and recipient are not present in the applicable + blocklist. +9. Verify the token contract or mint is not paused where that control + exists. +10. Verify replay protection for the selected credential type. + +For Stacks USDCx, the applicable controls include the +Circle-recognized xReserve controls and any partner-chain token +controls required by local policy. + +For EVM, servers then apply {{I-D.evm-charge}} verification with the +USDC restrictions in {{evm-profile}}. Servers MUST verify +`methodDetails.profile = "evm"`. For `payload.type="authorization"`, +the EIP-3009 fields are carried directly in `payload` as defined by +{{I-D.evm-charge}}. The server MUST verify `payload.to` and +`request.recipient` identify the same 20-byte EVM address, +`payload.value = request.amount`, and `payload.nonce` equals the +challenge-bound nonce derivation before it submits the authorization. EVM +address equality is byte equality after hexadecimal decoding, not +case-sensitive string equality. + +For Solana, servers then apply {{I-D.solana-charge}} verification with +the USDC restrictions in {{solana-profile}}. Servers MUST verify +`methodDetails.profile = "solana"`. Servers MUST also verify that +`payload.challengeBinding.type = "challenge-binding-v00"` and that +`payload.challengeBinding.signature` authenticates the challenge-binding +binding message under the same source key that authorizes the token +transfer. If `methodDetails.solana.feePayer=true`, the transaction MUST +set `methodDetails.solana.feePayerKey` as fee payer and the only +missing required signature MUST be the server fee-payer signature. The +server MUST reject any +Solana credential whose transaction bytes or challenge-binding +signature have already been consumed. The server MUST reject stale +transactions by verifying that the transaction uses a currently valid +recent blockhash, or a durable nonce whose `(nonceAccount, nonceValue)` +has not already been admitted by this deployment. + +USDC Solana verification is intentionally narrower than the generic +Solana transaction profile. Servers MUST reject transactions with +instructions outside the allowed set for this profile: SPL Token +transfer instructions for the advertised mint, associated-token-account +setup for the advertised recipient and mint when needed, bounded +Compute Budget instructions, optional Memo instructions, and durable +nonce advancement when a durable nonce is used. Token-2022, delegate +authority, and multisig authority flows are out of scope in v00. +Servers MUST verify that the source token account is owned by the +source account, that the recipient token account is the associated +token account for `request.recipient` and `request.currency` unless an +equivalent explicit token account is allowed by local policy, and that +the token transfer amount equals `request.amount`. An equivalent +explicit token account MUST be initialized for `request.currency` and +owned by `request.recipient`. + +For durable nonce transactions, servers MUST verify that the +transaction contains the expected `AdvanceNonceAccount` instruction, +that the nonce account is accepted by local policy, that the nonce +authority signed, and that the stored nonce value matches the +transaction's recent blockhash. When `feePayer=true`, the server MUST +simulate the final transaction after adding the fee-payer signature and +MUST reject transactions whose compute units, account writes, or fee +exposure exceed local policy. + +For Stacks, servers MUST verify: + +1. `payload.type = "transaction"`. +2. `methodDetails.profile = "stacks"`. +3. `source` uses `stacks::` and the + chain id matches `methodDetails.stacks.chainId`. +4. `methodDetails.stacks.decimals = 6`. +5. `request.currency` equals + `.::` using the parsed + values in `methodDetails.stacks`. +6. `(contractAddress, contractName, assetName)` matches a + Circle-recognized USDCx SIP-010 token for the selected chain. +7. `payload.transaction` decodes as a SIP-005 consensus-serialized + transaction. +8. The transaction version byte matches `methodDetails.stacks.network` + and the transaction chain id matches `methodDetails.stacks.chainId`. +9. `anchor_mode = OnChainOnly`. +10. `auth` matches `feePayer`: `Sponsored` with origin signed and + sponsor slot empty when `feePayer=true`; otherwise `Standard` with a + single origin signature. +11. The origin auth signature verifies, uses a low-s secp256k1 + signature, and recovers to a public key whose principal equals + `source`. +12. When `feePayer=true`, the sponsor principal equals + `methodDetails.stacks.feePayerAddress`; the server MUST co-sign + only after fee estimation satisfies local fee policy. +13. The payload is a `ContractCall` to `contractAddress.contractName` + with `function_name = "transfer"` and exactly the Clarity + arguments `(uint amount, principal sender, principal recipient, + (optional (buff 34)) memo)`. +14. The sender argument equals the principal in `source`. +15. The recipient argument equals `request.recipient`. +16. The amount argument equals `request.amount`. +17. `post_condition_mode = Deny`. +18. The transaction has exactly one `FungiblePostCondition` with + `principal = source-principal`, `asset_info = (contractAddress, + contractName, assetName)`, `condition_code = SentEq`, and + `amount = request.amount`. +19. The SIP-018 challenge-binding signature recovers to the principal + in `source`. +20. The origin auth nonce is fresh under the server's local Stacks + nonce policy. +21. The transaction reaches the server's local Stacks confirmation + threshold. + +For Gateway Transfer, servers MUST verify: + +1. `payload.type = "gateway-transfer"`. +2. `methodDetails.profile = "gateway"`. +3. `request.currency = "usdc"`. +4. `credential.source` is a CAIP-10 account for + `payload.sourceNetwork` and identifies the source depositor. +5. `payload.sourceNetwork` is included in + `methodDetails.gateway.acceptedSources`. +6. `payload.destinationNetwork = + methodDetails.gateway.destinationNetwork`. +7. `payload.maxFee <= methodDetails.gateway.maxFee`, with both values + parsed as unsigned decimal base-unit integers and compared + numerically. +8. Circle Gateway supports the selected source network and the + destination network for native USDC settlement. +9. The Gateway authorization package exposes one signed Gateway + transfer item to the verifier, either directly or through a + conforming SDK. +10. The server inspects the Gateway transfer item before submission and + submits the same signed package to Circle Gateway. +11. The Gateway TransferSpec inside the transfer item matches the + request recipient model, source depositor, source signer, + `payload.sourceNetwork`, `payload.destinationNetwork`, and a + Circle-supported route. For EVM destinations, the TransferSpec + destination recipient MUST equal `request.recipient`. For Solana + destinations, the TransferSpec destination recipient MUST be the + USDC token account for the owner in `request.recipient`. When + Gateway recipient setup options are present, they MUST encode + `request.recipient` as the recipient owner. +12. The Gateway TransferSpec salt matches the challenge-bound salt + defined in {{gateway-transfer-salt-binding}}. +13. The Gateway TransferSpec value equals `request.amount`. +14. The signed Gateway authorization fee cap equals `payload.maxFee`, + does not exceed `methodDetails.gateway.maxFee`, and, when + `methodDetails.gateway.maxFeeBps` is present, satisfies the + `maxFeeBps` ratio cap defined in {{gateway-transfer-profile}}. +15. PaymentAuth replay protection has not already consumed the + selected challenge or the challenge-bound Gateway salt for this + source depositor. +16. Circle Gateway accepts the signed authorization. Gateway validation + covers the Gateway signature, source signer authorization, route + support, source balance, Gateway replay, TransferSpec encoding, + TransferSpec hash, and transfer validity. +17. Circle Gateway reports destination settlement before the server + returns success. + +# Settlement Procedure + +For `intent="charge"`, settlement is complete only after the +underlying chain transaction has reached the server's local +confirmation threshold. + +For Gateway Transfer, settlement is complete only after Circle Gateway +reports destination settlement and exposes the destination transaction +hash, signature, transaction id, or equivalent final settlement +reference. After PaymentAuth verification, the server submits the +Gateway authorization through Circle Gateway or a conforming SDK. A +server MAY use Gateway forwarding {{CIRCLE-GATEWAY-FORWARDING}} to +complete the destination settlement, but it MUST NOT return a successful +charge receipt while the Gateway transfer is only estimated, submitted, +attested, pending, or confirmed but not finalized. + +## Gateway Transfer Non-Success Outcomes + +If Circle Gateway reports `failed` or `expired` before destination +settlement, the server MUST return a new `402` challenge. The client +MUST treat the original Gateway authorization as no longer reusable and +sign a new credential if it wants to retry the payment. + +If a Gateway transfer remains `pending` past the server's payment +deadline, the server SHOULD return `402` with `Retry-After` {{RFC9110}} +and a status reference. The client SHOULD NOT sign a replacement +authorization for the same resource until the original transfer reaches +a terminal status or the server reports that it is safe to retry. + +If the server loses transport state while submitting a Gateway +authorization, it MUST reconcile with Circle Gateway using any available +transfer or TransferSpec reference before requesting a replacement +authorization. This profile does not define repeated submission of the +same signed Gateway authorization as an idempotent client retry. + +The server MUST NOT return a successful receipt before the selected +profile's settlement has completed. If the server wants admission before +onchain settlement, it MUST use a separate deferred-settlement method or +intent outside this document. + +# Receipt Schema + +The receipt MUST identify the selected challenge, network, token, +payer, recipient, amount, and final transaction reference. + +Upon successful settlement, servers MUST return a `Payment-Receipt` +header per {{I-D.httpauth-payment}}. The decoded receipt payload +contains: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `method` | string | REQUIRED | MUST be `usdc`. | +| `intent` | string | REQUIRED | MUST be `charge`. | +| `challengeId` | string | REQUIRED | Original challenge ID. | +| `reference` | string | REQUIRED | Transaction hash, signature, or transaction ID. | +| `referenceType` | string | REQUIRED | `tx_hash`, `tx_signature`, or `tx_id`. | +| `status` | string | REQUIRED | `success` only after the selected profile has completed settlement. | +| `timestamp` | string | REQUIRED | RFC3339 settlement time. | +| `source` | string | REQUIRED | Payer account or address. | +| `recipient` | string | REQUIRED | Recipient account or address. | +| `amount` | string | REQUIRED | Amount in base units. | +| `currency` | string | REQUIRED | Same token identifier used in the request. | +| `settlement` | object | REQUIRED | Chain-local settlement reference. | +| `externalId` | string | OPTIONAL | Echo of `request.externalId`. | + +The `settlement` object MUST include: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `profile` | string | REQUIRED | `direct` or `gateway-transfer`. | +| `state` | string | REQUIRED | MUST be `completed`. | +| `network` | string | REQUIRED | Settlement network identifier. | +| `assetForm` | string | REQUIRED | `native` or `xreserve_backed`. | + +For EVM, `referenceType` SHOULD be `tx_hash`. + +For Solana, `referenceType` SHOULD be `tx_signature`. + +For Stacks, `referenceType` SHOULD be `tx_id`. + +For Gateway Transfer, `settlement.profile` MUST be +`gateway-transfer`, `settlement.network` MUST be the destination +network, and `referenceType` MUST identify the final destination +settlement reference exposed by Circle Gateway. EVM destinations +normally use `tx_hash`; Solana destinations normally use +`tx_signature`. The `settlement` object MUST include a `gateway` +object with the source network actually used for settlement. It does +not repeat the full `acceptedSources` list from the request. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `transferId` | string | REQUIRED | Circle Gateway transfer UUID. | +| `sourceNetwork` | string | REQUIRED | CAIP-2 source network used by the Gateway transfer. | +| `destinationNetwork` | string | REQUIRED | CAIP-2 destination network. | +| `transferSpecHash` | string | REQUIRED | Gateway-returned hash of the TransferSpec used by the transfer. | +| `destinationReference` | string | OPTIONAL | Destination settlement reference when available. | +| `destinationReferenceType` | string | OPTIONAL | Reference type for `destinationReference`. | +| `status` | string | REQUIRED | Raw Circle Gateway transfer status. MUST be `finalized` for a successful charge receipt. | +| `amount` | string | REQUIRED | Merchant amount in USDC base units. | +| `maxFee` | string | REQUIRED | Maximum Gateway fee authorized by the payer. | +| `fees` | object | OPTIONAL | Gateway fee summary returned by Circle Gateway. Fee summary amounts use Circle Gateway reporting units and are payer-paid. This field MUST be included when Circle Gateway returns a settled fee summary for the transfer. | + +The receipt `transferSpecHash` is evidence returned by Circle Gateway. +It is not supplied by the client credential. A verifier MAY use Circle +Gateway TransferSpec lookup {{CIRCLE-GATEWAY-TRANSFER-SPEC}} to inspect +the settled TransferSpec after acceptance or settlement. For Gateway +Transfer, the concrete token identity is verifiable from the Gateway +transfer item or TransferSpec lookup keyed by `transferSpecHash`; the +receipt does not repeat per-chain token addresses. + +# Security Considerations + +## Supported USDC Asset Forms + +This profile defines which USDC asset forms can satisfy `method="usdc"` +in v00. Servers MUST verify that the selected token matches a native +USDC deployment published by Circle, or, for Stacks, a Circle-recognized +USDCx deployment. Other bridged, wrapped, or synthetic USDC-like assets +need a separate profile or explicit method details. + +Gateway Wallet deposits remain subject to Circle Gateway recovery and +withdrawal rules. Gateway supports delayed trustless withdrawal from +Gateway Wallet when the service is unavailable. That recovery path is +outside PaymentAuth settlement and does not make a pending Gateway +Transfer idempotently retryable. + +## Blocklist and Pause Controls + +Native USDC and Stacks USDCx have different control surfaces. Native +USDC has token-level pause and blocklist controls on the selected +chain. For Stacks USDCx, xReserve {{CIRCLE-XRESERVE}} source-chain +controls gate deposits and withdrawals, but those controls do not freeze +same-chain partner token transfers by themselves. Same-chain USDCx +transfers depend on the partner-chain token controls. + +Servers MUST apply every control surface that is relevant to the +selected profile before accepting a credential. For native USDC, a +payer or recipient present in the applicable token blocklist MUST +cause rejection. For Stacks USDCx, servers MUST check the +Circle-recognized xReserve control surface and the partner-chain token +control surface required by local policy. Cached control data MUST +have an explicit freshness bound. + +## Replay Protection + +Servers MUST bind each credential to the selected challenge and MUST +maintain replay protection for consumed credentials. Challenge +consumption and receipt issuance MUST be atomic: concurrent requests +presenting the same valid credential MUST produce at most one +successful receipt. + +For EVM authorization credentials, the persistent replay key MUST +include `(chainId, verifyingContract, payload.from, payload.nonce)`. +The nonce already commits to `method`, `intent`, +`challenge.id`, `challenge.realm`, and `requestHash`, so this key +protects both onchain replay and cross-context replay. + +For Solana transaction credentials, the persistent replay key MUST +include `(network, transactionSignature)` after broadcast or +confirmation. Servers MUST also keep a replay key for the detached +challenge-binding material, `(network, transactionBytesDigest, +challengeBinding.signature)`, so the same signed transaction bytes +cannot be admitted twice before or around broadcast. For durable nonce +transactions, servers MUST also key replay protection on `(network, +nonceAccount, nonceValue)`. + +For Stacks transaction credentials, the replay key MUST include both +`(stacks:, transactionBytesDigest)` before or around +broadcast, the transaction id after broadcast, and +`(stacks:, , )`. + +For Gateway Transfer credentials, replay protection MUST cover the +selected challenge and the challenge-bound Gateway salt before the +server submits the authorization to Circle Gateway. A server that only +keys replay protection by source transaction or destination +transaction can submit the same signed Gateway authorization more than +once before final settlement. Circle Gateway separately enforces +Gateway-side replay when it accepts the transfer. The TransferSpec +salt binds the Gateway authorization to the PaymentAuth challenge, +request, source, destination, recipient, amount, and fee cap, as +specified in {{gateway-transfer-salt-binding}}. + +If a deployment advertises the same merchant order through both +`method="usdc"` and a chain-specific method such as `evm` or `solana`, +it MUST enforce one logical purchase across those offers. The +deduplication key MUST be a stable merchant order key, normally +`request.externalId` when present. If `externalId` is absent, the +deployment MUST maintain an explicit equivalent-offer group outside the +credential. This is merchant order deduplication, not a replacement for +challenge replay protection. A successful receipt for one offer MUST +atomically consume the shared key so that a later credential for the +same order is rejected. + +## Gateway Transfer Fees + +Gateway Transfer can charge fees in addition to the merchant amount. +The request binds a maximum fee in `methodDetails.gateway.maxFee`. +Servers MUST reject Gateway authorization material that exceeds that +fee cap. If `methodDetails.gateway.maxFeeBps` is present, servers MUST +also reject authorization material whose fee cap exceeds that ratio. +Clients SHOULD present the payer's worst-case spend as `amount + +maxFee`. + +Gateway Transfer fees are payer-paid in v00. The merchant server does +not sponsor those Gateway fees, even if it waits for destination +settlement before returning a successful `charge` receipt. + +## Sensitive Fields + +USDC credentials are bearer-equivalent between signing and settlement. +Servers SHOULD avoid logging raw authorization signatures, serialized +transactions, Gateway authorization packages, or full credential +headers. + +# IANA Considerations + +This specification registers the `usdc` payment method in the HTTP +Payment Methods Registry. + +| Field | Value | +| --- | --- | +| Method Identifier | `usdc` | +| Description | USDC payments across supported networks | +| Reference | This document | +| Contact | Harshal Bhangale, Circle Internet Group, Inc. | + +This document does not register `charge`; that intent is registered by +{{I-D.payment-intent-charge}}. This document states that `method="usdc"` +supports `intent="charge"` in v00. + +# Appendix A. Examples + +This appendix is informative. Field shapes are normative, example +values are not. Long signatures and chain transaction bytes are +shortened when the underlying chain format is already defined by the +referenced profile or SDK. + +### A.1 EVM Direct Charge + +The decoded request is: + +```json +{ + "amount": "1000000", + "currency": "0x3600000000000000000000000000000000000000", + "recipient": "0xc04193C50cD2E6a1C79593e46364496Fe5fcd9b6", + "description": "Arc Testnet USDC charge", + "externalId": "invoice-evm-001", + "methodDetails": { + "profile": "evm", + "evm": { + "chainId": 5042002, + "decimals": 6, + "credentialTypes": [ + "authorization" + ] + } + } +} +``` + +The server advertises the challenge: + +```http +HTTP/1.1 402 Payment Required +WWW-Authenticate: Payment realm="api.example.com", method="usdc", intent="charge", id="usdc_evm_direct_001", request="eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDM2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJkZXNjcmlwdGlvbiI6IkFyYyBUZXN0bmV0IFVTREMgY2hhcmdlIiwiZXh0ZXJuYWxJZCI6Imludm9pY2UtZXZtLTAwMSIsIm1ldGhvZERldGFpbHMiOnsiZXZtIjp7ImNoYWluSWQiOjUwNDIwMDIsImNyZWRlbnRpYWxUeXBlcyI6WyJhdXRob3JpemF0aW9uIl0sImRlY2ltYWxzIjo2fSwicHJvZmlsZSI6ImV2bSJ9LCJyZWNpcGllbnQiOiIweGMwNDE5M0M1MGNEMkU2YTFDNzk1OTNlNDYzNjQ0OTZGZTVmY2Q5YjYifQ", expires="2026-04-01T12:05:00Z" +``` + +The client returns an EIP-3009 authorization credential: + +```json +{ + "challenge": { + "id": "usdc_evm_direct_001", + "realm": "api.example.com", + "method": "usdc", + "intent": "charge", + "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDM2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJkZXNjcmlwdGlvbiI6IkFyYyBUZXN0bmV0IFVTREMgY2hhcmdlIiwiZXh0ZXJuYWxJZCI6Imludm9pY2UtZXZtLTAwMSIsIm1ldGhvZERldGFpbHMiOnsiZXZtIjp7ImNoYWluSWQiOjUwNDIwMDIsImNyZWRlbnRpYWxUeXBlcyI6WyJhdXRob3JpemF0aW9uIl0sImRlY2ltYWxzIjo2fSwicHJvZmlsZSI6ImV2bSJ9LCJyZWNpcGllbnQiOiIweGMwNDE5M0M1MGNEMkU2YTFDNzk1OTNlNDYzNjQ0OTZGZTVmY2Q5YjYifQ", + "expires": "2026-04-01T12:05:00Z" + }, + "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "payload": { + "type": "authorization", + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": "0xc04193c50cd2e6a1c79593e46364496fe5fcd9b6", + "value": "1000000", + "validAfter": "0", + "validBefore": "1775045100", + "nonce": "0x349f879fd77eb5a560c759b5d63e7f52cc04a82015b0cd5cce6d4b308fb36f3e", + "signature": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + } +} +``` + +The server submits the authorization, pays EVM gas, waits for the EVM +receipt, and returns: + +```http +HTTP/1.1 200 OK +Payment-Receipt: +``` + +The decoded receipt payload is: + +```json +{ + "method": "usdc", + "intent": "charge", + "challengeId": "usdc_evm_direct_001", + "reference": "0x3c5b4a1f00000000000000000000000000000000000000000000000000008d0a2c4e6b", + "referenceType": "tx_hash", + "status": "success", + "timestamp": "2026-04-01T12:00:04Z", + "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "recipient": "0xc04193C50cD2E6a1C79593e46364496Fe5fcd9b6", + "amount": "1000000", + "currency": "0x3600000000000000000000000000000000000000", + "externalId": "invoice-evm-001", + "settlement": { + "profile": "direct", + "state": "completed", + "network": "eip155:5042002", + "assetForm": "native" + } +} +``` + +### A.2 Solana Direct Charge + +The decoded request is: + +```json +{ + "amount": "1000000", + "currency": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", + "recipient": "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", + "description": "Solana devnet USDC charge", + "externalId": "invoice-sol-001", + "methodDetails": { + "profile": "solana", + "solana": { + "network": "devnet", + "decimals": 6, + "tokenProgram": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + } + } +} +``` + +The client returns the Solana transaction credential defined by +{{I-D.solana-charge}}: + +```json +{ + "challenge": { + "id": "usdc_solana_direct_001", + "realm": "api.example.com", + "method": "usdc", + "intent": "charge", + "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiI0ek1NQzlzcnQ1Umk1WDE0R0FnWGhhSGlpM0duUEFFRVJZUEpnWkpEbmNEVSIsImRlc2NyaXB0aW9uIjoiU29sYW5hIGRldm5ldCBVU0RDIGNoYXJnZSIsImV4dGVybmFsSWQiOiJpbnZvaWNlLXNvbC0wMDEiLCJtZXRob2REZXRhaWxzIjp7InByb2ZpbGUiOiJzb2xhbmEiLCJzb2xhbmEiOnsiZGVjaW1hbHMiOjYsIm5ldHdvcmsiOiJkZXZuZXQiLCJ0b2tlblByb2dyYW0iOiJUb2tlbmtlZ1FmZVp5aU53QUpiTmJHS1BGWENXdUJ2ZjlTczYyM1ZRNURBIn19LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", + "expires": "2026-04-01T12:05:00Z" + }, + "source": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy", + "payload": { + "type": "transaction", + "transaction": "", + "challengeBinding": { + "type": "challenge-binding-v00", + "signature": "" + } + } +} +``` + +This example omits `methodDetails.solana.feePayer`, so the client pays +the Solana transaction fee. If `feePayer=true`, the transaction leaves +the server fee-payer signature as the only missing required signature. + +After the transaction reaches the required commitment level, the server +returns: + +```http +HTTP/1.1 200 OK +Payment-Receipt: +``` + +The decoded receipt payload is: + +```json +{ + "method": "usdc", + "intent": "charge", + "challengeId": "usdc_solana_direct_001", + "reference": "5j7s2KpP4uYc8LmZqEhNwR3vJbXt6yA1DsVfBgCoM9TpHxUeQk", + "referenceType": "tx_signature", + "status": "success", + "timestamp": "2026-04-01T12:00:05Z", + "source": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy", + "recipient": "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", + "amount": "1000000", + "currency": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", + "externalId": "invoice-sol-001", + "settlement": { + "profile": "direct", + "state": "completed", + "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + "assetForm": "native" + } +} +``` + +### A.3 Stacks Direct Charge + +The decoded request is: + +```json +{ + "amount": "1000000", + "currency": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usdcx::usdcx-token", + "recipient": "ST3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8NQXMNRQ", + "description": "Stacks testnet USDCx charge", + "externalId": "invoice-stx-001", + "methodDetails": { + "profile": "stacks", + "stacks": { + "network": "testnet", + "chainId": "2147483648", + "contractAddress": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "contractName": "usdcx", + "assetName": "usdcx-token", + "functionName": "transfer", + "decimals": 6, + "feePayer": true, + "feePayerAddress": "ST4488BK2MKPFQBWPC7YYZKCRMQN52ST0ZV6EWT5" + } + } +} +``` + +The client returns a Stacks transaction credential: + +```json +{ + "challenge": { + "id": "usdc_stacks_direct_001", + "realm": "api.example.com", + "method": "usdc", + "intent": "charge", + "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiJTVDFQUUhRS1YwUkpYWkZZMURHWDhNTlNOWVZFM1ZHWkpTUlRQR1pHTS51c2RjeDo6dXNkY3gtdG9rZW4iLCJkZXNjcmlwdGlvbiI6IlN0YWNrcyB0ZXN0bmV0IFVTREN4IGNoYXJnZSIsImV4dGVybmFsSWQiOiJpbnZvaWNlLXN0eC0wMDEiLCJtZXRob2REZXRhaWxzIjp7InN0YWNrcyI6eyJhc3NldE5hbWUiOiJ1c2RjeC10b2tlbiIsImNoYWluSWQiOiIyMTQ3NDgzNjQ4IiwiY29udHJhY3RBZGRyZXNzIjoiU1QxUFFIUUtWMFJKWFpGWTFER1g4TU5TTllWRTNWR1pKU1JUUEdaR00iLCJjb250cmFjdE5hbWUiOiJ1c2RjeCIsImRlY2ltYWxzIjo2LCJmZWVQYXllciI6dHJ1ZSwiZmVlUGF5ZXJBZGRyZXNzIjoiU1Q0NDg4QksyTUtQRlFCV1BDN1lZWktDUk1RTjUyU1QwWlY2RVdUNSIsImZ1bmN0aW9uTmFtZSI6InRyYW5zZmVyIiwibmV0d29yayI6InRlc3RuZXQifX0sInJlY2lwaWVudCI6IlNUM0ZCUjJBR0s1SDlRQkRIM0VFTjZERjhFSzhKWTdSWDhOUVhNTlJRIn0", + "expires": "2026-04-01T12:05:00Z" + }, + "source": "stacks:2147483648:ST8H248H248H248H248H248H248H248H26RCPJ4T", + "payload": { + "type": "transaction", + "transaction": "", + "challengeBinding": { + "type": "challenge-binding-v00", + "signature": "" + }, + "transactionFormat": "stacks_transaction_v1" + } +} +``` + +The server sponsors, broadcasts, waits for the Stacks transaction, and +returns: + +```http +HTTP/1.1 200 OK +Payment-Receipt: +``` + +The decoded receipt payload is: + +```json +{ + "method": "usdc", + "intent": "charge", + "challengeId": "usdc_stacks_direct_001", + "reference": "0x9a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f50617283a4b5c6d7e8f90", + "referenceType": "tx_id", + "status": "success", + "timestamp": "2026-04-01T12:01:45Z", + "source": "stacks:2147483648:ST8H248H248H248H248H248H248H248H26RCPJ4T", + "recipient": "ST3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8NQXMNRQ", + "amount": "1000000", + "currency": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usdcx::usdcx-token", + "externalId": "invoice-stx-001", + "settlement": { + "profile": "direct", + "state": "completed", + "network": "stacks:2147483648", + "assetForm": "xreserve_backed" + } +} +``` + +### A.4 Gateway Transfer Charge + +This example shows an Arc Testnet source paying a Solana Devnet +merchant through Circle Gateway. The `recipient` is the merchant's +Solana owner address; the Gateway transfer uses the owner's USDC +token account as `spec.destinationRecipient`. The same profile also +supports Solana to EVM when Circle Gateway supports that route. The +example lists two accepted sources for readability. + +The decoded request is: + +```json +{ + "amount": "25000000", + "currency": "usdc", + "recipient": "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", + "description": "Arc Gateway balance to Solana merchant", + "externalId": "invoice-gw-001", + "methodDetails": { + "profile": "gateway", + "gateway": { + "acceptedSources": [ + "eip155:5042002", + "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" + ], + "destinationNetwork": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + "maxFee": "500000", + "credentialTypes": [ + "gateway-transfer" + ] + } + } +} +``` + +The server advertises the challenge: + +```http +HTTP/1.1 402 Payment Required +WWW-Authenticate: Payment realm="api.example.com", method="usdc", intent="charge", id="usdc_gateway_transfer_001", request="eyJhbW91bnQiOiIyNTAwMDAwMCIsImN1cnJlbmN5IjoidXNkYyIsImRlc2NyaXB0aW9uIjoiQXJjIEdhdGV3YXkgYmFsYW5jZSB0byBTb2xhbmEgbWVyY2hhbnQiLCJleHRlcm5hbElkIjoiaW52b2ljZS1ndy0wMDEiLCJtZXRob2REZXRhaWxzIjp7ImdhdGV3YXkiOnsiYWNjZXB0ZWRTb3VyY2VzIjpbImVpcDE1NTo1MDQyMDAyIiwic29sYW5hOkV0V1RSQUJaYVlxNmlNZmVZS291UnUxNjZWVTJ4cWExIl0sImNyZWRlbnRpYWxUeXBlcyI6WyJnYXRld2F5LXRyYW5zZmVyIl0sImRlc3RpbmF0aW9uTmV0d29yayI6InNvbGFuYTpFdFdUUkFCWmFZcTZpTWZlWUtvdVJ1MTY2VlUyeHFhMSIsIm1heEZlZSI6IjUwMDAwMCJ9LCJwcm9maWxlIjoiZ2F0ZXdheSJ9LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", expires="2026-04-01T12:05:00Z" +``` + +The client returns a Gateway Transfer credential. The nested Gateway +authorization object below is SDK-produced and illustrative; signatures, +salts, hashes, and transaction bytes are not self-verifying test +vectors. + +```json +{ + "challenge": { + "id": "usdc_gateway_transfer_001", + "realm": "api.example.com", + "method": "usdc", + "intent": "charge", + "request": "eyJhbW91bnQiOiIyNTAwMDAwMCIsImN1cnJlbmN5IjoidXNkYyIsImRlc2NyaXB0aW9uIjoiQXJjIEdhdGV3YXkgYmFsYW5jZSB0byBTb2xhbmEgbWVyY2hhbnQiLCJleHRlcm5hbElkIjoiaW52b2ljZS1ndy0wMDEiLCJtZXRob2REZXRhaWxzIjp7ImdhdGV3YXkiOnsiYWNjZXB0ZWRTb3VyY2VzIjpbImVpcDE1NTo1MDQyMDAyIiwic29sYW5hOkV0V1RSQUJaYVlxNmlNZmVZS291UnUxNjZWVTJ4cWExIl0sImNyZWRlbnRpYWxUeXBlcyI6WyJnYXRld2F5LXRyYW5zZmVyIl0sImRlc3RpbmF0aW9uTmV0d29yayI6InNvbGFuYTpFdFdUUkFCWmFZcTZpTWZlWUtvdVJ1MTY2VlUyeHFhMSIsIm1heEZlZSI6IjUwMDAwMCJ9LCJwcm9maWxlIjoiZ2F0ZXdheSJ9LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", + "expires": "2026-04-01T12:05:00Z" + }, + "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "payload": { + "type": "gateway-transfer", + "sourceNetwork": "eip155:5042002", + "destinationNetwork": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + "maxFee": "500000", + "authorization": { + "format": "circle-gateway-v1", + "transfer": { + "burnIntent": { + "maxBlockHeight": "39350204", + "maxFee": "500000", + "spec": { + "version": 1, + "sourceDomain": 26, + "destinationDomain": 5, + "sourceContract": "0x0000000000000000000000000077777d7eba4688bdef3e311b846f25870a19b9", + "destinationContract": "0x0000000000000000000000000000000000000000000000000000000000000005", + "sourceToken": "0x0000000000000000000000003600000000000000000000000000000000000000", + "destinationToken": "0x0000000000000000000000000000000000000000000000000000000000000006", + "sourceDepositor": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "destinationRecipient": "0xedc636e0401e29c099ea2703806591e6e40beec1091507025dbbfe6d63761fe2", + "sourceSigner": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "destinationCaller": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "25000000", + "salt": "0xab85000000000000000000000000000000000000000000000000000000000000" + }, + "recipientSetupOptions": { + "includeRecipientSetup": true, + "recipientOwnerAddress": "0x8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c" + } + }, + "signature": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + } + } +} +``` + +The payer authorizes up to 0.50 USDC in Gateway fees on top of the 25.00 +USDC merchant amount. The server submits the Gateway authorization to +Circle Gateway, waits for destination settlement, and returns a receipt +that includes the Gateway-returned `transferSpecHash`: + +```http +HTTP/1.1 200 OK +Payment-Receipt: +``` + +The decoded receipt payload is: + +```json +{ + "method": "usdc", + "intent": "charge", + "challengeId": "usdc_gateway_transfer_001", + "reference": "5j7s2KpP4uYc8LmZqEhNwR3vJbXt6yA1DsVfBgCoM9TpHxUeQk", + "referenceType": "tx_signature", + "status": "success", + "timestamp": "2026-04-01T12:02:17Z", + "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "recipient": "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", + "amount": "25000000", + "currency": "usdc", + "externalId": "invoice-gw-001", + "settlement": { + "profile": "gateway-transfer", + "state": "completed", + "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + "assetForm": "native", + "gateway": { + "transferId": "550e8400-e29b-41d4-a716-446655440000", + "sourceNetwork": "eip155:5042002", + "destinationNetwork": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + "transferSpecHash": "0xca85000000000000000000000000000000000000000000000000000000000000", + "destinationReference": "5j7s2KpP4uYc8LmZqEhNwR3vJbXt6yA1DsVfBgCoM9TpHxUeQk", + "destinationReferenceType": "tx_signature", + "status": "finalized", + "amount": "25000000", + "maxFee": "500000" + } + } +} +``` From db2361b66a5deb3a51e316972bafcbfb04f57b4d Mon Sep 17 00:00:00 2001 From: Harshal Bhangale Date: Mon, 8 Jun 2026 22:57:50 -0700 Subject: [PATCH 2/3] Address USDC review comments --- specs/methods/usdc/draft-usdc-charge-00.md | 264 +++++++++------------ 1 file changed, 107 insertions(+), 157 deletions(-) diff --git a/specs/methods/usdc/draft-usdc-charge-00.md b/specs/methods/usdc/draft-usdc-charge-00.md index c3fe0734..3920fa11 100644 --- a/specs/methods/usdc/draft-usdc-charge-00.md +++ b/specs/methods/usdc/draft-usdc-charge-00.md @@ -46,6 +46,12 @@ normative: I-D.solana-charge: title: Solana Charge Intent for HTTP Payment Authentication target: https://paymentauth.org/draft-solana-charge-00.html + CAIP-2: + title: Chain Agnostic Improvement Proposal 2 + target: https://chainagnostic.org/CAIPs/caip-2 + CAIP-10: + title: Chain Agnostic Improvement Proposal 10 + target: https://chainagnostic.org/CAIPs/caip-10 SIP-005: title: Stacks Blocks, Transactions, and Accounts target: https://raw.githubusercontent.com/stacksgov/sips/main/sips/sip-005/sip-005-blocks-and-transactions.md @@ -249,14 +255,18 @@ through Circle Gateway discovery and estimate APIs or a conforming SDK. ## Method Details -`methodDetails.profile` selects the active `usdc` profile. Exactly one of -`methodDetails.evm`, `methodDetails.solana`, `methodDetails.stacks`, or -`methodDetails.gateway` MUST be present, and it MUST match -`methodDetails.profile`. +`methodDetails.type` selects the active `usdc` profile. Its value MUST +be one of `evm`, `solana`, `stacks`, or `gateway`. + +The `methodDetails` object MUST include exactly one profile details +object, and that object MUST use the same name as `methodDetails.type`. +For example, when `methodDetails.type = "solana"`, +`methodDetails.solana` MUST be present and `methodDetails.evm`, +`methodDetails.stacks`, and `methodDetails.gateway` MUST be absent. | Field | Type | Required | Description | | --- | --- | --- | --- | -| `profile` | string | REQUIRED | One of `evm`, `solana`, `stacks`, or `gateway`. | +| `type` | string | REQUIRED | One of `evm`, `solana`, `stacks`, or `gateway`. | | `evm` | object | CONDITIONAL | EVM details. Required for EVM direct USDC charge. | | `solana` | object | CONDITIONAL | Solana details. Required for Solana direct USDC charge. | | `stacks` | object | CONDITIONAL | Stacks details. Required for Stacks USDCx charge. | @@ -376,7 +386,11 @@ so the payer's source-network choice is constrained by | `destinationNetwork` | string | REQUIRED | CAIP-2 destination network where the merchant wants to receive USDC. | | `maxFee` | string | REQUIRED | Absolute upper bound on the signed Gateway authorization fee cap, in USDC base units. | | `maxFeeBps` | number | OPTIONAL | Additional ratio cap. When present, `authorizationFeeCap * 10000 <= request.amount * maxFeeBps`. | -| `credentialTypes` | array | OPTIONAL | If present, MUST contain only `gateway-transfer` in v00. If absent, `gateway-transfer` is implied. | +| `credentialTypes` | array | OPTIONAL | If present, MUST contain only `transfer` in v00. If absent, `transfer` is implied. | + +Gateway network fields use CAIP-2 {{CAIP-2}} identifiers. Gateway +account identifiers use CAIP-10 {{CAIP-10}} account IDs in the form +`:`. Circle Gateway maps source and destination networks to Gateway domains, token identifiers, wallet contracts, minter contracts, recipient setup @@ -440,7 +454,7 @@ keccak256(UTF-8 bytes of JCS({ "method": "usdc", "realm": , "intent": "charge", - "profile": "gateway", + "type": "gateway", "requestHash": , "sourceNetwork": , "destinationNetwork": , @@ -646,17 +660,18 @@ For Gateway Transfer, the credential object contains: | `payload` | object | REQUIRED | Gateway Transfer payload. | | `source` | string | REQUIRED | CAIP-10 source depositor account ID. | -The Gateway Transfer `payload.type` MUST be `gateway-transfer`. The -payload carries the selected source for this charge. It does not repeat -the full `acceptedSources` list from the request. +The Gateway Transfer `payload.type` MUST be `transfer`. This value is +scoped to `methodDetails.type="gateway"`. The payload carries the +selected source for this charge. It does not repeat the full +`acceptedSources` list from the request. | Field | Type | Required | Description | | --- | --- | --- | --- | -| `type` | string | REQUIRED | MUST be `gateway-transfer`. | +| `type` | string | REQUIRED | MUST be `transfer`. | | `sourceNetwork` | string | REQUIRED | Selected CAIP-2 source network. MUST be one entry from `methodDetails.gateway.acceptedSources`. | | `destinationNetwork` | string | REQUIRED | CAIP-2 destination network. | | `maxFee` | string | REQUIRED | Maximum Gateway fee authorized by the payer, in USDC base units. | -| `authorization` | object | REQUIRED | Circle Gateway signed authorization package, or SDK-produced versioned object, for the selected source network. It MUST expose the signed fields required by {{verification-procedure}} to the verifier. | +| `authorization` | object | REQUIRED | Circle Gateway signed authorization package, or SDK-produced versioned object, for the selected source network. | The Gateway authorization package represents one Gateway transfer for the selected source network. Its internal encoding is Circle Gateway @@ -664,7 +679,9 @@ versioned data, not a PaymentAuth extension point. When this document names Gateway fields such as source depositor, source signer, TransferSpec value, fee cap, destination recipient, recipient setup options, or salt, it refers to their Gateway semantics. The wire object -MAY carry those values under versioned Gateway field names. +MAY carry those values under versioned Gateway field names. The +authorization object MUST expose the signed fields required by +{{verification-procedure}} to the verifier. The surrounding PaymentAuth credential binds that Gateway package to `credential.challenge.id` and `credential.challenge.realm` by requiring @@ -684,8 +701,8 @@ verification: 2. Verify `method="usdc"` and `intent="charge"`. 3. Verify `credential.challenge` matches the selected challenge. 4. Verify the challenge has not expired. -5. Verify `methodDetails.profile` is present and exactly one matching - profile object is present. +5. Verify `methodDetails.type` is present, exactly one profile object + is present, and the present object key matches `methodDetails.type`. 6. Verify `amount` is a positive integer in base units. 7. Verify the selected token is a supported USDC asset form for the selected profile. @@ -701,7 +718,7 @@ controls required by local policy. For EVM, servers then apply {{I-D.evm-charge}} verification with the USDC restrictions in {{evm-profile}}. Servers MUST verify -`methodDetails.profile = "evm"`. For `payload.type="authorization"`, +`methodDetails.type = "evm"`. For `payload.type="authorization"`, the EIP-3009 fields are carried directly in `payload` as defined by {{I-D.evm-charge}}. The server MUST verify `payload.to` and `request.recipient` identify the same 20-byte EVM address, @@ -712,7 +729,7 @@ case-sensitive string equality. For Solana, servers then apply {{I-D.solana-charge}} verification with the USDC restrictions in {{solana-profile}}. Servers MUST verify -`methodDetails.profile = "solana"`. Servers MUST also verify that +`methodDetails.type = "solana"`. Servers MUST also verify that `payload.challengeBinding.type = "challenge-binding-v00"` and that `payload.challengeBinding.signature` authenticates the challenge-binding binding message under the same source key that authorizes the token @@ -754,7 +771,7 @@ exposure exceed local policy. For Stacks, servers MUST verify: 1. `payload.type = "transaction"`. -2. `methodDetails.profile = "stacks"`. +2. `methodDetails.type = "stacks"`. 3. `source` uses `stacks::` and the chain id matches `methodDetails.stacks.chainId`. 4. `methodDetails.stacks.decimals = 6`. @@ -789,8 +806,9 @@ For Stacks, servers MUST verify: `principal = source-principal`, `asset_info = (contractAddress, contractName, assetName)`, `condition_code = SentEq`, and `amount = request.amount`. -19. The SIP-018 challenge-binding signature recovers to the principal - in `source`. +19. The SIP-018 challenge-binding signature authenticates the + challenge-binding message defined in this document and recovers to + the principal in `source`. 20. The origin auth nonce is fresh under the server's local Stacks nonce policy. 21. The transaction reaches the server's local Stacks confirmation @@ -798,8 +816,8 @@ For Stacks, servers MUST verify: For Gateway Transfer, servers MUST verify: -1. `payload.type = "gateway-transfer"`. -2. `methodDetails.profile = "gateway"`. +1. `payload.type = "transfer"`. +2. `methodDetails.type = "gateway"`. 3. `request.currency = "usdc"`. 4. `credential.source` is a CAIP-10 account for `payload.sourceNetwork` and identifies the source depositor. @@ -885,73 +903,50 @@ intent outside this document. # Receipt Schema -The receipt MUST identify the selected challenge, network, token, -payer, recipient, amount, and final transaction reference. - Upon successful settlement, servers MUST return a `Payment-Receipt` header per {{I-D.httpauth-payment}}. The decoded receipt payload -contains: - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `method` | string | REQUIRED | MUST be `usdc`. | -| `intent` | string | REQUIRED | MUST be `charge`. | -| `challengeId` | string | REQUIRED | Original challenge ID. | -| `reference` | string | REQUIRED | Transaction hash, signature, or transaction ID. | -| `referenceType` | string | REQUIRED | `tx_hash`, `tx_signature`, or `tx_id`. | -| `status` | string | REQUIRED | `success` only after the selected profile has completed settlement. | -| `timestamp` | string | REQUIRED | RFC3339 settlement time. | -| `source` | string | REQUIRED | Payer account or address. | -| `recipient` | string | REQUIRED | Recipient account or address. | -| `amount` | string | REQUIRED | Amount in base units. | -| `currency` | string | REQUIRED | Same token identifier used in the request. | -| `settlement` | object | REQUIRED | Chain-local settlement reference. | -| `externalId` | string | OPTIONAL | Echo of `request.externalId`. | - -The `settlement` object MUST include: +contains the following fields. Fields are REQUIRED unless the +description says OPTIONAL. + +The receipt is a settlement pointer interpreted together with the +original challenge and request. It does not repeat every request or +verification field. + +| Field | Type | Description | +| --- | --- | --- | +| `method` | string | MUST be `usdc`. | +| `type` | string | Selected USDC profile: `evm`, `solana`, `stacks`, or `gateway`. | +| `challengeId` | string | Original challenge ID. | +| `reference` | string | Final settlement reference. | +| `status` | string | MUST be `success` only after the selected profile has completed settlement. | +| `timestamp` | string | RFC3339 settlement time. | +| `network` | string | Settlement network identifier. | +| `externalId` | string | OPTIONAL. Echo of `request.externalId`. | + +For direct EVM, `reference` is the EVM transaction hash. For direct +Solana, `reference` is the Solana transaction signature. For direct +Stacks, `reference` is the Stacks transaction ID. + +For Gateway Transfer, `reference` is the final destination settlement +reference exposed by Circle Gateway, and `network` MUST be +`methodDetails.gateway.destinationNetwork`. The receipt MAY include a +`gateway` object with Gateway audit handles. SDKs that do not need +Gateway reconciliation MAY ignore this object. + +When present, the `gateway` object contains: | Field | Type | Required | Description | | --- | --- | --- | --- | -| `profile` | string | REQUIRED | `direct` or `gateway-transfer`. | -| `state` | string | REQUIRED | MUST be `completed`. | -| `network` | string | REQUIRED | Settlement network identifier. | -| `assetForm` | string | REQUIRED | `native` or `xreserve_backed`. | +| `transferId` | string | OPTIONAL | Circle Gateway transfer UUID when available. | +| `sourceNetwork` | string | OPTIONAL | CAIP-2 source network used by the Gateway transfer. | +| `destinationNetwork` | string | OPTIONAL | CAIP-2 destination network. When present, MUST equal receipt `network`. | +| `transferSpecHash` | string | OPTIONAL | Gateway-returned hash of the TransferSpec used by the transfer. | -For EVM, `referenceType` SHOULD be `tx_hash`. - -For Solana, `referenceType` SHOULD be `tx_signature`. - -For Stacks, `referenceType` SHOULD be `tx_id`. - -For Gateway Transfer, `settlement.profile` MUST be -`gateway-transfer`, `settlement.network` MUST be the destination -network, and `referenceType` MUST identify the final destination -settlement reference exposed by Circle Gateway. EVM destinations -normally use `tx_hash`; Solana destinations normally use -`tx_signature`. The `settlement` object MUST include a `gateway` -object with the source network actually used for settlement. It does -not repeat the full `acceptedSources` list from the request. - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `transferId` | string | REQUIRED | Circle Gateway transfer UUID. | -| `sourceNetwork` | string | REQUIRED | CAIP-2 source network used by the Gateway transfer. | -| `destinationNetwork` | string | REQUIRED | CAIP-2 destination network. | -| `transferSpecHash` | string | REQUIRED | Gateway-returned hash of the TransferSpec used by the transfer. | -| `destinationReference` | string | OPTIONAL | Destination settlement reference when available. | -| `destinationReferenceType` | string | OPTIONAL | Reference type for `destinationReference`. | -| `status` | string | REQUIRED | Raw Circle Gateway transfer status. MUST be `finalized` for a successful charge receipt. | -| `amount` | string | REQUIRED | Merchant amount in USDC base units. | -| `maxFee` | string | REQUIRED | Maximum Gateway fee authorized by the payer. | -| `fees` | object | OPTIONAL | Gateway fee summary returned by Circle Gateway. Fee summary amounts use Circle Gateway reporting units and are payer-paid. This field MUST be included when Circle Gateway returns a settled fee summary for the transfer. | - -The receipt `transferSpecHash` is evidence returned by Circle Gateway. -It is not supplied by the client credential. A verifier MAY use Circle -Gateway TransferSpec lookup {{CIRCLE-GATEWAY-TRANSFER-SPEC}} to inspect -the settled TransferSpec after acceptance or settlement. For Gateway -Transfer, the concrete token identity is verifiable from the Gateway -transfer item or TransferSpec lookup keyed by `transferSpecHash`; the -receipt does not repeat per-chain token addresses. +The receipt `gateway.transferSpecHash` is evidence returned by Circle +Gateway. It is not supplied by the client credential. A verifier MAY +use Circle Gateway TransferSpec lookup +{{CIRCLE-GATEWAY-TRANSFER-SPEC}} to inspect the settled TransferSpec +after acceptance or settlement. # Security Considerations @@ -1092,7 +1087,7 @@ The decoded request is: "description": "Arc Testnet USDC charge", "externalId": "invoice-evm-001", "methodDetails": { - "profile": "evm", + "type": "evm", "evm": { "chainId": 5042002, "decimals": 6, @@ -1108,7 +1103,7 @@ The server advertises the challenge: ```http HTTP/1.1 402 Payment Required -WWW-Authenticate: Payment realm="api.example.com", method="usdc", intent="charge", id="usdc_evm_direct_001", request="eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDM2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJkZXNjcmlwdGlvbiI6IkFyYyBUZXN0bmV0IFVTREMgY2hhcmdlIiwiZXh0ZXJuYWxJZCI6Imludm9pY2UtZXZtLTAwMSIsIm1ldGhvZERldGFpbHMiOnsiZXZtIjp7ImNoYWluSWQiOjUwNDIwMDIsImNyZWRlbnRpYWxUeXBlcyI6WyJhdXRob3JpemF0aW9uIl0sImRlY2ltYWxzIjo2fSwicHJvZmlsZSI6ImV2bSJ9LCJyZWNpcGllbnQiOiIweGMwNDE5M0M1MGNEMkU2YTFDNzk1OTNlNDYzNjQ0OTZGZTVmY2Q5YjYifQ", expires="2026-04-01T12:05:00Z" +WWW-Authenticate: Payment realm="api.example.com", method="usdc", intent="charge", id="usdc_evm_direct_001", request="eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDM2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJkZXNjcmlwdGlvbiI6IkFyYyBUZXN0bmV0IFVTREMgY2hhcmdlIiwiZXh0ZXJuYWxJZCI6Imludm9pY2UtZXZtLTAwMSIsIm1ldGhvZERldGFpbHMiOnsiZXZtIjp7ImNoYWluSWQiOjUwNDIwMDIsImNyZWRlbnRpYWxUeXBlcyI6WyJhdXRob3JpemF0aW9uIl0sImRlY2ltYWxzIjo2fSwidHlwZSI6ImV2bSJ9LCJyZWNpcGllbnQiOiIweGMwNDE5M0M1MGNEMkU2YTFDNzk1OTNlNDYzNjQ0OTZGZTVmY2Q5YjYifQ", expires="2026-04-01T12:05:00Z" ``` The client returns an EIP-3009 authorization credential: @@ -1120,7 +1115,7 @@ The client returns an EIP-3009 authorization credential: "realm": "api.example.com", "method": "usdc", "intent": "charge", - "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDM2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJkZXNjcmlwdGlvbiI6IkFyYyBUZXN0bmV0IFVTREMgY2hhcmdlIiwiZXh0ZXJuYWxJZCI6Imludm9pY2UtZXZtLTAwMSIsIm1ldGhvZERldGFpbHMiOnsiZXZtIjp7ImNoYWluSWQiOjUwNDIwMDIsImNyZWRlbnRpYWxUeXBlcyI6WyJhdXRob3JpemF0aW9uIl0sImRlY2ltYWxzIjo2fSwicHJvZmlsZSI6ImV2bSJ9LCJyZWNpcGllbnQiOiIweGMwNDE5M0M1MGNEMkU2YTFDNzk1OTNlNDYzNjQ0OTZGZTVmY2Q5YjYifQ", + "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDM2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJkZXNjcmlwdGlvbiI6IkFyYyBUZXN0bmV0IFVTREMgY2hhcmdlIiwiZXh0ZXJuYWxJZCI6Imludm9pY2UtZXZtLTAwMSIsIm1ldGhvZERldGFpbHMiOnsiZXZtIjp7ImNoYWluSWQiOjUwNDIwMDIsImNyZWRlbnRpYWxUeXBlcyI6WyJhdXRob3JpemF0aW9uIl0sImRlY2ltYWxzIjo2fSwidHlwZSI6ImV2bSJ9LCJyZWNpcGllbnQiOiIweGMwNDE5M0M1MGNEMkU2YTFDNzk1OTNlNDYzNjQ0OTZGZTVmY2Q5YjYifQ", "expires": "2026-04-01T12:05:00Z" }, "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", @@ -1131,7 +1126,7 @@ The client returns an EIP-3009 authorization credential: "value": "1000000", "validAfter": "0", "validBefore": "1775045100", - "nonce": "0x349f879fd77eb5a560c759b5d63e7f52cc04a82015b0cd5cce6d4b308fb36f3e", + "nonce": "0x03e1d1aa38e2c56a0bb12e2d4562082c1c26496553f838064f3e6b4c3db9d2c2", "signature": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" } } @@ -1150,23 +1145,13 @@ The decoded receipt payload is: ```json { "method": "usdc", - "intent": "charge", + "type": "evm", "challengeId": "usdc_evm_direct_001", "reference": "0x3c5b4a1f00000000000000000000000000000000000000000000000000008d0a2c4e6b", - "referenceType": "tx_hash", "status": "success", "timestamp": "2026-04-01T12:00:04Z", - "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "recipient": "0xc04193C50cD2E6a1C79593e46364496Fe5fcd9b6", - "amount": "1000000", - "currency": "0x3600000000000000000000000000000000000000", - "externalId": "invoice-evm-001", - "settlement": { - "profile": "direct", - "state": "completed", - "network": "eip155:5042002", - "assetForm": "native" - } + "network": "eip155:5042002", + "externalId": "invoice-evm-001" } ``` @@ -1182,7 +1167,7 @@ The decoded request is: "description": "Solana devnet USDC charge", "externalId": "invoice-sol-001", "methodDetails": { - "profile": "solana", + "type": "solana", "solana": { "network": "devnet", "decimals": 6, @@ -1202,7 +1187,7 @@ The client returns the Solana transaction credential defined by "realm": "api.example.com", "method": "usdc", "intent": "charge", - "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiI0ek1NQzlzcnQ1Umk1WDE0R0FnWGhhSGlpM0duUEFFRVJZUEpnWkpEbmNEVSIsImRlc2NyaXB0aW9uIjoiU29sYW5hIGRldm5ldCBVU0RDIGNoYXJnZSIsImV4dGVybmFsSWQiOiJpbnZvaWNlLXNvbC0wMDEiLCJtZXRob2REZXRhaWxzIjp7InByb2ZpbGUiOiJzb2xhbmEiLCJzb2xhbmEiOnsiZGVjaW1hbHMiOjYsIm5ldHdvcmsiOiJkZXZuZXQiLCJ0b2tlblByb2dyYW0iOiJUb2tlbmtlZ1FmZVp5aU53QUpiTmJHS1BGWENXdUJ2ZjlTczYyM1ZRNURBIn19LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", + "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiI0ek1NQzlzcnQ1Umk1WDE0R0FnWGhhSGlpM0duUEFFRVJZUEpnWkpEbmNEVSIsImRlc2NyaXB0aW9uIjoiU29sYW5hIGRldm5ldCBVU0RDIGNoYXJnZSIsImV4dGVybmFsSWQiOiJpbnZvaWNlLXNvbC0wMDEiLCJtZXRob2REZXRhaWxzIjp7InNvbGFuYSI6eyJkZWNpbWFscyI6NiwibmV0d29yayI6ImRldm5ldCIsInRva2VuUHJvZ3JhbSI6IlRva2Vua2VnUWZlWnlpTndBSmJOYkdLUEZYQ1d1QnZmOVNzNjIzVlE1REEifSwidHlwZSI6InNvbGFuYSJ9LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", "expires": "2026-04-01T12:05:00Z" }, "source": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy", @@ -1234,23 +1219,13 @@ The decoded receipt payload is: ```json { "method": "usdc", - "intent": "charge", + "type": "solana", "challengeId": "usdc_solana_direct_001", "reference": "5j7s2KpP4uYc8LmZqEhNwR3vJbXt6yA1DsVfBgCoM9TpHxUeQk", - "referenceType": "tx_signature", "status": "success", "timestamp": "2026-04-01T12:00:05Z", - "source": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy", - "recipient": "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", - "amount": "1000000", - "currency": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", - "externalId": "invoice-sol-001", - "settlement": { - "profile": "direct", - "state": "completed", - "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", - "assetForm": "native" - } + "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + "externalId": "invoice-sol-001" } ``` @@ -1266,7 +1241,7 @@ The decoded request is: "description": "Stacks testnet USDCx charge", "externalId": "invoice-stx-001", "methodDetails": { - "profile": "stacks", + "type": "stacks", "stacks": { "network": "testnet", "chainId": "2147483648", @@ -1291,7 +1266,7 @@ The client returns a Stacks transaction credential: "realm": "api.example.com", "method": "usdc", "intent": "charge", - "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiJTVDFQUUhRS1YwUkpYWkZZMURHWDhNTlNOWVZFM1ZHWkpTUlRQR1pHTS51c2RjeDo6dXNkY3gtdG9rZW4iLCJkZXNjcmlwdGlvbiI6IlN0YWNrcyB0ZXN0bmV0IFVTREN4IGNoYXJnZSIsImV4dGVybmFsSWQiOiJpbnZvaWNlLXN0eC0wMDEiLCJtZXRob2REZXRhaWxzIjp7InN0YWNrcyI6eyJhc3NldE5hbWUiOiJ1c2RjeC10b2tlbiIsImNoYWluSWQiOiIyMTQ3NDgzNjQ4IiwiY29udHJhY3RBZGRyZXNzIjoiU1QxUFFIUUtWMFJKWFpGWTFER1g4TU5TTllWRTNWR1pKU1JUUEdaR00iLCJjb250cmFjdE5hbWUiOiJ1c2RjeCIsImRlY2ltYWxzIjo2LCJmZWVQYXllciI6dHJ1ZSwiZmVlUGF5ZXJBZGRyZXNzIjoiU1Q0NDg4QksyTUtQRlFCV1BDN1lZWktDUk1RTjUyU1QwWlY2RVdUNSIsImZ1bmN0aW9uTmFtZSI6InRyYW5zZmVyIiwibmV0d29yayI6InRlc3RuZXQifX0sInJlY2lwaWVudCI6IlNUM0ZCUjJBR0s1SDlRQkRIM0VFTjZERjhFSzhKWTdSWDhOUVhNTlJRIn0", + "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiJTVDFQUUhRS1YwUkpYWkZZMURHWDhNTlNOWVZFM1ZHWkpTUlRQR1pHTS51c2RjeDo6dXNkY3gtdG9rZW4iLCJkZXNjcmlwdGlvbiI6IlN0YWNrcyB0ZXN0bmV0IFVTREN4IGNoYXJnZSIsImV4dGVybmFsSWQiOiJpbnZvaWNlLXN0eC0wMDEiLCJtZXRob2REZXRhaWxzIjp7InN0YWNrcyI6eyJhc3NldE5hbWUiOiJ1c2RjeC10b2tlbiIsImNoYWluSWQiOiIyMTQ3NDgzNjQ4IiwiY29udHJhY3RBZGRyZXNzIjoiU1QxUFFIUUtWMFJKWFpGWTFER1g4TU5TTllWRTNWR1pKU1JUUEdaR00iLCJjb250cmFjdE5hbWUiOiJ1c2RjeCIsImRlY2ltYWxzIjo2LCJmZWVQYXllciI6dHJ1ZSwiZmVlUGF5ZXJBZGRyZXNzIjoiU1Q0NDg4QksyTUtQRlFCV1BDN1lZWktDUk1RTjUyU1QwWlY2RVdUNSIsImZ1bmN0aW9uTmFtZSI6InRyYW5zZmVyIiwibmV0d29yayI6InRlc3RuZXQifSwidHlwZSI6InN0YWNrcyJ9LCJyZWNpcGllbnQiOiJTVDNGQlIyQUdLNUg5UUJESDNFRU42REY4RUs4Slk3Ulg4TlFYTU5SUSJ9", "expires": "2026-04-01T12:05:00Z" }, "source": "stacks:2147483648:ST8H248H248H248H248H248H248H248H26RCPJ4T", @@ -1320,23 +1295,13 @@ The decoded receipt payload is: ```json { "method": "usdc", - "intent": "charge", + "type": "stacks", "challengeId": "usdc_stacks_direct_001", "reference": "0x9a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f50617283a4b5c6d7e8f90", - "referenceType": "tx_id", "status": "success", "timestamp": "2026-04-01T12:01:45Z", - "source": "stacks:2147483648:ST8H248H248H248H248H248H248H248H26RCPJ4T", - "recipient": "ST3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8NQXMNRQ", - "amount": "1000000", - "currency": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usdcx::usdcx-token", - "externalId": "invoice-stx-001", - "settlement": { - "profile": "direct", - "state": "completed", - "network": "stacks:2147483648", - "assetForm": "xreserve_backed" - } + "network": "stacks:2147483648", + "externalId": "invoice-stx-001" } ``` @@ -1359,7 +1324,7 @@ The decoded request is: "description": "Arc Gateway balance to Solana merchant", "externalId": "invoice-gw-001", "methodDetails": { - "profile": "gateway", + "type": "gateway", "gateway": { "acceptedSources": [ "eip155:5042002", @@ -1368,7 +1333,7 @@ The decoded request is: "destinationNetwork": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "maxFee": "500000", "credentialTypes": [ - "gateway-transfer" + "transfer" ] } } @@ -1379,7 +1344,7 @@ The server advertises the challenge: ```http HTTP/1.1 402 Payment Required -WWW-Authenticate: Payment realm="api.example.com", method="usdc", intent="charge", id="usdc_gateway_transfer_001", request="eyJhbW91bnQiOiIyNTAwMDAwMCIsImN1cnJlbmN5IjoidXNkYyIsImRlc2NyaXB0aW9uIjoiQXJjIEdhdGV3YXkgYmFsYW5jZSB0byBTb2xhbmEgbWVyY2hhbnQiLCJleHRlcm5hbElkIjoiaW52b2ljZS1ndy0wMDEiLCJtZXRob2REZXRhaWxzIjp7ImdhdGV3YXkiOnsiYWNjZXB0ZWRTb3VyY2VzIjpbImVpcDE1NTo1MDQyMDAyIiwic29sYW5hOkV0V1RSQUJaYVlxNmlNZmVZS291UnUxNjZWVTJ4cWExIl0sImNyZWRlbnRpYWxUeXBlcyI6WyJnYXRld2F5LXRyYW5zZmVyIl0sImRlc3RpbmF0aW9uTmV0d29yayI6InNvbGFuYTpFdFdUUkFCWmFZcTZpTWZlWUtvdVJ1MTY2VlUyeHFhMSIsIm1heEZlZSI6IjUwMDAwMCJ9LCJwcm9maWxlIjoiZ2F0ZXdheSJ9LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", expires="2026-04-01T12:05:00Z" +WWW-Authenticate: Payment realm="api.example.com", method="usdc", intent="charge", id="usdc_gateway_transfer_001", request="eyJhbW91bnQiOiIyNTAwMDAwMCIsImN1cnJlbmN5IjoidXNkYyIsImRlc2NyaXB0aW9uIjoiQXJjIEdhdGV3YXkgYmFsYW5jZSB0byBTb2xhbmEgbWVyY2hhbnQiLCJleHRlcm5hbElkIjoiaW52b2ljZS1ndy0wMDEiLCJtZXRob2REZXRhaWxzIjp7ImdhdGV3YXkiOnsiYWNjZXB0ZWRTb3VyY2VzIjpbImVpcDE1NTo1MDQyMDAyIiwic29sYW5hOkV0V1RSQUJaYVlxNmlNZmVZS291UnUxNjZWVTJ4cWExIl0sImNyZWRlbnRpYWxUeXBlcyI6WyJ0cmFuc2ZlciJdLCJkZXN0aW5hdGlvbk5ldHdvcmsiOiJzb2xhbmE6RXRXVFJBQlphWXE2aU1mZVlLb3VSdTE2NlZVMnhxYTEiLCJtYXhGZWUiOiI1MDAwMDAifSwidHlwZSI6ImdhdGV3YXkifSwicmVjaXBpZW50IjoiQUtuTDROTmYzREdXWkpTNmNQa25CdUVHblZzVjRBNG01dGdlYkxIYVJTWjkifQ", expires="2026-04-01T12:05:00Z" ``` The client returns a Gateway Transfer credential. The nested Gateway @@ -1394,12 +1359,12 @@ vectors. "realm": "api.example.com", "method": "usdc", "intent": "charge", - "request": "eyJhbW91bnQiOiIyNTAwMDAwMCIsImN1cnJlbmN5IjoidXNkYyIsImRlc2NyaXB0aW9uIjoiQXJjIEdhdGV3YXkgYmFsYW5jZSB0byBTb2xhbmEgbWVyY2hhbnQiLCJleHRlcm5hbElkIjoiaW52b2ljZS1ndy0wMDEiLCJtZXRob2REZXRhaWxzIjp7ImdhdGV3YXkiOnsiYWNjZXB0ZWRTb3VyY2VzIjpbImVpcDE1NTo1MDQyMDAyIiwic29sYW5hOkV0V1RSQUJaYVlxNmlNZmVZS291UnUxNjZWVTJ4cWExIl0sImNyZWRlbnRpYWxUeXBlcyI6WyJnYXRld2F5LXRyYW5zZmVyIl0sImRlc3RpbmF0aW9uTmV0d29yayI6InNvbGFuYTpFdFdUUkFCWmFZcTZpTWZlWUtvdVJ1MTY2VlUyeHFhMSIsIm1heEZlZSI6IjUwMDAwMCJ9LCJwcm9maWxlIjoiZ2F0ZXdheSJ9LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", + "request": "eyJhbW91bnQiOiIyNTAwMDAwMCIsImN1cnJlbmN5IjoidXNkYyIsImRlc2NyaXB0aW9uIjoiQXJjIEdhdGV3YXkgYmFsYW5jZSB0byBTb2xhbmEgbWVyY2hhbnQiLCJleHRlcm5hbElkIjoiaW52b2ljZS1ndy0wMDEiLCJtZXRob2REZXRhaWxzIjp7ImdhdGV3YXkiOnsiYWNjZXB0ZWRTb3VyY2VzIjpbImVpcDE1NTo1MDQyMDAyIiwic29sYW5hOkV0V1RSQUJaYVlxNmlNZmVZS291UnUxNjZWVTJ4cWExIl0sImNyZWRlbnRpYWxUeXBlcyI6WyJ0cmFuc2ZlciJdLCJkZXN0aW5hdGlvbk5ldHdvcmsiOiJzb2xhbmE6RXRXVFJBQlphWXE2aU1mZVlLb3VSdTE2NlZVMnhxYTEiLCJtYXhGZWUiOiI1MDAwMDAifSwidHlwZSI6ImdhdGV3YXkifSwicmVjaXBpZW50IjoiQUtuTDROTmYzREdXWkpTNmNQa25CdUVHblZzVjRBNG01dGdlYkxIYVJTWjkifQ", "expires": "2026-04-01T12:05:00Z" }, "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "payload": { - "type": "gateway-transfer", + "type": "transfer", "sourceNetwork": "eip155:5042002", "destinationNetwork": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "maxFee": "500000", @@ -1451,33 +1416,18 @@ The decoded receipt payload is: ```json { "method": "usdc", - "intent": "charge", + "type": "gateway", "challengeId": "usdc_gateway_transfer_001", "reference": "5j7s2KpP4uYc8LmZqEhNwR3vJbXt6yA1DsVfBgCoM9TpHxUeQk", - "referenceType": "tx_signature", "status": "success", "timestamp": "2026-04-01T12:02:17Z", - "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "recipient": "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", - "amount": "25000000", - "currency": "usdc", + "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "externalId": "invoice-gw-001", - "settlement": { - "profile": "gateway-transfer", - "state": "completed", - "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", - "assetForm": "native", - "gateway": { - "transferId": "550e8400-e29b-41d4-a716-446655440000", - "sourceNetwork": "eip155:5042002", - "destinationNetwork": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", - "transferSpecHash": "0xca85000000000000000000000000000000000000000000000000000000000000", - "destinationReference": "5j7s2KpP4uYc8LmZqEhNwR3vJbXt6yA1DsVfBgCoM9TpHxUeQk", - "destinationReferenceType": "tx_signature", - "status": "finalized", - "amount": "25000000", - "maxFee": "500000" - } + "gateway": { + "transferId": "550e8400-e29b-41d4-a716-446655440000", + "sourceNetwork": "eip155:5042002", + "destinationNetwork": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + "transferSpecHash": "0xca85000000000000000000000000000000000000000000000000000000000000" } } ``` From edafb64caa5d853562d2ead33f9d8d0fa1083913 Mon Sep 17 00:00:00 2001 From: Harshal Bhangale Date: Mon, 15 Jun 2026 17:21:41 -0700 Subject: [PATCH 3/3] Update USDC charge draft --- specs/methods/usdc/draft-usdc-charge-00.md | 351 +++++++++------------ 1 file changed, 150 insertions(+), 201 deletions(-) diff --git a/specs/methods/usdc/draft-usdc-charge-00.md b/specs/methods/usdc/draft-usdc-charge-00.md index 3920fa11..041c0ea4 100644 --- a/specs/methods/usdc/draft-usdc-charge-00.md +++ b/specs/methods/usdc/draft-usdc-charge-00.md @@ -58,9 +58,6 @@ normative: SIP-010: title: Stacks Fungible Token Standard target: https://raw.githubusercontent.com/stacksgov/sips/main/sips/sip-010/sip-010-fungible-token-standard.md - SIP-018: - title: Stacks Signed Structured Data - target: https://raw.githubusercontent.com/stacksgov/sips/main/sips/sip-018/sip-018-signed-structured-data.md CIRCLE-GATEWAY-INFO: title: Circle Gateway GET /v1/info target: https://developers.circle.com/api-reference/gateway/all/get-gateway-info @@ -100,9 +97,10 @@ and broadcast mechanics in the relevant chain profile. This version covers direct USDC charges on EVM and Solana by profiling the existing PaymentAuth EVM and Solana charge specifications. The EVM profile is intentionally limited to EIP-3009 -authorization credentials in v00. It also defines a direct Stacks -USDCx profile because Stacks USDCx is backed by USDC through xReserve -and is not covered by a generic MPP chain method today. +authorization credentials in v00. It also defines a direct +USDCx on Stacks profile because USDCx on Stacks is backed by USDC +through xReserve and is not covered by a generic MPP chain method +today. This version also defines a Gateway Transfer charge profile for cross-chain USDC payments through Circle Gateway. The merchant chooses @@ -137,8 +135,7 @@ Normatively specified: - EVM direct USDC charges, by reference to {{I-D.evm-charge}}. - Solana direct USDC charges, by reference to {{I-D.solana-charge}}. -- Stacks direct USDCx charges using SIP-010 transfers and SIP-018 - challenge binding. +- Stacks direct USDCx charges using SIP-010 transfers. - Gateway Transfer charges through Circle Gateway that settle on the merchant's selected destination network before a successful receipt is returned. @@ -189,10 +186,10 @@ capitals. deployment. **USDCx on Stacks** -: The SIP-010 token issued by the partner-deployed Stacks USDCx - contract and backed 1:1 by USDC controlled through Circle xReserve on - supported source chains. USDCx is not native USDC, so this profile - treats it as a distinct asset form. +: The SIP-010 token issued by the partner-deployed USDCx on Stacks + contract and backed 1:1 by USDC deposited into a Circle xReserve + smart contract on supported source chains. USDCx is not native USDC, + so this profile treats it as a distinct asset form. **Direct Charge** : A payment where the server returns a successful receipt only after @@ -269,9 +266,26 @@ For example, when `methodDetails.type = "solana"`, | `type` | string | REQUIRED | One of `evm`, `solana`, `stacks`, or `gateway`. | | `evm` | object | CONDITIONAL | EVM details. Required for EVM direct USDC charge. | | `solana` | object | CONDITIONAL | Solana details. Required for Solana direct USDC charge. | -| `stacks` | object | CONDITIONAL | Stacks details. Required for Stacks USDCx charge. | +| `stacks` | object | CONDITIONAL | Stacks details. Required for USDCx on Stacks charge. | | `gateway` | object | CONDITIONAL | Circle Gateway Transfer details. Required for Gateway Transfer charge. | +This `methodDetails.type` discriminator with a nested per-profile +object is intentionally not the flat `methodDetails` shape used by +{{I-D.evm-charge}} and {{I-D.solana-charge}}. A parser written for the +base specs' flat `methodDetails` will not work here; implementations +MUST select the active profile object by `methodDetails.type`. + +Identifier formats otherwise follow each profile's base specification. +The direct EVM and Solana profiles use the payer and recipient +identifier formats defined by {{I-D.evm-charge}} and +{{I-D.solana-charge}}. CAIP-2 {{CAIP-2}} network identifiers and CAIP-10 +{{CAIP-10}} account identifiers are used normatively in the Gateway +Transfer profile, where one charge can route across chain families and a +chain-native identifier alone would be ambiguous, and in the Stacks +profile, which has no base MPP chain method to inherit from. The receipt +`network` field is a single, deliberate cross-profile identifier +described in {{receipt-schema}}. + ## EVM Profile {#evm-profile} The EVM profile inherits {{I-D.evm-charge}}. The following restrictions @@ -327,16 +341,27 @@ Servers SHOULD verify that `tokenProgram` equals the owner program of the mint account returned by Solana RPC. A mismatch MUST cause credential rejection. -Solana credentials for this profile MUST use `payload.type="transaction"`. -Unlike the base Solana charge profile, this profile also requires a -detached challenge-binding signature. The transaction signature alone -prevents network replay, but it does not prove that the transaction was -created for this exact `usdc` challenge. +Solana credentials for this profile MUST use `payload.type="transaction"` +and inherit base Solana pull-mode verification from +{{I-D.solana-charge}}. The credential echoes the challenge, and the +server binds settlement to it through challenge consumption and +transaction-signature replay protection as defined by +{{I-D.solana-charge}}. + +Deployments that need third-party verifier or facilitator proof MAY +define a stricter challenge-bound authorization profile in a later +version. ## Stacks Profile {#stacks-profile} -Stacks uses SIP-005 {{SIP-005}} consensus-serialized transactions and -SIP-018 {{SIP-018}} challenge binding. +Stacks uses SIP-005 {{SIP-005}} consensus-serialized transactions. + +Unlike EVM and Solana, Stacks has no base MPP chain method to inherit +from, so this document defines server-broadcast transaction +verification directly. The server verifies the SIP-010 transfer, its +post-condition, and the origin signature, then binds settlement to the +challenge through challenge consumption and transaction-id replay +protection. | Field | Type | Required | Description | | --- | --- | --- | --- | @@ -350,27 +375,27 @@ SIP-018 {{SIP-018}} challenge binding. | `feePayer` | boolean | OPTIONAL | Whether the server sponsors fees using Stacks sponsored transaction authorization. | | `feePayerAddress` | string | CONDITIONAL | Required when `feePayer=true`; absent otherwise. | -The Stacks USDCx mainnet token identity at publication time is: +The USDCx on Stacks mainnet token identity at publication time is: ```text SP120SBRBQJ00MCWS7TM5R8WJNTTKD5K0HFRC2CNE.usdcx::usdcx-token ``` -The Stacks USDCx testnet token identity used by the examples is: +The USDCx on Stacks testnet token identity used by the examples is: ```text ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usdcx::usdcx-token ``` -Servers MUST verify the advertised token tuple against a -Circle-recognized USDCx registry or an explicit implementation -allowlist. Until a public registry is available, a v00 allowlist entry -MUST include `network`, `chainId`, `contractAddress`, `contractName`, -`assetName`, `decimals`, and the xReserve control surface used for -issuance and redemption checks. A token identifier that appears only in -a partner-published registry MUST NOT be accepted if it contradicts the -Circle-recognized registry or allowlist. The parsed -`methodDetails.stacks` tuple MUST match `request.currency`. +Servers MUST verify the advertised token tuple against the Circle +xReserve registry or an explicit implementation allowlist. Until a +public registry is available, a v00 allowlist entry MUST include +`network`, `chainId`, `contractAddress`, `contractName`, `assetName`, +`decimals`, and the xReserve control surface used for issuance and +redemption checks. A token identifier that appears only in a +partner-published registry MUST NOT be accepted if it contradicts the +Circle xReserve registry or allowlist. The parsed `methodDetails.stacks` +tuple MUST match `request.currency`. ## Gateway Transfer Profile {#gateway-transfer-profile} @@ -450,20 +475,20 @@ challenge-bound `salt`. In v00, the salt binding is: ```text keccak256(UTF-8 bytes of JCS({ - "id": , + "id": "CHALLENGE_ID", "method": "usdc", - "realm": , + "realm": "CHALLENGE_REALM", "intent": "charge", "type": "gateway", - "requestHash": , - "sourceNetwork": , - "destinationNetwork": , - "sourceDepositor": , - "sourceSigner": , - "recipient": , - "destinationRecipient": , - "amount": , - "maxFee": + "requestHash": "REQUEST_HASH", + "sourceNetwork": "SELECTED_SOURCE_NETWORK", + "destinationNetwork": "DESTINATION_NETWORK", + "sourceDepositor": "SOURCE_DEPOSITOR_ACCOUNT", + "sourceSigner": "SOURCE_SIGNER_ACCOUNT", + "recipient": "REQUEST_RECIPIENT", + "destinationRecipient": "DESTINATION_RECIPIENT_ACCOUNT", + "amount": "TRANSFER_SPEC_VALUE", + "maxFee": "GATEWAY_AUTHORIZATION_FEE_CAP" })) ``` @@ -504,11 +529,11 @@ was accepted. # Credential Schema -EVM credentials inherit {{I-D.evm-charge}} authorization payloads, with -the nonce derivation below. Solana credentials inherit -{{I-D.solana-charge}} pull-mode transaction payloads and add the required -challenge-binding object defined in this section. Stacks and Gateway -Transfer define their profile-specific payloads below. +EVM credentials use {{I-D.evm-charge}} authorization payloads, except +for the nonce derivation, which this profile overrides as defined +below. Solana credentials inherit {{I-D.solana-charge}} pull-mode +transaction payloads. Stacks and Gateway Transfer define their +profile-specific payloads below. For EVM authorization credentials, the EIP-3009 fields are carried directly in `payload` as defined by {{I-D.evm-charge}}. `payload.nonce` @@ -517,23 +542,31 @@ representing exactly 32 bytes. The nonce MUST equal: ```text keccak256(UTF-8 bytes of JCS({ - "id": , + "id": "CHALLENGE_ID", "method": "usdc", - "realm": , + "realm": "CHALLENGE_REALM", "intent": "charge", - "requestHash": + "requestHash": "REQUEST_HASH" })) ``` `requestHash` is `keccak256` of the UTF-8 bytes of the exact JCS-canonicalized request JSON before base64url encoding. -This is stricter than the base EVM charge profile. The base EVM profile +This nonce derivation overrides, and does not inherit, the base EVM +charge profile's derivation in {{I-D.evm-charge}}. The base EVM profile binds EIP-3009 authorization nonces to `challenge.id` and `challenge.realm`. The `usdc` profile also binds the method, intent, and request hash, so an authorization cannot move between `evm`, `usdc`, `charge`, another intent, or a different request with the same amount and -recipient. +recipient. Because the derivation differs, a generic `method="evm"` +verifier computes a different expected nonce and will reject these +credentials; `usdc` EVM credentials are therefore not interchangeable +with `method="evm"` credentials. + +When present, the EVM credential `source` follows {{I-D.evm-charge}} and +is OPTIONAL; the RECOMMENDED form is +`did:pkh:eip155::
`. For Solana transaction credentials, the credential object contains: @@ -541,7 +574,7 @@ For Solana transaction credentials, the credential object contains: | --- | --- | --- | --- | | `challenge` | object | REQUIRED | Echo of the server challenge. | | `payload` | object | REQUIRED | Solana payment payload. | -| `source` | string | REQUIRED | CAIP-10 account ID using `solana::`. | +| `source` | string | OPTIONAL | Payer account, per {{I-D.solana-charge}}. MAY be a base58 public key or a DID (RECOMMENDED `did:pkh:solana::`). | The Solana `payload.type` MUST be `transaction`. The payload carries: @@ -549,37 +582,6 @@ The Solana `payload.type` MUST be `transaction`. The payload carries: | --- | --- | --- | --- | | `type` | string | REQUIRED | MUST be `transaction`. | | `transaction` | string | REQUIRED | Base64-encoded serialized Solana transaction. | -| `challengeBinding` | object | REQUIRED | Detached challenge-binding signature. | - -The `challengeBinding` object contains: - -| Field | Type | Required | Description | -| --- | --- | --- | --- | -| `type` | string | REQUIRED | MUST be `challenge-binding-v00`. | -| `signature` | string | REQUIRED | Base64-encoded 64-byte Ed25519 signature. | - -The client MUST sign the following JCS-canonicalized binding message: - -```text -{ - "type": "challenge-binding-v00", - "method": "usdc", - "challenge": , - "source": , - "transactionBytesDigest": -} -``` - -The signing input is: - -```text -SHA-256("CIRCLE::USDC::ChallengeBindingV00") || -UTF-8(JCS(binding_message)) -``` - -The signature MUST verify under the same Ed25519 public key that -authorizes the Solana token transfer. `transactionBytesDigest` is used -only for challenge binding. It is not the Solana transaction signature. For Stacks, the credential object contains: @@ -599,56 +601,16 @@ The Stacks `payload.type` MUST be `transaction`. The payload carries: | --- | --- | --- | --- | | `type` | string | REQUIRED | MUST be `transaction`. | | `transaction` | string | REQUIRED | Base64-encoded Stacks consensus-serialized transaction. | -| `challengeBinding` | object | REQUIRED | SIP-018 challenge-binding signature. | | `transactionFormat` | string | OPTIONAL | MUST be `stacks_transaction_v1` when present. | -| `signatures` | array | OPTIONAL | MUST be absent for Stacks. | The Stacks transaction MUST call the SIP-010 `transfer` function with `amount`, `sender`, `recipient`, and optional memo arguments. It MUST include a post-condition that pins a `SentEq` transfer of `request.amount` for the advertised USDCx asset. -`payload.challengeBinding.type` MUST be `challenge-binding-v00`. -`payload.challengeBinding.signature` MUST be a base64-encoded 65-byte -recoverable secp256k1 signature in `r || s || v` order, with `v` in -`{0x00, 0x01}`. This document defines the SIP-018 domain tuple for the -challenge-binding message. The `USDCPaymentAuth` name is a -draft-specific domain label, not a token name or a Circle registry -identifier. - -```text -domain = (tuple - (name "USDCPaymentAuth") - (version "usdc-charge-v00") - (chain-id uCHAIN_ID)) - -message: - binding = JCS-canonicalized challenge binding message -``` - -`CHAIN_ID` is `methodDetails.stacks.chainId` parsed as a base-10 integer -and encoded as a SIP-018 Clarity `uint`. It MUST NOT be encoded as a -`string-ascii` or `string-utf8` value. - -The challenge binding message is the following JCS-canonicalized JSON -object: - -```text -{ - "type": "challenge-binding-v00", - "method": "usdc", - "challenge": , - "source": "", - "transactionBytesDigest": "" -} -``` - -The recovered public key MUST derive to the principal in `source`. - -When present, `transactionFormat` MUST be -`stacks_transaction_v1`. `signatures` MUST be absent for Stacks -because origin and sponsor signatures are embedded in the transaction -auth field. +When present, `transactionFormat` MUST be `stacks_transaction_v1`. For +Stacks, origin and sponsor signatures are carried in the transaction +auth field, not in a separate credential field. ## Gateway Transfer Credential @@ -712,9 +674,8 @@ verification: exists. 10. Verify replay protection for the selected credential type. -For Stacks USDCx, the applicable controls include the -Circle-recognized xReserve controls and any partner-chain token -controls required by local policy. +For USDCx on Stacks, the applicable controls include the Circle xReserve +controls and any partner-chain token controls required by local policy. For EVM, servers then apply {{I-D.evm-charge}} verification with the USDC restrictions in {{evm-profile}}. Servers MUST verify @@ -729,44 +690,33 @@ case-sensitive string equality. For Solana, servers then apply {{I-D.solana-charge}} verification with the USDC restrictions in {{solana-profile}}. Servers MUST verify -`methodDetails.type = "solana"`. Servers MUST also verify that -`payload.challengeBinding.type = "challenge-binding-v00"` and that -`payload.challengeBinding.signature` authenticates the challenge-binding -binding message under the same source key that authorizes the token -transfer. If `methodDetails.solana.feePayer=true`, the transaction MUST -set `methodDetails.solana.feePayerKey` as fee payer and the only -missing required signature MUST be the server fee-payer signature. The -server MUST reject any -Solana credential whose transaction bytes or challenge-binding -signature have already been consumed. The server MUST reject stale -transactions by verifying that the transaction uses a currently valid -recent blockhash, or a durable nonce whose `(nonceAccount, nonceValue)` -has not already been admitted by this deployment. +`methodDetails.type = "solana"`. If `methodDetails.solana.feePayer=true`, +the transaction MUST set `methodDetails.solana.feePayerKey` as fee payer +and the only missing required signature MUST be the server fee-payer +signature. The server MUST reject any Solana credential whose +transaction bytes have already been consumed. The server MUST reject +stale transactions by verifying that the transaction uses a currently +valid recent blockhash. USDC Solana verification is intentionally narrower than the generic Solana transaction profile. Servers MUST reject transactions with instructions outside the allowed set for this profile: SPL Token transfer instructions for the advertised mint, associated-token-account setup for the advertised recipient and mint when needed, bounded -Compute Budget instructions, optional Memo instructions, and durable -nonce advancement when a durable nonce is used. Token-2022, delegate -authority, and multisig authority flows are out of scope in v00. +Compute Budget instructions, and optional Memo instructions. Token-2022, +delegate authority, and multisig authority flows are out of scope in +v00. Servers MUST verify that the source token account is owned by the -source account, that the recipient token account is the associated +transfer authority, that the recipient token account is the associated token account for `request.recipient` and `request.currency` unless an equivalent explicit token account is allowed by local policy, and that the token transfer amount equals `request.amount`. An equivalent explicit token account MUST be initialized for `request.currency` and owned by `request.recipient`. -For durable nonce transactions, servers MUST verify that the -transaction contains the expected `AdvanceNonceAccount` instruction, -that the nonce account is accepted by local policy, that the nonce -authority signed, and that the stored nonce value matches the -transaction's recent blockhash. When `feePayer=true`, the server MUST -simulate the final transaction after adding the fee-payer signature and -MUST reject transactions whose compute units, account writes, or fee -exposure exceed local policy. +When `feePayer=true`, the server MUST simulate the final transaction +after adding the fee-payer signature and MUST reject transactions whose +compute units, account writes, or fee exposure exceed local policy. For Stacks, servers MUST verify: @@ -778,8 +728,9 @@ For Stacks, servers MUST verify: 5. `request.currency` equals `.::` using the parsed values in `methodDetails.stacks`. -6. `(contractAddress, contractName, assetName)` matches a - Circle-recognized USDCx SIP-010 token for the selected chain. +6. `(contractAddress, contractName, assetName)` matches a USDCx SIP-010 + token in the Circle xReserve registry or an explicit implementation + allowlist for the selected chain. 7. `payload.transaction` decodes as a SIP-005 consensus-serialized transaction. 8. The transaction version byte matches `methodDetails.stacks.network` @@ -806,12 +757,9 @@ For Stacks, servers MUST verify: `principal = source-principal`, `asset_info = (contractAddress, contractName, assetName)`, `condition_code = SentEq`, and `amount = request.amount`. -19. The SIP-018 challenge-binding signature authenticates the - challenge-binding message defined in this document and recovers to - the principal in `source`. -20. The origin auth nonce is fresh under the server's local Stacks +19. The origin auth nonce is fresh under the server's local Stacks nonce policy. -21. The transaction reaches the server's local Stacks confirmation +20. The transaction reaches the server's local Stacks confirmation threshold. For Gateway Transfer, servers MUST verify: @@ -901,7 +849,7 @@ profile's settlement has completed. If the server wants admission before onchain settlement, it MUST use a separate deferred-settlement method or intent outside this document. -# Receipt Schema +# Receipt Schema {#receipt-schema} Upon successful settlement, servers MUST return a `Payment-Receipt` header per {{I-D.httpauth-payment}}. The decoded receipt payload @@ -920,7 +868,7 @@ verification field. | `reference` | string | Final settlement reference. | | `status` | string | MUST be `success` only after the selected profile has completed settlement. | | `timestamp` | string | RFC3339 settlement time. | -| `network` | string | Settlement network identifier. | +| `network` | string | CAIP-2 {{CAIP-2}} settlement network identifier. | | `externalId` | string | OPTIONAL. Echo of `request.externalId`. | For direct EVM, `reference` is the EVM transaction hash. For direct @@ -948,15 +896,23 @@ use Circle Gateway TransferSpec lookup {{CIRCLE-GATEWAY-TRANSFER-SPEC}} to inspect the settled TransferSpec after acceptance or settlement. +The receipt uses a single CAIP-2 {{CAIP-2}} `network` field for every +profile. This is a deliberate, method-wide settlement locator and +intentionally differs from the base EVM charge receipt, which uses a +numeric `chainId`, and the base Solana charge receipt, which has no +network field. A `method="usdc"` receipt consumer reads `network` for +every profile instead of branching on the profile type. + # Security Considerations ## Supported USDC Asset Forms This profile defines which USDC asset forms can satisfy `method="usdc"` in v00. Servers MUST verify that the selected token matches a native -USDC deployment published by Circle, or, for Stacks, a Circle-recognized -USDCx deployment. Other bridged, wrapped, or synthetic USDC-like assets -need a separate profile or explicit method details. +USDC deployment published by Circle, or, for Stacks, a USDCx token +listed in the Circle xReserve registry or an explicit implementation +allowlist. Other bridged, wrapped, or synthetic USDC-like assets need a +separate profile or explicit method details. Gateway Wallet deposits remain subject to Circle Gateway recovery and withdrawal rules. Gateway supports delayed trustless withdrawal from @@ -966,9 +922,9 @@ Transfer idempotently retryable. ## Blocklist and Pause Controls -Native USDC and Stacks USDCx have different control surfaces. Native +Native USDC and USDCx on Stacks have different control surfaces. Native USDC has token-level pause and blocklist controls on the selected -chain. For Stacks USDCx, xReserve {{CIRCLE-XRESERVE}} source-chain +chain. For USDCx on Stacks, xReserve {{CIRCLE-XRESERVE}} source-chain controls gate deposits and withdrawals, but those controls do not freeze same-chain partner token transfers by themselves. Same-chain USDCx transfers depend on the partner-chain token controls. @@ -976,9 +932,9 @@ transfers depend on the partner-chain token controls. Servers MUST apply every control surface that is relevant to the selected profile before accepting a credential. For native USDC, a payer or recipient present in the applicable token blocklist MUST -cause rejection. For Stacks USDCx, servers MUST check the -Circle-recognized xReserve control surface and the partner-chain token -control surface required by local policy. Cached control data MUST +cause rejection. For USDCx on Stacks, servers MUST check the +Circle xReserve control surface and the partner-chain token control +surface required by local policy. Cached control data MUST have an explicit freshness bound. ## Replay Protection @@ -995,19 +951,20 @@ The nonce already commits to `method`, `intent`, `challenge.id`, `challenge.realm`, and `requestHash`, so this key protects both onchain replay and cross-context replay. -For Solana transaction credentials, the persistent replay key MUST -include `(network, transactionSignature)` after broadcast or -confirmation. Servers MUST also keep a replay key for the detached -challenge-binding material, `(network, transactionBytesDigest, -challengeBinding.signature)`, so the same signed transaction bytes -cannot be admitted twice before or around broadcast. For durable nonce -transactions, servers MUST also key replay protection on `(network, -nonceAccount, nonceValue)`. - -For Stacks transaction credentials, the replay key MUST include both -`(stacks:, transactionBytesDigest)` before or around -broadcast, the transaction id after broadcast, and -`(stacks:, , )`. +For Solana transaction credentials, replay protection follows +{{I-D.solana-charge}}. Servers MUST maintain consumed transaction +signatures and atomically consume the selected challenge before +returning a successful receipt. Servers that admit pull-mode +transactions before broadcast SHOULD also deduplicate on `(network, +transactionBytesDigest)` to avoid concurrent admission of the same +serialized transaction. + +For Stacks transaction credentials, the replay key MUST include the +transaction id after broadcast and `(stacks:CHAIN_ID, +ORIGIN_PRINCIPAL, ORIGIN_NONCE)`. Servers that admit transactions before +broadcast SHOULD also deduplicate on `(stacks:CHAIN_ID, +transactionBytesDigest)` to avoid concurrent admission of the same +serialized transaction. For Gateway Transfer credentials, replay protection MUST cover the selected challenge and the challenge-bound Gateway salt before the @@ -1118,7 +1075,7 @@ The client returns an EIP-3009 authorization credential: "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDM2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJkZXNjcmlwdGlvbiI6IkFyYyBUZXN0bmV0IFVTREMgY2hhcmdlIiwiZXh0ZXJuYWxJZCI6Imludm9pY2UtZXZtLTAwMSIsIm1ldGhvZERldGFpbHMiOnsiZXZtIjp7ImNoYWluSWQiOjUwNDIwMDIsImNyZWRlbnRpYWxUeXBlcyI6WyJhdXRob3JpemF0aW9uIl0sImRlY2ltYWxzIjo2fSwidHlwZSI6ImV2bSJ9LCJyZWNpcGllbnQiOiIweGMwNDE5M0M1MGNEMkU2YTFDNzk1OTNlNDYzNjQ0OTZGZTVmY2Q5YjYifQ", "expires": "2026-04-01T12:05:00Z" }, - "source": "eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "source": "did:pkh:eip155:5042002:0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "payload": { "type": "authorization", "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", @@ -1137,7 +1094,7 @@ receipt, and returns: ```http HTTP/1.1 200 OK -Payment-Receipt: +Payment-Receipt: BASE64URL_JCS_RECEIPT ``` The decoded receipt payload is: @@ -1190,14 +1147,10 @@ The client returns the Solana transaction credential defined by "request": "eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiI0ek1NQzlzcnQ1Umk1WDE0R0FnWGhhSGlpM0duUEFFRVJZUEpnWkpEbmNEVSIsImRlc2NyaXB0aW9uIjoiU29sYW5hIGRldm5ldCBVU0RDIGNoYXJnZSIsImV4dGVybmFsSWQiOiJpbnZvaWNlLXNvbC0wMDEiLCJtZXRob2REZXRhaWxzIjp7InNvbGFuYSI6eyJkZWNpbWFscyI6NiwibmV0d29yayI6ImRldm5ldCIsInRva2VuUHJvZ3JhbSI6IlRva2Vua2VnUWZlWnlpTndBSmJOYkdLUEZYQ1d1QnZmOVNzNjIzVlE1REEifSwidHlwZSI6InNvbGFuYSJ9LCJyZWNpcGllbnQiOiJBS25MNE5OZjNER1daSlM2Y1BrbkJ1RUduVnNWNEE0bTV0Z2ViTEhhUlNaOSJ9", "expires": "2026-04-01T12:05:00Z" }, - "source": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy", + "source": "did:pkh:solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy", "payload": { "type": "transaction", - "transaction": "", - "challengeBinding": { - "type": "challenge-binding-v00", - "signature": "" - } + "transaction": "BASE64_SOLANA_TRANSACTION" } } ``` @@ -1211,7 +1164,7 @@ returns: ```http HTTP/1.1 200 OK -Payment-Receipt: +Payment-Receipt: BASE64URL_JCS_RECEIPT ``` The decoded receipt payload is: @@ -1272,11 +1225,7 @@ The client returns a Stacks transaction credential: "source": "stacks:2147483648:ST8H248H248H248H248H248H248H248H26RCPJ4T", "payload": { "type": "transaction", - "transaction": "", - "challengeBinding": { - "type": "challenge-binding-v00", - "signature": "" - }, + "transaction": "BASE64_SIP005_SERIALIZED_TRANSACTION", "transactionFormat": "stacks_transaction_v1" } } @@ -1287,7 +1236,7 @@ returns: ```http HTTP/1.1 200 OK -Payment-Receipt: +Payment-Receipt: BASE64URL_JCS_RECEIPT ``` The decoded receipt payload is: @@ -1408,7 +1357,7 @@ that includes the Gateway-returned `transferSpecHash`: ```http HTTP/1.1 200 OK -Payment-Receipt: +Payment-Receipt: BASE64URL_JCS_RECEIPT ``` The decoded receipt payload is: