How It Works
USDT Pay lets an Acquirer accept a customer’s on-chain USDT payment and receive verified settlement in the mode configured at onboarding. The customer pays from their own wallet to a one-time deposit address. The Acquirer does not take custody of customer funds, and the QR carries the chain-native payment URI into the wallet.
The flow starts when the Acquirer creates a payment intent. t-0 asks the Issuer for payment instructions, returns deposit options (one per chain, each with the deposit address, the payment URI and the USDt token contract), verifies the Issuer’s payment report, authorizes the sale, and coordinates settlement. In USDT settlement, the Issuer settles on-chain to the Acquirer’s wallet. In fiat settlement, the Issuer settles USDT to the LP, and the LP pays local fiat to the Acquirer over bank rails.
Participants
- Acquirer — owns the merchant relationship; talks to the POS through its own internal API.
- t-0 Network — routes messages between the Acquirer and the Issuer, keeps the payment-intent ledger, maintains an Order Book of LP-pushed standing quotes (each Acquirer’s quotes come from its single assigned LP) and answers USDt↔fiat quote requests from it, verifies settlement on-chain, and relays the Liquidity Provider’s self-initiated fiat settlement to the Acquirer for confirmation when the Acquirer is configured for fiat settlement. Each Acquirer is mapped to exactly one Issuer at onboarding (and, for fiat settlement, to exactly one LP); t-0 routes
5to that Issuer. - Issuer — creates payment instructions, watches the blockchain for the customer’s payment, reports a deposit’s outcome (authorized or unprocessable), and settles in USDt on-chain. At onboarding the Issuer configures an
acquirerId → settlementWalletmapping per Acquirer it serves; the wallet may belong to the Acquirer (USDt mode) or to that Acquirer’s single LP (fiat mode — each Acquirer is served by exactly one LP, fixed at onboarding). The Issuer identifies the Acquirer from theacquirerIdcarried on5 CreatePaymentInstructionsand uses this mapping to determine thedestinationAddressfor9 SettlementSent. This resolution is independent of any t-0 instruction by design, so t-0’s on-chain destination check on9is a genuine cross-check, not a confirmation of its own input. The Issuer releases deposit addresses on its own afterexpiresAt; there is no Issuer-side expiry callback. - Liquidity Provider (LP) — in fiat-settlement mode, receives the Issuer’s USDt and, on its own initiative, settles the locked fiat amount with the Acquirer over bank rails, reporting it to t-0 through
10 FiatSettlementSent. Each Acquirer is served by exactly one LP, fixed at onboarding; that LP feeds t-0’s Order Book on its own initiative: it pushes standing quotes (1 PublishQuote) and refreshes its pricing by publishing new quotes alongside earlier ones; each standing quote belongs to the LP that published it. The Acquirer’s LP is fixed for all fiat-mode steps of every intent (8, the settlement wallet,10); a referenced quote (4) selects the rate and currency for that intent, not the LP. Currency coverage is not pre-checked at onboarding: a local-fiat currency is serviceable only while the LP has a standing quote for it. Because that LP is the sole source of the Acquirer’s quotes, a currency it is not currently quoting is declined at3(QUOTE_UNAVAILABLE), and the LP’s quote liveness gates all of the Acquirer’s fiat sales — a deliberate MVP simplification with no fallback LP. In the fiat leg the LP is the Acquirer’s payer — it pays over bank rails and knows the Acquirer as a payee, resolving its bank account fromacquirerId— but not its obligor: the Issuer stays liable and the Acquirer reconciles against t-0, never the LP.
The settlement mode — USDt on-chain or fiat — is fixed per Acquirer at onboarding by the Acquirer↔LP association (an associated LP ⇒ fiat, none ⇒ USDt); the Issuer always settles in USDt either way. There are no direct messages between non-adjacent parties: the POS only talks to the Acquirer, the Acquirer only to t-0, and t-0 talks to the Issuer and to the LP. Responses and asynchronous events travel the same chain in reverse. The LP settles fiat with the Acquirer over bank rails — a real-world payout, not an API message; the Acquirer never talks to the LP directly.
End-to-end flow
The diagrams below show the protocol from all participants at once. If you integrate a single role, start with the per-role page instead: Acquirer, Issuer, Liquidity Provider.
The customer payment and authorization mechanics are the same in both settlement modes. The diagrams duplicate those steps so each mode can be read on its own.
USDt settlement
sequenceDiagram
participant ACQ as Acquirer
participant T0 as t-0
participant ISS as Issuer
participant CUS as Customer
participant BC as Chain
ACQ->>T0: CreatePaymentIntent (§4)
T0->>ISS: CreatePaymentInstructions (§5)
ISS-->>T0: depositOptions, expiresAt
T0-->>ACQ: paymentIntentId, depositOptions, expiresAt
CUS->>BC: USDt transfer to chosen depositAddress
BC-->>ISS: tx observed + KYT cleared
ISS->>T0: PaymentReceived (§6, authorized)
T0->>ACQ: PaymentAuthorized (§7)
Note over ISS,BC: ≤15 min batch window
ISS->>BC: settlement transfer — USDt to Acquirer wallet
ISS->>T0: SettlementSent (§9)
T0->>BC: verify on-chain
T0->>ACQ: SettlementCompleted (§13)
Unprocessable deposit
sequenceDiagram
participant ACQ as Acquirer
participant T0 as t-0
participant ISS as Issuer
participant CUS as Customer
participant BC as Chain
CUS->>BC: USDt transfer to chosen depositAddress
BC-->>ISS: tx observed + screening fails
ISS->>T0: PaymentReceived (§6, unprocessable + disposition)
T0->>ACQ: PaymentFailed (§16, disposition)
Fiat settlement via LP
sequenceDiagram
participant ACQ as Acquirer
participant T0 as t-0
participant ISS as Issuer
participant LP as Liquidity Provider
participant CUS as Customer
participant BC as Chain
LP->>T0: PublishQuote (§1)
Note over T0,LP: standing quotes, refreshed by publishing — not per-sale
ACQ->>T0: GetPaymentQuote (§3)
T0-->>ACQ: quoteId, settlementAmount, fxRate, expiresAt (filled from the Order Book)
ACQ->>T0: CreatePaymentIntent (§4, quoteId + amount)
T0->>ISS: CreatePaymentInstructions (§5)
ISS-->>T0: depositOptions, expiresAt
T0-->>ACQ: paymentIntentId, depositOptions, expiresAt
CUS->>BC: USDt transfer to chosen depositAddress
BC-->>ISS: tx observed + KYT cleared
ISS->>T0: PaymentReceived (§6, authorized)
T0->>ACQ: PaymentAuthorized (§7)
T0->>LP: ExecuteQuote (§8, executionId → Accepted or Rejected)
Note over ISS,LP: Issuer→LP USDt reimbursement — decoupled, never gates the intent
ISS->>BC: USDt to LP wallet (≤15 min batch)
ISS->>T0: SettlementSent (§9)
T0->>BC: verify on-chain → books the Issuer→LP ledger
Note over ACQ,LP: Acquirer-facing leg — this is what settles the intent (may precede §9: fronting)
LP->>ACQ: fiat settlement via bank rails
LP->>T0: FiatSettlementSent (§10, self-initiated)
T0->>ACQ: SettlementInitiated (§11, fiatSettlementId)
ACQ->>T0: SettlementReceived (§12) → intent SETTLED
In fiat mode there is no 13: the Acquirer’s own 12 SettlementReceived is the terminal event. 13 SettlementCompleted is sent only in USDt mode (see below).
If the QR expires before the customer pays, t-0 — the expiry authority — transitions the intent to EXPIRED at expiresAt on its own clock and sends 15 to the Acquirer. The Issuer releases its deposit addresses on its own after expiresAt.
Once 7 PaymentAuthorized has fired, the Issuer has accepted the payment and is obligated to settle it. A chain reorg on the customer’s payment, a rejected settlement batch, or a stuck reconciliation is an on-chain or operational matter for the Issuer to resolve — never a reversal for the Acquirer or the merchant. The Issuer’s obligation is unchanged whether funds reach the Acquirer as USDt on-chain or as fiat through an LP; the LP delivers the fiat, but the Issuer — not the LP — remains the Acquirer’s obligor.
When the Acquirer is configured for fiat settlement (e.g., COP), the USDt↔fiat rate comes from t-0’s Order Book of standing quotes. Each LP pushes quotes into the book on its own initiative through 1 PublishQuote — a locked fxRate and a validity window — and refreshes its pricing by publishing new quotes alongside earlier ones. Before a sale, the Acquirer obtains a quote through 3 GetPaymentQuote; t-0 answers from the book with no per-sale LP interaction. A standing quote is multi-consumable: the same quoteId may be returned to any number of quote requests and referenced by any number of payment intents while the quote stands. The Acquirer passes its amount — denominated in USDt (settlement) or in fiat (local; refused for an on-chain-settled Acquirer) — into 4 CreatePaymentIntent, and may reference a quoteId or omit it (t-0 resolves the freshest standing quote for the payment’s currency). Acceptance locks the quote’s rate for that intent, and the headroom rule on 4 guarantees the quote outlives the intent’s QR window.
Fiat settlement runs as two independent legs. In the normal order the Issuer settles the LP first and the LP then settles the Acquirer, but the contract ties the intent to the Acquirer-facing leg alone, so neither order is required:
- Issuer→LP reimbursement leg — decoupled; it never gates the intent. The Issuer only ever sends USDt: at settlement it sends USDt on-chain to the LP’s wallet (not the Acquirer’s) and reports it through
9 SettlementSent, which t-0 verifies on-chain and books as an Issuer→LP obligation in its ledger, settling the Issuer’s USDt to the LP for the executions it covers. This is independent of the Acquirer’s intent: if9is delayed or never verifies, the Issuer simply stays owing the LP in the ledger — the intent is unaffected, and the LP carries the Issuer-default risk on any fiat it has already delivered. One9to the shared LP wallet may cover executions across several Acquirers that LP serves (see §9). - Acquirer-facing leg — this is what settles the intent. The quote’s validity (≈5 min) is shorter than the settlement batch cadence, so the moment the payment is authorized t-0 executes the quote with the LP through
8 ExecuteQuote, minting a per-saleexecutionId— bound 1:1 to the intent inside t-0. The LP returns Accepted or Rejected: Accepted creates the LP’s firm per-sale obligation at the locked rate; Rejected leaves the intent authorized for manual handling (the Acquirer still holds an authorized payment, but the fiat leg does not proceed through the standing-quote path). On the obligations it took at8, the LP settles the locked fiat to the Acquirer over bank rails on its own initiative and reports it through10 FiatSettlementSent. t-0 maps the LP’ssettledExecutionIdsto their intents, mints afiatSettlementId, and pre-notifies the Acquirer through11 SettlementInitiated, naming thebankTransferRefto expect and the intents it clears. Because the bank-rails leg is not on-chain-verifiable, t-0 treats the intent asSETTLEDonly once the Acquirer confirms receipt through12 SettlementReceivedwith the matching amount. The Acquirer’s12is the terminal event — there is no13in fiat mode. Because this leg does not depend on9, the LP may also settle the Acquirer before the Issuer’s USDt arrives (fronting it on the8obligations plus its private funding arrangement with the Issuer); the independence rule resolves that corner case without special handling.
Payment intent states (t-0 ledger)
stateDiagram-v2
[*] --> OPEN
OPEN --> EXPIRED: expiresAt reached (t-0 clock)
OPEN --> AUTHORIZED: 6 authorized
OPEN --> FAILED: 6 unprocessable
AUTHORIZED --> SETTLEMENT_PENDING: 7 sent, USDt mode
AUTHORIZED --> AWAITING_FIAT: 7 sent, fiat mode
SETTLEMENT_PENDING --> SETTLED: 9 verified
AWAITING_FIAT --> SETTLED: 12 received
SETTLED --> [*]
EXPIRED --> [*]
FAILED --> [*]
The intent’s lifecycle tracks only the Acquirer-facing settlement. In fiat mode the Issuer→LP 9 SettlementSent is booked on a separate settlement record (the Issuer→LP reimbursement ledger) and does not appear in this state machine — it never moves the intent.
OPEN — intent created, payment instructions live, customer payment not yet observed.
AUTHORIZED — t-0 has accepted 6 PaymentReceived and is transmitting 7 PaymentAuthorized. On transmit of 7 (not on the Acquirer’s acknowledgment, consistent with Obligation binding) the intent moves to SETTLEMENT_PENDING (USDt mode) or AWAITING_FIAT (fiat mode); in fiat mode t-0 also fires 8 ExecuteQuote here.
SETTLEMENT_PENDING (USDt mode only) — t-0 is waiting for the Issuer’s 9 SettlementSent to the Acquirer’s wallet. A verified 9 → SETTLED, then 13.
AWAITING_FIAT (fiat mode only) — the LP owes the Acquirer fiat at the locked rate. t-0 awaits the LP’s self-initiated 10 FiatSettlementSent, relays it as 11 SettlementInitiated, and closes the leg on the Acquirer’s 12 SettlementReceived. The intent → SETTLED when 12 arrives with the matching amount. The Issuer→LP 9 is independent and never enters here.
SETTLED — settlement reached the Acquirer (terminal). USDt mode: 9 SettlementSent passed on-chain verification. Fiat mode: the Acquirer confirmed fiat receipt via 12 (the Issuer→LP 9 may still be outstanding — that is an Issuer↔LP ledger matter, not the intent’s).
FAILED — the Issuer will not process the deposit (terminal). Entered when 6 PaymentReceived reports an unprocessable outcome; the funds follow the disposition (returned to sender or retained by the Issuer), and t-0 sends 16 PaymentFailed to the Acquirer.
EXPIRED — reservation lapsed with no valid payment (terminal). Entered on t-0’s clock at expiresAt; the Issuer releases deposit addresses on its own.
Conventions
- Data types used:
string,number,amount(decimal monetary),timestamp(absolute, UTC),boolean,list of T,object { ... },oneof { ... }(tagged union — exactly one variant present; the variant key is the discriminator). - Amount derivation.
settlementAmountis derived only by t-0, at its single derivation point4 CreatePaymentIntent(and computed the same way as the indicative value on3):settlementAmount = round(localAmount / fxRate, 2 dp, half-up)— two decimal places, round-half-up. This holds in both settlement modes. The customer pays exactly thissettlementAmount, and6 PaymentReceivedrequires strict equality against it. - Synchronous endpoints return a response in the same call. Asynchronous endpoints are pushed events; the sender retries until the receiver acknowledges.
- Obligation binding — all obligations in this protocol attach at the moment t-0 transmits the relevant message, except
8 ExecuteQuote, where the LP’s obligation is created only onAccepted(aRejectedresult leaves the intent authorized for manual handling). LP or Issuer acknowledgment timing on other messages does not affect when an obligation is created or takes effect. - Decline model — synchronous endpoints (
1,3,4,5) can decline in the same call, and each synchronous response is aresultthat isoneof { success { … } | failure { reason } }— thefailurevariant carries a canonicalreasoncode (mirroring the proto; there is no separatedeclinedboolean). The asynchronous8 ExecuteQuotealso returns aresult(AcceptedorRejected);Rejectedleaves the intent authorized for manual handling. Before the Issuer reports payment, the Acquirer learns of failure through15 PaymentExpired; after6reportsunprocessable, the Acquirer learns through16 PaymentFailed. After7 PaymentAuthorizedhas fired the Issuer has accepted the payment and is obligated to settle it; any on-chain retry or reconciliation the Issuer performs (directly in USDt, or via an LP in fiat mode) is not exposed to the Acquirer. - Chain names are a fixed canonical set maintained by t-0 — full, case-sensitive names, identical across every endpoint carrying a
chainfield. (The protoBLOCKCHAIN_*enum labels and the SQLpay_blockchainlabels are internal representations mapped to these wire names.) Live at launch:Ethereum,BSC.TRONcarries a value but is not accepted until it goes live in a later phase. Announced as upcoming:Polygon,Arbitrum,Avalanche— the Issuer offers nodepositOptionson these, so a customer cannot pay on them through the flow until they go live. Extensible as the upcoming chains are enabled.
Idempotency keys, retry identity, the accepted/rejected contract, and reliability timers are covered in Idempotency and Reliability.
Shared identifiers
| Identifier | Minted by | Notes |
|---|---|---|
quoteRef | LP | Idempotency key on 1 — the LP’s identifier for one pushed standing quote; echoed as data on 8 ExecuteQuote. Unique per LP. |
quoteId | t-0 | t-0’s identifier for one standing quote in its Order Book, minted on 1. Returned by 3; referenced by any number of 4s while the quote stands; carried as data on 8. Fiat-configured Acquirers only. |
paymentRef | Acquirer | Echoed data on 7, 15, 16. Not an idempotency key. |
idempotencyKey | Acquirer | Idempotency key on 4. Unique per Acquirer. |
paymentIntentId | t-0 | Idempotency key on 5, 6, 7, 15, 16. Not exposed to the LP — see executionId. |
executionId | t-0 | t-0’s identifier for one execution of a standing quote — minted at authorization, bound 1:1 to the authorized payment intent. The LP’s idempotency key on 8 and its obligation handle; the unit the LP reports settlements in (settledExecutionIds on 10). Not exposed to the Acquirer. Fiat mode only. |
settlementRef | Issuer | Idempotency key on 9. The Issuer’s id for its own USDt settlement. Unique per Issuer. |
settlementId | t-0 | t-0’s id for one on-chain USDt settlement (9), minted at the verified 9. USDt mode: the Issuer→Acquirer settlement and the idempotency key on 13. Fiat mode: the Issuer→LP reimbursement record, booked to the Issuer↔LP ledger and not surfaced to the Acquirer. |
fiatSettlementId | t-0 | t-0’s id for one bank-rails fiat settlement (10/11/12), minted when t-0 accepts the LP’s 10 (the fiat_settlement record); the idempotency key on 11. Fiat mode only. |
bankTransferRef | LP | LP-generated reference on the bank-rails transfer and the idempotency key on the LP’s self-initiated 10. t-0 relays it as data on 11 so the Acquirer knows which transfer to expect; the Acquirer reads it off its statement and echoes it on 12, where the idempotency key is the pair (lpId, bankTransferRef); t-0 matches 10 ↔ 12 on that pair. Unique per LP only — lpId disambiguates across LPs. Fiat mode only. |
acquirerId | t-0 | t-0’s stable identifier for the Acquirer. Included in 5 so the Issuer can resolve its acquirerId → settlementWallet mapping, and in 8 so the LP can resolve the Acquirer’s registered bank destination. |
lpId | t-0 | t-0’s stable identifier for the Liquidity Provider. Carried as data on 11 so the Acquirer knows which LP’s transfer to expect; echoed on 12, where it scopes bankTransferRef (the key is the pair). Fiat mode only. |
11 SettlementInitiated (fiat mode) is keyed on t-0’s fiatSettlementId; 13 SettlementCompleted (USDt mode only) is keyed on t-0’s settlementId; 16 PaymentFailed is keyed on paymentIntentId. Neither 11 nor 13 uses a foreign role’s id as its key — the lpId and bankTransferRef carried on 11 ride as data, not as the key. In USDt mode the Acquirer correlates the settlement through 13’s settledPaymentIntentIds and onChainTxHash; in fiat mode through 11’s fiatSettlementId + bankTransferRef, which it confirms with its own 12.
Endpoint map
The flow numbers used above map to RPCs across the three role services. Request and response fields for each live in the generated API Reference.
| # | Endpoint | Hosted by | Called by | Reference |
|---|---|---|---|---|
| 1 | PublishQuote | t-0 | LP | Liquidity Provider |
| 3 | GetPaymentQuote | t-0 | Acquirer | Acquirer |
| 4 | CreatePaymentIntent | t-0 | Acquirer | Acquirer |
| 5 | CreatePaymentInstructions | Issuer | t-0 | Issuer |
| 6 | PaymentReceived | t-0 | Issuer | Issuer |
| 7 | PaymentAuthorized | Acquirer | t-0 | Acquirer |
| 8 | ExecuteQuote | LP | t-0 | Liquidity Provider |
| 9 | SettlementSent | t-0 | Issuer | Issuer |
| 10 | FiatSettlementSent | t-0 | LP | Liquidity Provider |
| 11 | SettlementInitiated | Acquirer | t-0 | Acquirer |
| 12 | SettlementReceived | t-0 | Acquirer | Acquirer |
| 13 | SettlementCompleted | Acquirer | t-0 | Acquirer |
| 15 | PaymentExpired | Acquirer | t-0 | Acquirer |
| 16 | PaymentFailed | Acquirer | t-0 | Acquirer |
The contract removed 2 WithdrawQuote (LP → t-0) and 14 PaymentExpired (Issuer → t-0); their numbers stay retired.
Out of scope (MVP)
Refunds, chargebacks, disputes, manual cancel/void, card channel, onboarding endpoints, query/status/report endpoints, bilateral credit-line management, Acquirer→merchant payout, the LP’s internal pricing and risk mechanics and bank-rail mechanics, Acquirer-side quote amendment or cancellation (standing quotes are immutable; the LP refreshes by publishing new quotes), and the POS↔Acquirer API.