# State commitments

Once an hour the sequencer commits to every account's balances in a Merkle sum tree whose root also carries the total USDC the network owes, and any wallet can check its own balance is counted in it.

## What is committed

At most once an hour, and only after new blocks, the sequencer takes every account (system accounts and wallets), sorts them by name and builds a Merkle **sum** tree over them (`commitState` in `lib/chain/integrity.ts`). The tree has the same shape as the [log](/integrity/proofs#the-log); the difference is that every node also carries the USDC below it, so the root commits to the total.

Each leaf is one account:

| Field | Type | Notes |
|---|---|---|
| `n` | string | The account name. |
| `nonce` | integer | Its nonce. |
| `usdc` | string | USDC balance, base units. |
| `usdt` | string | Older USDT balance, base units. |
| `mlx` | string | Unlocked MLX, base units. |
| `locked` | string | MLX still locked at the time of the commitment, base units. |

What the network owes an account is `usdc + usdt` (both are dollars 1:1).

## Hashing rules

`lib/chain/tlog.ts`, with every sum written as 16 bytes, big-endian:

| | Sum | Hash |
|---|---|---|
| Leaf | `usdc + usdt` | `SHA-256(0x00 ‖ sum ‖ leaf JSON)` |
| Inner node | left sum + right sum | `SHA-256(0x01 ‖ left hash ‖ right hash ‖ sum)` |

The leaf JSON is the leaf's fields with keys in alphabetical order and no whitespace, the numbers as strings except `nonce`:

```json title="Leaf JSON"
{"locked":"0","mlx":"1499995840385794","n":"treasury.mlx","nonce":0,"usdc":"1677000000","usdt":"0"}
```

The root's sum is the commitment's `usdc`: the total USDC (including older USDT) every account on MLX Network holds.

## The signed statement

The sequencer signs each commitment with its Ed25519 key, over this text (`commitStatement`), one item per line, each line ending in `\n`:

```text title="Statement for the commitment at height 397"
mlx-state/v1
mlx-1
height 397
state e61dd19f5dffb8ba9b465106faa6a7a2928cfd2bbabd7ef7af13d39d39c5448d
usdc 11285277511
accounts 68
log 398 cb97aebd459faf519f4d967d5b6a98fafff2df0f6a681f4bf89d34560b253d7e
```

`height` is the block the balances are after, `accounts` the number of leaves, and `log` the log size and root at that block. That last line ties the balances to the history: a [consistency proof](/integrity/proofs#consistency-proofs) from that size to any later checkpoint shows they belong to the same log.

## Check one wallet

```endpoint try
GET /api/log/state/treasury.mlx
```

The account's leaf in a state commitment and the sibling path from the leaf to the root.

| Parameter | Type | Notes |
|---|---|---|
| `name` | path | The account, for example `luca.mlx`. |
| `height` | integer | Optional. The commitment made after this block (heights are listed in `history` of `GET /api/log`); default: the newest. |

```json title="Response"
{
  "commit": {
    "height": 397,
    "root": "e61dd19f5dffb8ba9b465106faa6a7a2928cfd2bbabd7ef7af13d39d39c5448d",
    "usdc": "11285277511",
    "accounts": 68,
    "size": 398,
    "logRoot": "cb97aebd459faf519f4d967d5b6a98fafff2df0f6a681f4bf89d34560b253d7e",
    "ts": 1793115783839,
    "sig": "78fc7df1a95d357b45aecb195a95c216600cd95f48391f9a604a05e1e89c516c0da09883bd70e5d787d97d11b3eceb11d7a5a9f94fea4b6f0cf39eb869d04704"
  },
  "leaf": { "n": "treasury.mlx", "nonce": 0, "usdc": "1677000000", "usdt": "0", "mlx": "1499995840385794", "locked": "0" },
  "index": 57,
  "count": 68,
  "path": [
    { "h": "6967c23af794d67ebc045bed87802e3d27c748b55023d396ef07601b235d6d0e", "s": "2000000" },
    { "h": "31b98b772c654e3358947d062d2f7a40745c04e12b56345df9eba4c279e448e0", "s": "0" },
    { "h": "ee827ef10ea9a0bd3d39a8b278d612b426b5b13ceb805f7ebde6e4ffa0cf0ce8", "s": "0" },
    { "h": "1cb09f7643ecdf3c129e22a924b7f255d90011ed30bd653264e646b6233d2458", "s": "116929" },
    { "h": "dd2fbd52953500bb53c8dc1927a00d6e5c4e65ca32c4339ec55933609a51cd47", "s": "50000000" },
    { "h": "b4907a105ef58c08c92a08707be44ae138a61c2d62923a9f3b66fd29f55d9dcf", "s": "9296000000" },
    { "h": "940fe1cee0286e8df785f55070738a31051196d8bc78a00977d24b340a0f3789", "s": "260160582" }
  ]
}
```

| Field | Type | Notes |
|---|---|---|
| `commit` | object | The commitment: `height`, `root` (hex), `usdc` (total, base units), `accounts`, `size` and `logRoot` (the log at that block), `ts`, `sig` (Ed25519 over the statement above). |
| `leaf` | object | The account's leaf. |
| `index` | integer | The leaf's position (accounts sorted by name). |
| `count` | integer | The number of leaves. |
| `path` | array | The siblings from the leaf up to the root, each with its hash `h` and its USDC sum `s`. |

Other answers, all `200` with `commit`:

- `"absent": true`: no account of that name was in the commitment.
- `"missing": true`: the commitment exists, but the leaves behind it are no longer kept (they are kept for a few days), so no path can be given.

`404` `No state commitment yet` when there is no commitment, or none at the `height` asked for.

## Verify a balance in JavaScript

```js title="state.mjs"
import { ed25519 } from "@noble/curves/ed25519";
import { sha256 } from "@noble/hashes/sha2";
import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes } from "@noble/hashes/utils";

// 16-byte big-endian sum
const u128 = (v) => {
  const out = new Uint8Array(16);
  for (let i = 15; i >= 0; i--) { out[i] = Number(v & 0xffn); v >>= 8n; }
  return out;
};
// The leaf's JSON, keys in this exact order
const leafJson = (l) =>
  `{"locked":${JSON.stringify(l.locked)},"mlx":${JSON.stringify(l.mlx)},"n":${JSON.stringify(l.n)},` +
  `"nonce":${l.nonce},"usdc":${JSON.stringify(l.usdc)},"usdt":${JSON.stringify(l.usdt)}}`;
const splitPoint = (n) => { let k = 1; while (k * 2 < n) k *= 2; return k; };

function verifyBalance({ commit, leaf, index, count, path }) {
  let sum = BigInt(leaf.usdc) + BigInt(leaf.usdt);
  let hash = sha256(concatBytes(Uint8Array.of(0x00), u128(sum), utf8ToBytes(leafJson(leaf))));
  // which side the sibling is on at each level, found by replaying the tree's splits from the top
  const right = [];
  for (let lo = 0, hi = count, m = index; hi - lo > 1; ) {
    const k = splitPoint(hi - lo);
    if (m < k) { right.push(true); hi = lo + k; } else { right.push(false); lo += k; m -= k; }
  }
  if (right.length !== path.length) return false;
  right.reverse(); // the path runs bottom up
  path.forEach((step, i) => {
    const s = BigInt(step.s);
    if (s < 0n) throw new Error("negative sum");
    sum += s;
    const [l, r] = right[i] ? [hash, hexToBytes(step.h)] : [hexToBytes(step.h), hash];
    hash = sha256(concatBytes(Uint8Array.of(0x01), l, r, u128(sum)));
  });
  return bytesToHex(hash) === commit.root && sum.toString() === commit.usdc;
}

const commitStatement = (c, chain) =>
  `mlx-state/v1\n${chain}\nheight ${c.height}\nstate ${c.root}\nusdc ${c.usdc}\naccounts ${c.accounts}\nlog ${c.size} ${c.logRoot}\n`;

const { chain, sequencer } = await (await fetch("https://mlx.app/api/log")).json();
const proof = await (await fetch("https://mlx.app/api/log/state/treasury.mlx")).json();
console.log("balance in the root:", verifyBalance(proof));
console.log("commitment signed:", ed25519.verify(hexToBytes(proof.commit.sig), utf8ToBytes(commitStatement(proof.commit, chain)), hexToBytes(sequencer)));
```

```text title="Output"
balance in the root: true
commitment signed: true
```

Replace `treasury.mlx` with your wallet. Also check that `leaf` shows the balances you expect: the proof shows that *these* balances are in the total, not that they are right.

Because every inner node carries a sum and no sum can be negative, a balance can't be hidden or counted twice without changing the root or the total. The explorer's [Integrity page](https://explorer.mlx.app/integrity) runs the same balance check for any name you type, and the MLX Wallet runs it for the open wallet ("Verified · block N").

## The total against the reserves

The commitment's `usdc` is everything the network owes in dollars. Compare it with the reserves on [mlx.app/reserves](https://mlx.app/reserves). USDC enters the ledger only through system transactions anyone can read: deposits and seat payments carry their on-chain payment, and every `issue` names the reserve transfer behind it ([system transactions](/transactions/types#system-transactions)).
