> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Stablecoin Payments

> Accepting USDC and USDT: which of the two integration paths to pick, the exact chain values to send, how the receiving address works in each path, amount and decimal rules, the payment window, what a shopper's completed transfer does and does not mean, fiat settlement, and where a stablecoin order shows up in the Portal.

This page covers stablecoin acceptance through the normal wallet / hosted-cashier
path. The AI-Agent path built on the x402 protocol has its own scope, request
fields and error behaviour, and is documented in
[x402 stablecoin acquiring](/docs/en/developer-docs/use-cases/x402-stablecoin-acquiring).
Exception cases and refunds are in
[Stablecoin Exceptions & Refunds](/docs/en/support/stablecoin-exceptions-faq).

Acceptance is enabled per merchant contract — ask the integration team to
provision the CRYPTO payment product and your chain set before you build.

## How many ways are there to accept stablecoins, and which do I pick?

Two, and they are not variants of one integration — they differ in who the payer
is and in what your server does:

| | Wallet / hosted-cashier path | x402 signature path |
| - | - | - |
| The payer is | A person with a crypto wallet | Typically an AI Agent |
| Documented on | This page | [x402 stablecoin acquiring](/docs/en/developer-docs/use-cases/x402-stablecoin-acquiring) |
| Currencies | `USDC` and `USDT` | `USDC` only |
| Chains | Your contracted chain set | Base only |
| How the payer pays | Transfers to a receiving address | Signs an on-chain authorization |
| Receiving address | Issued per order | A merchant-level address returned by `wallet/inquiry` |
| Payment window | The order's expiry | The timeout in the 402 challenge |
| Refund | `refundAddress` on `order/refund` | Same |

Pick on the payer, not on preference: a human with a wallet cannot pay an x402
challenge, and an Agent has no browser to render a checkout in. If you sell to
both, you integrate both.

## What do I send on order creation to accept stablecoins?

Three things identify a stablecoin order on the wallet path:

