DuckyPayDocs

Fiat pricing

Price an invoice in fiat and let DuckyPay size the stablecoin amount at a live rate — your customer sees ฿350, the chain settles the exact tokens.

Supported currencies#

This is the complete list — any other currency code is rejected with a 400 before anything is quoted:

currencyCurrencyRate source
THBThai bahtBitkub (THB markets), CoinGecko fallback
USDUS dollarCoinGecko
unsupported currency → 400
// fiat: { currency: "EUR", amount: "25" } →
{
  "success": false,
  "data": null,
  "error": "Invalid enum value. Expected 'THB' | 'USD', received 'EUR'"
}

Pricing in another currency today? Convert to USD on your side and pass fiat: { currency: 'USD', … }, or price directly in token units with amount — see Amounts: token or fiat.

How to price in fiat#

Send fiat instead of amount when creating a charge. amount and fiat are mutually exclusive — exactly one is required.

create.ts
const charge = await duckypay.charges.create({
  chainId: 137,
  fiat: { currency: 'THB', amount: '350' },  // ฿350 — a decimal string
  // no `amount` — you provide EITHER fiat OR a token amount, never both
});

The response carries the token amount DuckyPay sized, plus the full quote breakdown in data.fiat so nothing about the price is a black box:

response (fiat fields)
{
  "amount": "10120000",          // ← authoritative: what the buyer actually pays (token, smallest unit)
  "fiat": {
    "currency": "THB",
    "amount": "350",             // your original price
    "marketRate": 35.10,         // fiat per 1 whole token at quote time
    "buffer": 0.50,              // subtracted from the rate in YOUR favor
    "effectiveRate": 34.60,      // marketRate − buffer → used to size the payment
    "tokenAmount": "10120000",   // = ceil(350 / 34.60 × 10^6)
    "source": "bitkub",
    "asOf": 1789550000
  }
}

The token amount is authoritative — it is what the buyer pays, what the contract splits, and what invoice.paid reports. fiat is display metadata; the hosted checkout uses it to show the buyer “฿350” alongside the exact token amount and rate.

How the token amount is computed#

  1. Fetch the live market rate for the invoice’s token in your currency (fiat per 1 whole token).
  2. Subtract a per-currency buffer from the rate (THB default: ฿0.50): effectiveRate = marketRate − buffer. The buffer works in your favor — it cushions rate movement between quote and payment.
  3. Size the payment, rounding up to the token’s smallest unit: tokenAmount = ceil(fiatAmount ÷ effectiveRate × 10^decimals).

Both the buffer and the round-up lean the same way: when the quote is slightly off, you receive marginally more than the fiat price — never less.

Where the rates come from#

  • THB Bitkub’s public THB markets first (the reference price Thai users actually trade at), CoinGecko as fallback.
  • USD — CoinGecko.
  • If a provider is briefly unreachable, the last known good rate is reused for a short window. If no trustworthy rate exists, charge creation fails with 503 — DuckyPay never invents a rate to keep an invoice minting.

Rules & edge cases#

  • Stablecoins only. Fiat pricing requires an ERC-20 stablecoin token — combining fiat with token: "NATIVE" is rejected.
  • Multi-asset charges quote per token. On a both/transfer charge with several accepted tokens, each token gets its own fiat-sized amount, so the buyer pays the equivalent price whichever asset they pick.
  • The quote is frozen at creation. A charge’s token amount never changes after minting; a stale unpaid invoice simply expires (expiry) and you mint a fresh one at the current rate.