Payment URIs (BIP-21, EIP-681, TRX)

How to build QR payloads that any wallet can scan — and when to fall back to a bare address.

When you render a payment QR for a customer — on a checkout page, a receipt, a kiosk — the QR should encode the address and the expected amount. Wallets read the URI and pre-fill the send screen, so customers can't accidentally type the wrong amount.

The platform's own payment page (on the hosted invoice URL and on the dashboard's Test payment) already does this. This guide is for merchants rendering their own UI.

Scheme per network#

Bitcoin — BIP-21#

bitcoin:bc1q...?amount=0.00082145
  • amount is in BTC, as a plain decimal.
  • Any BIP-21 compliant wallet scans and prefills.

Ethereum + L2s — EIP-681#

Native ETH:

ethereum:0xAbc...@1?value=820000000000000
  • value is in wei.
  • The @<chainId> segment identifies mainnet (1), Sepolia (11155111), etc.

ERC-20 tokens (e.g. USDT on Ethereum):

ethereum:0xTokenContract@1/transfer?address=0xMerchant...&uint256=1000000
  • uint256 is the amount in the token's smallest unit. For USDT-6, 1000000 = 1.00 USDT.
  • address is the buyer's destination — your invoice's deposit_address.

Tron — bare address#

TronLink and most Tron wallets don't currently honour a URI scheme for amount pre-fill. Use a bare address QR and show the expected amount next to it so the customer types it manually.

Amount precision matters#

Never compute the amount in base units via floating-point math — you will drift on the last digits. Work from the string representation of crypto_amount and multiply by 10^decimals as integers.

The safest way#

If you're rendering a QR outside the platform's own page:

  1. Fetch the invoice: GET /api/invoices/{id}.
  2. Use deposit_address + crypto_amount + crypto_network as inputs.
  3. Apply the scheme rules above.
  4. Encode as a standard QR — any QR library works; the payload is the URI string.

See also#