| Field | Value |
| - | - |
| `payMethodType` | `CRYPTO` |
| `payMethodName` | `USDC` or `USDT` |
| `payMethodProperties.cryptoChain` | The chain the shopper will pay on — [values below](#what-exactly-do-i-put-in-the-chain-field) |

The method is catalogued under the global region code `GLB` rather than a single
country, so it is not filtered by the shopper's country. Pricing works like any
other cross-currency order: keep `orderCurrency` and `orderAmount` in your
pricing currency (typically `USD`), and set `userCurrency` to `USDC` or `USDT` —
the currency the shopper actually pays in. See
[Pricing currency strategy](/docs/en/developer-docs/use-cases/pricing-currency).

## Do I have to make the shopper pick a chain myself?

Only if you run your own checkout page.

* **Waffo-hosted cashier** — omit `payMethodProperties.cryptoChain`. The
  consultation step returns it as a missing field together with the chain
  options available for the order, and the cashier collects the shopper's choice
  before the order is finalized.
* **Your own checkout page** — present the chain choice yourself and send the
  selected value back in `payMethodProperties.cryptoChain`.

Treat that returned option list as authoritative: it reflects what is available
for your account and that stablecoin, which can be narrower than the
platform-wide list below.

## Can I offer only one chain, so the shopper cannot pick the wrong one?

Yes — that is what the option list is for. The chains offered for an order come
from your account's configuration, so a single-chain configuration produces a
single-chain option list and, on the hosted cashier, no chain selector worth
speaking of. Ask your integration contact for the chain set you want rather than
filtering the list client-side: a client-side filter still leaves the wider set
acceptable on `order/create`.

Note what this does **not** prevent. Narrowing the chains removes the choice of
*which order* the shopper pays; it cannot stop them sending funds on some other
chain from their wallet, which is the `WRONG_CHAIN` case in
[Stablecoin Exceptions & Refunds](/docs/en/support/stablecoin-exceptions-faq).

## What exactly do I put in the chain field?

The chain's **code**, as a string — `BASE`, not `Base`, and not a numeric chain
ID. Matching is case-insensitive, and each chain's short name is accepted as an
alternative to its code, but send the code: it is the value that comes back on
the order and the value the refund path reads.

| Chain | Code to send | Short name also accepted | Address family |
| - | - | - | - |
| Ethereum | `ETHEREUM` | `ETH` | EVM |
| Polygon | `POLYGON` | `MATIC` | EVM |
| Arbitrum One | `ARBITRUM` | `ARB` | EVM |
| Base | `BASE` | `BASE` | EVM |
| Optimism | `OPTIMISM` | `OP` | EVM |
| Avalanche | `AVALANCHE` | `AVAX` | EVM |
| BNB Smart Chain | `BSC` | `BSC` | EVM |
| Solana | `SOLANA` | `SOL` | Solana |
| Tron | `TRON` | `TRX` | Tron |

Testnets are separate codes, usable where your sandbox is configured for them:

| Testnet | Code | Mainnet counterpart |
| - | - | - |
| Ethereum Sepolia | `ETH-SEPOLIA` | `ETHEREUM` |
| Polygon Amoy | `AMOY` | `POLYGON` |
| Arbitrum Sepolia | `ARBITRUM_SEPOLIA` | `ARBITRUM` |
| Base Sepolia | `BASE_SEPOLIA` | `BASE` |
| Optimism Sepolia | `OPTIMISM_SEPOLIA` | `OPTIMISM` |
| Avalanche Fuji | `FUJI` | `AVALANCHE` |
| Solana Testnet | `SOLANA_TEST` | `SOLANA` |

Tron and BNB Smart Chain have no testnet code, so those two cannot be exercised
against a testnet.

Two naming points that come up often:

* **`BSC` is BNB Smart Chain.** The chain's code is `BSC` and its network is BNB
  Smart Chain; "BNB" on its own is the network's token, not a chain code. Send
  `BSC`.
* **Avalanche means the C-Chain.** Only C-Chain (EVM) addresses are valid;
  X-Chain and P-Chain addresses are rejected outright, including on refunds.

The **address family** column is the one to build against: it determines the
address format the chain accepts, which matters most on refunds — see
[Stablecoin Exceptions & Refunds](/docs/en/support/stablecoin-exceptions-faq).

## Is the chain field mandatory? What happens if I leave it out?

It is not mandatory on the request, but the order cannot be paid without a
chain, so leaving it out has one of two outcomes and never a default:

* On the **hosted cashier**, consultation reports it as a missing field with the
  available options, and the shopper's choice supplies it.
* On **your own checkout**, you get the same missing-field response and it is
  your job to collect the choice and re-send.

There is no fallback chain. Do not assume the cheapest or the most popular chain
is picked for you.

## Which stablecoins are supported?

`USDC` and `USDT` on the wallet path; `USDC` only on the x402 path. No other
tokens — no non-stablecoin tokens, no small-cap tokens, and no merchant-issued
tokens.

Anything else sent to a receiving address is not a valid payment: a transfer of
the chain's native token (ETH, for example) or of any other token does not pay
the order, and neither does a transfer on a chain other than the order's.

## Is there a minimum or maximum amount?

Yes, and it is on your agreement like any other payment method, not a special
stablecoin rule. The published
[payment method catalogue](/docs/en/essentials/payment-methods) carries the
catalogue-level range for `USDC` and `USDT`; the range that actually applies to
you is `orderAmountMin` / `orderAmountMax` on your own configuration entry for
the method, readable from `/api/v1/paymethodconfig/inquiry` or from **Service →
Payin** in the Portal.

Check it before you create the order. An amount outside the range is rejected at
order creation with `A0008` — see
[Limits, Currency & Configuration Errors](/docs/en/support/error-codes-and-limits-supplement)
for how that differs from the two account-level limits.

## How many decimal places do stablecoin amounts use?

Two, on every Waffo API field, exactly like a 2-decimal fiat currency — see
[Currency and amount](/docs/en/developer-docs/core-concepts/currency). On-chain the
tokens use 6-decimal atomic units; Waffo handles that conversion, so neither your
requests nor your reports carry atomic units, and a block explorer shows the
amount in a different unit than the one you sent.

The token's on-chain precision therefore does **not** change what you send: the
same `orderAmount` string is correct on every chain, and you never scale it
yourself. The one place atomic units are yours to construct is the x402
signature path, where the challenge and the signature are both in atomic units —
that page spells out the conversion.

## Is the receiving address mine, per order, or fixed?

It depends on the path, and the two answers are genuinely different:

| Path | Address | Can I cache it? |
| - | - | - |
| Wallet / hosted cashier | Issued **per order** | **No** |
| x402 signature path | A **merchant-level** receiving address returned by `wallet/inquiry` | No — fetch it per order and use it verbatim |

On the wallet path an address is bound to one order and released when that order
finishes, which means it can later be issued for a **different** order. That is
the reason the rule is absolute rather than a preference: a transfer to an
address you cached does not pay the order you cached it for, and once the address
has moved on it cannot be attributed to it at all.

Neither path gives you an address of your own to publish, print on an invoice, or
hard-code. In both, the address is Waffo's; Waffo receives on-chain and settles
to you per your contract.

## The shopper paid to a recycled address. What happens?

Nothing good, and this is worth designing against rather than handling. Once an
order's address has been released, a late transfer to it lands in one of the
unmatched cases in
[Stablecoin Exceptions & Refunds](/docs/en/support/stablecoin-exceptions-faq): it does
not pay the original order, and it does not pay whichever order holds the address
now, because the amount and the order will not correspond.

Practical guards: never render a stablecoin address outside the payment page for
that order, never let a shopper "reuse the address from last time", and never
show a QR code from an expired order.

## Does the payment QR code carry the amount?

Do not assume it does. The reliable instruction to a shopper is: **send exactly
the order amount, in the order's token, on the order's chain, before the window
closes** — all four, from the payment page. A wallet that pre-fills the amount is
a convenience, not a guarantee, and an amount the shopper edits produces an
overpay or underpay case rather than a paid order.

## How long does the shopper have to pay?

The order's expiry, which is the same mechanism as every other payment method:
the contract default unless you send `orderExpiredAt` on `order/create`. See
[Payment Timeouts & Finality](/docs/en/support/payment-timeout-and-finality-faq).

Two stablecoin-specific things to plan for:

* The window has to cover **the shopper's action plus the chain's confirmation
  time**, not just the shopper's action. A window that would be generous for a
  wallet redirect can be tight on a chain that confirms slowly.
* A transfer that arrives after the window closes does not pay the order. It
  becomes the `LATE_PAID` case, and there is no way to attach it to the closed
  order afterwards.

## The shopper's wallet says the transfer succeeded — why is the order still processing?

Because the order advances to success only once the transfer meets the
confirmation requirement on-chain, and that wait is a property of the chain, not
of Waffo. A wallet showing "sent" is not the same event as the payment being
final. How long the wait is varies substantially between chains:

* Chains with near-instant deterministic finality (for example Avalanche,
  Polygon) confirm quickest.
* Ethereum mainnet waits for a block-confirmation depth, so it is noticeably
  slower.
* Optimistic-rollup L2s (Arbitrum, Base, Optimism) are slowest: the sequencer
  accepts the transfer in seconds, but confirmation waits for the batch to be
  confirmed on Ethereum.

So show the shopper a "waiting for network confirmation" state, and treat the
payment-result webhook or an order inquiry as the only source of truth for
success — never the wallet's own confirmation screen, and never a block explorer
you checked yourself.

## The order has been processing for a while. Is it still waiting, or has it failed?

Read the order, not the chain. An order in a non-final state is still in play;
the state machine is the same as for every other payment method, so the same
rules apply about which states are final and which are not — see
[Payment lifecycle](/docs/en/developer-docs/core-concepts/payment-lifecycle) and
[Payment Timeouts & Finality](/docs/en/support/payment-timeout-and-finality-faq).

What you must not do is infer failure from the chain being slow, or success from
having found the transfer on a block explorer. If the order has not reached a
final state by its expiry, it closes; if funds arrived off-spec, it becomes one
of the exception cases, which are visible to support with the order id and the
transaction hash.

## Do I receive stablecoins or fiat?

Fiat. The USDC or USDT the shopper pays is converted at payment success through
the `USD/USDC` and `USD/USDT` currency pairs, and everything downstream — your
unsettled balance, settlement batches and reports, and withdrawals — stays in
your settlement currency. You never hold the stablecoin, so you carry no token
price exposure and no on-chain fee exposure.

Because conversion happens **at payment success**, not at order creation, the
rate is the rate at that moment. Stablecoin orders use the same cross-currency
machinery as any other cross-currency order, so the rate mechanics, and whether a
quote can be held, are covered in
[Exchange Rates](/docs/en/support/exchange-rates-faq) and
[Pricing currency strategy](/docs/en/developer-docs/use-cases/pricing-currency)
rather than being special here.

## Where do stablecoin orders show up in the Portal?

In the ordinary transaction list, filtered like anything else. Two filters get
you there:

| Filter | Value |
| - | - |
| Payment method type | `CRYPTO` |
| Payment method | `USDC` or `USDT` |

What the Portal does **not** give you is any on-chain detail: there is no chain
column, no chain filter, no transaction hash, no confirmation count and no
network-fee figure on the transaction list or the transaction detail. The one
place a chain appears is the refund drawer, where it is shown read-only as the
chain the original payment used.

So if your reconciliation needs the chain or the transaction hash per order, do
not plan to get it from a Portal export. On the x402 path the hash is available
on the API — the settlement receipt returned on success carries it, and that page
documents its shape. On the wallet path, raise the order id with support.

## What do I need in order to be enabled for stablecoins?

Two provisioning steps, both arranged with your integration contact: the CRYPTO
payment product on your contract, and your chain set. Until both are in place,
stablecoin order creation is refused as not permitted by your contract (`A0010`).

Enrollment itself follows the normal path, including where you watch its status —
see [Payment Method Enrollment](/docs/en/support/payment-enrollment-faq). Adding a
chain later is the same kind of request as adding a payment method: it is a
configuration change on the Waffo side, not something you can switch on yourself.

## Where do I look next?

* [Stablecoin Exceptions & Refunds](/docs/en/support/stablecoin-exceptions-faq) —
  overpay, underpay, late, wrong chain, wrong token, and what a refund must carry
* [x402 stablecoin acquiring](/docs/en/developer-docs/use-cases/x402-stablecoin-acquiring) —
  the AI-Agent path end to end
* [Payment Timeouts & Finality](/docs/en/support/payment-timeout-and-finality-faq) —
  order expiry and which states are final
* [Reading Your Payment Method Configuration](/docs/en/support/payment-method-config-and-limits-faq) —
  your own amount range and availability for `USDC` / `USDT`
* [Settlement Reports](/docs/en/support/settlement-reports-faq) — how a converted
  stablecoin payment appears in settlement


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.