---
title: "Why is my stablecoin payout off by a cent? Minor units, token decimals, and rounding"
seoTitle: "Why is my stablecoin payout off by a cent?"
description: "One-cent gaps between your math and a stablecoin payout come from token decimals, whole-unit rounding, and recomputed fees. How to reconcile each."
date: "2026-10-16"
updated: "2026-10-16"
category: "payments"
author: "BlindPay Team"
faq:
  - q: "Why does my calculated payout differ from receiver_amount by one cent?"
    a: "Usually because you recomputed the amount instead of reading it. Multiplying by blindpay_quotation and rounding with a different rule (half up, half even, or truncation) can land one minor unit away from the API's figure. Store sender_amount and receiver_amount exactly as the quote returns them and reconcile against those integers."
  - q: "How many decimals does USDC use on-chain?"
    a: "BlindPay's payout quote example shows it: a sender_amount of 10000 (100.00 USDC in minor units) comes back with a contract amount of 100000000 for the ERC-20 approve. That is 100 times 10 to the sixth, so the on-chain amount has six decimals while the API amount has two."
  - q: "Why did my MXN, COP, or ARS deposit amount get rounded down?"
    a: "Those rails settle in whole currency units. On a sender-side payin quote the request must be a multiple of 100 minor units, and on a receiver-side quote BlindPay truncates the computed sender amount down to a whole peso before returning it. Show the payer the sender_amount from the response, not your own conversion."
  - q: "Should I store payment amounts as floats?"
    a: "No. Store minor units as integers, or exact decimals as strings, and convert for display only. In JavaScript, 0.1 plus 0.2 evaluates to 0.30000000000000004, which is exactly the kind of drift that breaks a reconciliation report at month end."
  - q: "What is the token_amount field on wallet deposits?"
    a: "Since October 15, 2026, the managed wallet deposits list returns amount in cents plus token_amount, the exact on-chain amount as a decimal string. Use amount for ledger matching against payouts and payins, and keep token_amount when you need the precise on-chain value, for example to match a block explorer."
---

A stablecoin payout ends up one cent away from your own math for three reasons: the token has more decimals on-chain than the API amount, some currencies settle in whole units and get rounded down, and recomputed rates or fees use a different rounding rule than the provider. Read amounts from the quote, store integers, and the gaps disappear.

This is a reconciliation guide. The basics of why payment APIs take integers live in [stablecoin API quotes explained](/resources/more/stablecoin-api-quotes-explained). Here the question is narrower: your ledger says 6,633.47 and the bank statement says 6,633.46. Which one is wrong?

**Key takeaways**

- API amounts are minor units (two decimals for USD, BRL, MXN, EUR). USDC on-chain amounts carry six decimals.
- MXN, COP, and ARS payins settle in whole currency units, so BlindPay rounds the payer-side amount down to the peso.
- Don't recompute `receiver_amount` from the rate. Store what the quote returns.
- Different rounding rules (half up, half even, truncation) differ by exactly one minor unit on the same input.
- Exact on-chain values arrive as decimal strings. Keep them as strings until you need math.

## Where do one-cent differences come from?

Almost always from one of three conversions. Each has a different fix.

| Source | What happens | Typical gap | Fix |
| --- | --- | --- | --- |
| Token decimals | API amount has 2 decimals, on-chain amount has 6 | Sub-cent dust | Convert with integer math, never floats |
| Whole-unit rails | MXN, COP, ARS payer amounts rounded down to a whole unit | Up to 0.99 of a peso | Show and store the quoted `sender_amount` |
| Recomputed math | Your rate times amount uses another rounding rule | 1 minor unit | Read `receiver_amount`, don't derive it |
| Float storage | `0.1 + 0.2` drifts in binary floating point | Random, grows with volume | Integers or decimal strings only |

Most teams hit the last two first, because they show up in spreadsheets.

## How do API minor units map to on-chain token units?

Two decimals in the API, six on the chain. BlindPay's `request_amount`, `sender_amount`, and `receiver_amount` are integers in minor units: `10000` is 100.00 ([payout quotes](/docs/payout-quotes)).

The on-chain side is different. The same docs show a payout quote with `sender_amount: 10000` returning a `contract.amount` of `"100000000"` for the ERC-20 `approve`. That's 100 × 1,000,000. So for USDC:

- API minor units × 10,000 = on-chain base units.
- On-chain base units ÷ 1,000,000 = USDC.

Two things to notice. `contract.amount` is a string, on purpose: on-chain integers can exceed what a JavaScript `Number` holds exactly, which is why MDN documents `BigInt` for arbitrary-size integers ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)). And you never need to compute it yourself. Pass the payload from the quote straight to your wallet library. [Executing a payout quote on EVM, Solana, and Stellar](/resources/more/execute-payout-quote-evm-solana-stellar) covers what each chain signs.

Wallet balances can hold dust below one cent (0.000001 USDC is a valid amount). Your ledger works in cents. Expect a few base units of difference between a raw wallet balance and the sum of your recorded payouts, and don't chase it.

## Why do MXN, COP, and ARS amounts get rounded down?

