Reference

Working with mojos

Chia amounts are denominated in mojos: 1012 mojos = 1 XCH, 103 mojos = 1 CAT token. The API emits them as decimal strings, not JSON numbers. This page explains why and shows how to handle them in JavaScript, C#, and Python.

Why strings?

JavaScript's Number is an IEEE-754 double. The largest integer it can hold without losing precision is 253 - 1, which is about 9 × 1015. A modest XCH balance — say 10 XCH — is 10 × 1012 = 1013 mojos, which is fine. But aggregate balances and historical totals quickly exceed 253, and once you cross that boundary JSON.parse silently drops the low bits.

To make sure the bytes that reach your client are the bytes the node sent, the API serializes mojo amounts as decimal strings. You decide when to widen them into BigInt, ulong, or Python's arbitrary-precision int.

{
  "walletId": 1,
  "spendableMojos": "12345678901234",
  "confirmedMojos": "12345678901234",
  "unconfirmedMojos": "12345678901234"
}

Field naming: anything ending in Mojos is a decimal string. If a response also has an *Xch sibling, that's a display-friendly decimal string rounded to 12 places — never use it for math.

JavaScript / TypeScript

Use BigInt for arithmetic. Don't pass a string mojo amount through Number() unless you've already checked it fits.

// Parse the API response without losing precision.
const balance = await fetch("/api/v1/admin/wallets/1/balance").then(r => r.json());
const spendable = BigInt(balance.spendableMojos); // bigint

// Mojos → XCH as a decimal string (handles the fractional part).
function mojosToXch(mojos) {
  const value = BigInt(mojos);
  const sign = value < 0n ? "-" : "";
  const absolute = value < 0n ? -value : value;
  const whole = absolute / 1000000000000n;
  const fractional = (absolute % 1000000000000n).toString().padStart(12, "0");
  return `${sign}${whole}.${fractional.replace(/0+$/, "") || "0"}`;
}

// XCH-decimal string → mojos (BigInt). Throws on malformed input.
function xchToMojos(xch) {
  const match = /^(-?)(\d+)(?:\.(\d{1,12}))?$/.exec(xch.trim());
  if (!match) throw new Error(`Bad XCH amount: ${xch}`);
  const [, sign, whole, fractional = ""] = match;
  const padded = fractional.padEnd(12, "0");
  return BigInt(sign + whole + padded);
}

mojosToXch("12345678901234");  // "12.345678901234"
xchToMojos("0.000000000001");  // 1n

C# / .NET

ulong is the natural fit: 0 to ~1.8 × 1019, enough for every plausible balance. For signed math (sweeps, deltas) use long or fall back to BigInteger.

using System.Numerics;

// Read the response with System.Text.Json — strings stay strings.
JsonDocument balance = await client.GetFromJsonAsync<JsonDocument>(
    "api/v1/admin/wallets/1/balance")!;

string spendableMojos = balance.RootElement.GetProperty("spendableMojos").GetString()!;
ulong mojos = ulong.Parse(spendableMojos, CultureInfo.InvariantCulture);

// Mojos → XCH (decimal). decimal has 28-29 significant digits; safe for display.
const decimal MojosPerXch = 1_000_000_000_000m;
decimal xch = mojos / MojosPerXch;

// XCH (decimal) → mojos (ulong). Round only when you mean to.
decimal request = 1.25m;
ulong asMojos = (ulong)(request * MojosPerXch);

// For deltas that might be negative or might exceed ulong, use BigInteger.
BigInteger delta = BigInteger.Parse(spendableMojos) - BigInteger.Parse(other);

If you're modelling DTOs, declare mojo fields as string and parse at the boundary. Don't bind to ulong directly — that defeats the point of sending it as a string.

Python

Python's int is arbitrary precision — no precautions needed. Use decimal.Decimal for display formatting.

from decimal import Decimal, getcontext
import requests

response = requests.get("https://your-host/api/v1/admin/wallets/1/balance", timeout=10)
response.raise_for_status()
balance = response.json()

mojos = int(balance["spendableMojos"])  # arbitrary precision

# Mojos → XCH (Decimal). Use Decimal, not float; float will lose precision.
getcontext().prec = 28
xch = Decimal(mojos) / Decimal(10) ** 12
print(f"{xch:.12f}")  # 12.345678901234

# XCH → mojos. Quantize to 12 places, then convert to int.
amount = Decimal("1.25")
mojos = int((amount * Decimal(10) ** 12).quantize(Decimal("1")))

Common pitfalls

  • Don't call JSON.parse with a reviver that does Number(value) — you'll silently corrupt large amounts.
  • Don't multiply by 1e12 in JavaScript to convert XCH to mojos: 1.1 * 1e12 is 1100000000000.0001 due to float error. Use BigInt after string manipulation.
  • Don't store balances as double / float in any language. The error compounds across additions.
  • Do compare amounts as strings only if you've zero-padded them to a fixed width — otherwise "9" sorts after "100". Parse to a number type first.
An unhandled error has occurred. Reload 🗙