Add usdc charge payment method#272
Conversation
|
|
||
| `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 |
There was a problem hiding this comment.
what does this mean? must match methoddetails.profile?
won't the field always match the field?
There was a problem hiding this comment.
Yes, that wording was awkward. The intent was to say that methodDetails.type is the discriminator, and exactly one matching profile object should be present. I updated this to use type instead of profile
|
|
||
| | Field | Type | Required | Description | | ||
| | --- | --- | --- | --- | | ||
| | `profile` | string | REQUIRED | One of `evm`, `solana`, `stacks`, or `gateway`. | |
There was a problem hiding this comment.
not sure i love the name "profile" -- did we consider others like
- kind
- type
There was a problem hiding this comment.
changed it to type
| MUST reject credentials whose `tokenProgram` is not: | ||
|
|
||
| ```text | ||
| TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA |
| | 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. | |
There was a problem hiding this comment.
does CAIP-2 cover networks like solana too?
if so, why not use CAIP-2 globally for all names?
There was a problem hiding this comment.
CAIP-2 does cover Solana. I kept CAIP-2 for Gateway source and destination network fields because Gateway can route across chain families. I did not switch the direct EVM and Solana profiles to CAIP-2 globally because those profiles inherit the identifier formats from the existing EVM and Solana PaymentAuth specs. But I dont have a strong preference
There was a problem hiding this comment.
I see -- this makes sense. i'm ok keeping with this
| | `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. | |
There was a problem hiding this comment.
since this is aleady under gateway, can this just be transfer ?
There was a problem hiding this comment.
Thanks, updated
|
|
||
| ```text | ||
| { | ||
| "type": "challenge-binding-v00", |
There was a problem hiding this comment.
why do we need to do this?
There was a problem hiding this comment.
The Solana transaction signature proves that the payer authorized the transaction bytes, but it does not itself sign the PaymentAuth challenge. The detached binding adds a same-key signature over the exact challenge, source account, and transaction bytes digest. That gives the profile challenge-bound payer authorization similar in spirit to EVM, while keeping the challenge id offchain.
The tradeoff is extra client and SDK complexity. If we want v00 to stay closer to the existing Solana pull-mode model, I’m fine removing the detached binding for Solana and using the base verification semantics. For Stacks, where there is no base method to inherit, we can define the same server-broadcast, verify-terms, consume-transaction-id model without the second signature.
There was a problem hiding this comment.
Decided to drop it for v00. The Solana profile now just inherits base pull-mode verification from solana-charge, no detached binding.
| `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 |
There was a problem hiding this comment.
similar to above -- why is this strictly needed?
There was a problem hiding this comment.
removed for v00. Stacks now defines plain server-broadcast verification: check the SIP-010 transfer, its post-condition, and the origin signature, then bind to the challenge through challenge consumption and transaction-id replay. No second SIP-018 signature
| | `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. | |
There was a problem hiding this comment.
why do we need to add all of these fields to the receipt? are all of these strictly necessary?
this is a large addition to the existing types and would make it harder to write an SDK
There was a problem hiding this comment.
That makes sense. Reduced the receipt shape substantially. This should be easier for SDKs while still preserving enough settlement context for the selected USDC profile.
Spec Preview
|
035ed88 to
fbe60a5
Compare
fbe60a5 to
db2361b
Compare
There was a problem hiding this comment.
Left some final reviews! Overall i'm not fully convinced for the need for CAIP prefixes as all of the methods are scoped to a specific chain as is.
In other methods (solana, evm etc..) we just use the chaindId field to discriminate, as the prefix is implicit in the method (e.g. all solana chain ids are implicitly under solana)
| | --- | --- | --- | --- | | ||
| | `challenge` | object | REQUIRED | Echo of the server challenge. | | ||
| | `payload` | object | REQUIRED | Solana payment payload. | | ||
| | `source` | string | REQUIRED | CAIP-10 account ID using `solana:<genesis-hash-prefix>:<pubkey>`. | |
There was a problem hiding this comment.
source encoding contradicts core/evm/solana.
This draft requires bare CAIP-10 for source across all profiles (here solana:<genesis>:<pubkey>, and eip155:5042002:0x… in the EVM example at line 1121). But:
- core recommends DID format (
source= "Payer identifier (RECOMMENDED: DID format per [W3C-DID])") - evm-charge §5.1:
sourceSHOULD bedid:pkh:eip155:… - solana-charge:
sourceMAY be a base58 pubkey or a DID
So the EVM example "source":"eip155:…" directly contradicts evm-charge's did:pkh:eip155:…. This draft also makes source REQUIRED, whereas all three base specs make it OPTIONAL.
Please either adopt the DID/did:pkh convention used by the rest of the family, or get core/evm/solana updated to bless CAIP-10 — and reconcile the required-vs-optional difference.
There was a problem hiding this comment.
Thanks for the catch. Fixed it. Reconciled per profile, aligning the direct profiles with their base specs:
- EVM: source is now OPTIONAL with did:pkh:eip155:… RECOMMENDED, matching evm-charge §5.1. The line-1121 example now reads did:pkh:eip155:5042002:0x….
- Solana: source OPTIONAL, may be a base58 pubkey or a DID (did:pkh:solana:… recommended), matching solana-charge. Example updated to did:pkh:….
- Gateway Transfer: source stays REQUIRED and bare CAIP-10. It's the source depositor account, and Gateway needs it named to build and verify the burn/transfer authorization, so it can't
be optional here. There's no base method for it to contradict. - Stacks: source stays REQUIRED and CAIP-10 (stacks::). No base MPP method to inherit a DID convention from, and the origin signature recovers to this principal
during verification.
| `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 |
There was a problem hiding this comment.
EVM authorization nonce derivation conflicts with evm-charge
evm-charge §5.3.1 defines nonce = keccak256(abi.encodePacked(challenge.id, challenge.realm)). This draft (line 516) defines nonce = keccak256(JCS({id, method, realm, intent, requestHash})). These are not interchangeable.
The text just above says the EVM profile "inherit[s] {{I-D.evm-charge}} authorization payloads," but a generic evm-charge verifier will compute a different expected nonce and reject these credentials. The stricter binding is reasonable, but please reword to state explicitly that the nonce binding is overridden (not inherited) and that usdc EVM credentials are not interchangeable with method="evm".
There was a problem hiding this comment.
Reworded. It now says the nonce derivation overrides (does not inherit) the base evm-charge derivation
|
|
||
| ## Method Details | ||
|
|
||
| `methodDetails.type` selects the active `usdc` profile. Its value MUST |
There was a problem hiding this comment.
methodDetails shape diverges from evm-charge/solana-charge
Base evm-charge and solana-charge place method fields flat in methodDetails (methodDetails.chainId, methodDetails.network). This draft introduces a methodDetails.type discriminator plus a nested profile object (methodDetails.evm.chainId).
I think this is a sensible design for a method multiplexing four profiles, but it means the EVM/Solana request envelope is not literally inherited — a base-spec methodDetails parser won't work here.
Please call this divergence out explicitly so implementers don't assume drop-in reuse.
There was a problem hiding this comment.
Agreed. There's now a note right after the methodDetails table
| | `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. | |
There was a problem hiding this comment.
Receipt settlement-locator inconsistent with sibling specs
This receipt uses network (CAIP-2 string) for every profile. But evm-charge's receipt uses chainId (number), and solana-charge's receipt has no network field at all. Cross-method receipt consumers will see a different locator field depending on method.
Consider either keeping chainId (number) for the EVM profile to match evm-charge, or explicitly documenting the deliberate switch to a unified CAIP-2 network field and noting it differs from the base EVM/Solana receipts.
There was a problem hiding this comment.
Kept the unified field but documented it. network is now typed CAIP-2 in the table, with a note that it's a deliberate, method-wide settlement locator that differs from the base EVM receipt (numeric chainId) and the base Solana receipt (no network field). A usdc receipt is asset-level, so a consumer reads one locator regardless of profile, and a Gateway destination may not be EVM, so a numeric chainId wouldn't work there anyway.
brendanjryan
left a comment
There was a problem hiding this comment.
LGTM -- thanks for all of the revisions on this!
Summary
This PR adds a
usdccharge method for Payment Auth.The method gives merchants a unified way to advertise USDC acceptance across supported chains and settlement paths. Clients still receive the concrete details they need for the selected path, including how to pay, verify, apply replay protection, and prove settlement.
In this draft, each supported payment path is modeled as a profile. The v00 profiles are direct EVM USDC, direct Solana USDC, Stacks USDCx, and Gateway Transfer.
Motivation
USDC is available across multiple chains and settlement paths. Merchants may want to integrate at the asset level while still choosing the chains and paths they support.
The
usdcmethod provides that common layer. A merchant can advertiseusdconce and indicate the supported profiles. A client with EVM support can pay through the EVM profile. A client with Solana support can pay through the Solana profile. A client using Gateway can fund from an advertised source network while the merchant settles on its chosen destination network.This keeps the merchant integration centered on USDC while giving clients the concrete path details they need for payment and verification.
What This Adds
This draft defines the
usdccharge method and four v00 profiles:It also defines shared USDC behavior for asset identity, profile selection, replay protection, token controls, and receipts.
Notes
USDC is a common payment asset across multiple supported environments. Giving it a method-level home makes discovery and integration clearer for merchants and clients.
Merchants get one method for USDC acceptance. Clients get the profile details needed for the path they support. Servers apply common USDC request and receipt rules, then use profile-specific verification for settlement.
Gateway Transfer extends the same model to cross-chain charges. A seller can choose where it wants to receive USDC, while the payer funds from another supported Gateway network.
This keeps the public method simple:
usdcfor the asset, profiles for the payment path.Verification
make lintmake checkAI Assistance Disclosure
This draft was prepared and reviewed with significant AI assistance. I reviewed the generated content for technical correctness, RFC-style formatting, and the contribution requirements in
CONTRIBUTING.md.