Concepts

Currencies and chains

The chains and tokens QBitFlow takes, live from its catalog, and how to handle amounts exactly.

23 currencies · 3 chainsLive from the API · read hourly

Ethereum

8 coins

Base

8 coins

Solana

7 coins

The catalog

A currency is a chain's own coin or a token on a chain. The catalog above is the API's: it grows with nothing to change on your side. A currency's numeric id is the same everywhere.

FieldWhat it is
idwhat availableCurrencyIds (in requests) and mainCurrencyId hold
codeBASE:USDC: chain and symbol, what requests take
symbol, nameUSDC, "USD Coin"
chainthe chain it lives on, BASE
decimalsthe smallest unit's decimal places (6 for USDC and 18 for its chain's own coin, in the contract's examples)
addressthe token's contract (or mint); absent on a chain's own coin
mainCurrencyIda token's chain coin; absent on a chain's own coin
testa test network's currency
List currencies

currencies-list.sh

# A public route, limited to 60 requests a minute per IP: cache the list at start-up.# ?test=true lists the test networks' currencies instead.curl https://api.qbitflow.app/v2/currencies# 200: a page of Currency ({ items, hasMore: false })
// A public route limited to 60 requests a minute per IP: cache the list at start-up. One bounded// list (an array): the live currencies; test: true, the test networks'; main: true, the chains'// native coins only.const currencies = await client.currencies.list();for (const { id, code, name, chain, decimals } of currencies) {	console.log(`${id}: ${code} (${name} on ${chain}), ${decimals} decimals`);}
# A public route limited to 60 requests a minute: cache the list at start-up. One bounded# list: the live currencies; test=True the test networks'; main=True the native coins only.currencies = client.currencies.list()for currency in currencies:    print(currency.id, currency.code, currency.name, currency.chain, currency.decimals)
// A public route limited to 60 requests a minute per IP: cache the list at start-up.// One bounded list (a slice): the live currencies; Test: the test networks'; Main: the// chains' native coins only.currencies, err := client.Currencies.List(ctx, nil)if err != nil {	log.Fatal(err)}for _, c := range currencies {	fmt.Printf("%d: %s (%s on %s), %d decimals\n", c.ID, c.Code, c.Name, c.Chain, c.Decimals)}
// One bounded list: the live currencies; test: the test networks'; main: the chains' native coins only.foreach ($client->currencies->list() as $currency) {	echo "{$currency->id}: {$currency->code} ({$currency->name} on {$currency->chain}), {$currency->decimals} decimals\n";}

GET /currencies lists them (?test=true for test networks), GET /currencies/:id resolves one: no credential needed. They're rate-limited per client IP and rarely change: cache them (rate limits).

Which currencies a checkout takes

What the space's wallets accept (one per chain, with its enabled tokens): GET /wallets/accepted-currencies. Your customer pays in one of them and you receive that currency: never a conversion. Subscriptions take tokens only. Wallets that accept nothing can't open one (409 merchant_not_ready).

How amounts work

From the contract · payment.completed

amountMinUnits"10000000"10.000000
currency.decimals÷ 106
= the token amount10 USDCamount: $10.00

amountMinUnits, allowance.amountMinUnits, allowance.maxPerPeriodMinUnits, paidMinUnits: an integer in the smallest unit, as a decimal string. decimals places the point: 10000000 with 6 decimals is 10 USDC.

Avoid

Parse it as a float

A JavaScript number loses digits on an 18-decimal coin.

Do

Keep a string or a big integer

Exact to the last unit, as the contract counts it.
Display amounts exactly
// Token amounts come both ways, as decimal strings: payment.amount ('4.99') and// payment.amountMinUnits ('4990000', with payment.currency.decimals). Convert between them with// string arithmetic, never floats.const shown = formatAmount('4990000', 6); // '4.99' (USDC has 6 decimals)const minUnits = parseAmount('4.99', 6); // '4990000'console.log(`${shown} USDC = ${minUnits} minimal units`);
# Amounts in a token's smallest unit are decimal strings: never convert them through a float.shown = qbitflow.format_amount("4990000", 6)  # "4.99" (6 decimals, like USDC)min_units = qbitflow.parse_amount("4.99", 6)  # "4990000"print(shown, min_units)
// Exact decimal arithmetic on strings, never floats. USDC has 6 decimals.display, err := qbitflow.FormatAmount("4990000", 6)if err != nil {	log.Fatal(err)}fmt.Println(display, "USDC") // 4.99 USDC minUnits, err := qbitflow.ParseAmount("4.99", 6) // more than 6 decimals is a *ValidationErrorif err != nil {	log.Fatal(err)}fmt.Println(minUnits) // 4990000 // From a payment: payment.Currency.FormatAmount(payment.AmountMinUnits)
// Amounts in a token's smallest unit are decimal strings: convert them exactly, never through floats.$display = \QBitFlow\Support\Amount::format('4990000', 6); // "4.99" (USDC has 6 decimals)$minUnits = \QBitFlow\Support\Amount::parse('4.99', 6);     // "4990000"echo "{$display} USDC = {$minUnits} min units\n";
  • USD beside it. amount is the token amount (an exact decimal string) and amountUsd its USD value. QBitFlow converts your USD prices when the customer picks a currency, rounding down to the smallest unit.
  • Amounts fit the chain. A price too large for a token, or under one unit, is refused at the intent: another token may hold it.
  • A fresh price is required. Without one, or with a stablecoin off its peg, QBitFlow prepares nothing (503 network_unavailable): try later.
  • USD in requests. priceUsd and amountUsd are decimal strings ("4.99"), never numbers. A fee line's amountUsd: above 0, at most $1,000,000, 2 decimals.