Money & currency

NGN only, integer kobo, no floats, ever. How amounts ride the wire, and the one rule that keeps you from being off by a kobo at scale.

Money in Acute follows one rule, no exceptions: every amount is an integer number of kobo. Not naira. Not a decimal. Not a float. An integer count of the smallest unit. Get this right once and you never reconcile a phantom kobo again.

One naira is 100 kobo. Acute represents every amount as a whole number of kobo:

NairaKobo (what you send)
₦1.00100
₦5,000.00500000
₦300,000.0030000000
₦0.5050
text
amount_in_kobo = naira × 100

So baseAmount: 500000 is ₦5,000.00, and the tier1 balance cap of 30000000 is ₦300,000.00. When you display money, divide by 100 and format to two decimals at the edge. Never carry naira-with-decimals through your logic.

Amounts cross the wire as ordinary JSON integers: a JavaScript number. No string-encoding, no BigInt serialization games:

amounts are just numbers
json
{
  "balance": 500000,
  "baseAmount": 500000,
  "fee": 7500
}

Kobo amounts sit comfortably inside JavaScript's safe-integer range (Number.MAX_SAFE_INTEGER ≈ 9 × 10¹⁵ kobo, far past any naira balance you'll hold), so a number is exact here. Treat them as integers in your own code: int / bigint / decimal columns, never float.

Floats are how you lose money

0.1 + 0.2 !== 0.3 in every language with IEEE-754 floats. Do that a million times across a million transactions and you're off by real money. Acute never uses floats for amounts, and neither should you. Integer kobo end to end: the only safe way to count money.

NGN is the only currency today. Wallets are created with currency: "NGN", balances report "NGN", and amounts are always kobo. The currency field exists on the wire so the model is ready for more, but for now: naira, in kobo.

Convert at the boundaries: parse user input to kobo on the way in, format kobo to naira on the way out, and keep everything in between as integers.

/** ₦ string → integer kobo. Reject anything with sub-kobo precision. */
export function toKobo(naira: number): number {
  const kobo = Math.round(naira * 100);
  if (Math.abs(naira * 100 - kobo) > 1e-6) {
    throw new Error("amount has sub-kobo precision");
  }
  return kobo;
}
 
/** integer kobo → ₦ display string. Format at the edge only. */
export function formatNaira(kobo: number): string {
  return `${(kobo / 100).toLocaleString("en-NG", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  })}`;
}
 
formatNaira(500000); // "₦5,000.00"
toKobo(5000);        // 500000