Because those rails settle in whole currency units. On a payin quote where `currency_type` is `sender` and the currency is MXN, COP, or ARS, `request_amount` must be a multiple of 100. `10050` fails with `request_amount_must_be_a_whole_currency_unit` ([payin quotes](/docs/payin-quotes)).

The receiver side is where the cent goes missing. If you quote by the stablecoin amount (`currency_type: receiver`), BlindPay computes the peso amount and rounds it down to the nearest 100 minor units before returning `sender_amount`. The docs give the reason: a fractional amount would be truncated on arrival at the bank, and the deposit could come back as an invalid payment amount.

Illustrative example. You want a customer in Mexico to fund 500.00 USDC.

| Line | Value |
| --- | --- |
| Requested stablecoin | 500.00 USDC (`request_amount: 50000`, `currency_type: receiver`) |
| Illustrative rate | 18.4567 MXN per USDC |
| Exact conversion | 500 × 18.4567 = 9,228.35 MXN |
| `sender_amount` returned | 922800 (9,228.00 MXN, rounded down) |
| Difference | 0.35 MXN |

If your checkout shows "9,228.35 MXN" from your own math, the payer sends 35 centavos more than the quote expects, or your UI and the bank instruction disagree. Show `sender_amount` from the response. Read `receiver_amount` too, rather than assuming exactly 500.00 lands.

## Why does recomputing the rate give a different number?

Because rounding rules disagree at the half cent. The rate in a quote has more decimals than the currency, so every conversion produces a fraction that someone has to round.

Illustrative example. A payout of 250.00 USDC at a `blindpay_quotation` of 5.3713 BRL per USDC:

| Method | Calculation | Result |
| --- | --- | --- |
| Exact product | 250.00 × 5.3713 | 1,342.825 BRL |
| Round half up | .825 → .83 | 1,342.83 |
| Round half even (banker's) | .825 → .82 | 1,342.82 |
| Truncate | .825 → .82 | 1,342.82 |

Three reasonable methods, two answers. Python's `decimal` module defaults to `ROUND_HALF_EVEN` and offers eight rounding modes ([Python docs](https://docs.python.org/3/library/decimal.html)), and spreadsheets often default to half up. Neither is wrong. They just aren't the provider's.

The quote already did this math. `receiver_amount` is the number the bank account receives, after the rate, the `flat_fee`, any `partner_fee_amount`, and the `billing_fee_amount` when billing charges are on. Reconcile against it. If you want to measure the price, compare `commercial_quotation` with `blindpay_quotation` as rates, not by re-deriving amounts; [how to read an FX quote](/resources/more/how-to-read-an-fx-quote) shows how.

## How should you store amounts so reconciliation never drifts?

Integers for minor units, decimal strings for exact values, floats never. JavaScript shows the problem in one line: `0.1 + 0.2` is `0.30000000000000004` ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON)). Sum ten thousand payouts in floats and the error stops being theoretical.

A storage pattern that holds up:

1. **Store the quote's integers.** `sender_amount`, `receiver_amount`, `flat_fee`, `partner_fee_amount` as integer columns, next to the `quote_id`.
2. **Store the currency with its exponent.** ISO 4217 publishes the minor unit for every currency code ([SIX, ISO 4217 maintenance agency](https://www.six-group.com/en/products-services/financial-information/data-standards.html)). USD, BRL, MXN, COP, ARS, EUR, and GBP all use two.
3. **Store rates as decimal strings.** `"5.3713"`, not `5.3713`. Multiply with a decimal library when you need to.
4. **Keep exact on-chain values as strings.** Since October 15, 2026, the managed wallet deposits list returns `amount` in cents and the exact `token_amount` as a decimal string ([changelog](/changelog/2026-10-15-wallet-deposits-list)). Match the ledger on `amount`, keep `token_amount` for explorer lookups.
5. **Format at the edge.** Convert to "R$ 1.342,82" only in the UI.

[Webhooks and reconciliation](/resources/more/stablecoin-api-webhooks-reconciliation) covers matching these rows to payout events.

## How do you reconcile a payout against the bank credit?

Match on the quote, not on your math. The `receiver_amount` on the quote is the expected credit in minor units. Your own record links the payout id (`po_...`) to the `quote_id` you executed, and webhooks tell you when it completes.

A reconciliation check that catches real problems and ignores rounding noise:

- Expected credit = quote `receiver_amount`. Exact match required.
- Expected debit = quote `sender_amount`. Exact match required against your ledger.
- Wallet balance vs. ledger: allow a tolerance of a few on-chain base units for dust.
- Any one-cent mismatch on an exact field: check whether someone recomputed instead of reading.

If an exact field is off by more than rounding, that's a real exception. Check the bank details, the payout status, and the tracking fields before anything else. [Payout quality metrics](/resources/more/payout-quality-metrics-slippage-expiry-rate) shows how to log quoted vs. delivered amounts per payout so these show up on a dashboard instead of in a month-end spreadsheet.

## What to do next

Pull last month's payouts, compare your stored amounts with each quote's `sender_amount` and `receiver_amount`, and count the one-cent gaps. Every gap points at a place your code recomputes. The [payout quotes docs](/docs/payout-quotes) list each amount field.
