MLXDocs
Network API

Blocks

Markdown

The ledger is a chain of signed blocks, each naming the one before it by hash. List them newest first, filter them, read one with its transactions, and check its hash and signature yourself.

GET /api/blocks

GEThttps://mlx.app/api/blocks?limit=2

Blocks, newest first, with the chain head. Without filters it is a fast page through the newest blocks; with any of sort, min, period or cursor it becomes a filtered scan.

curl "https://mlx.app/api/blocks?limit=2"

Newest first

ParameterTypeNotes
limitintegerHow many blocks. The route has a default and a maximum, and clamps anything outside it.
beforeintegerStart below this height, for the next page: pass the last height you got.
Response
{
  "head": {
    "height": 397,
    "hash": "ef5f250abbe3aa7f14767ea19ce5b1b67525fff8a8c039f0e72d5a7d33d04601",
    "ts": 1793113786120
  },
  "blocks": [
    {
      "height": 397,
      "prev": "3e8c4828eaf513d12d0096afc3db2b8a7833c6eb8e24d913df7dd6a5a66d0d63",
      "ts": 1793113786120,
      "txRoot": "26c5d2d2f219185f18af55dca8b38e793688d8ae61a86ab397c78b0769e9c31a",
      "count": 1,
      "producer": "92114fe09a90d1a3aced63f35463e4120969d1b323c41070a9ee3ca3cf6ae9a3",
      "hash": "ef5f250abbe3aa7f14767ea19ce5b1b67525fff8a8c039f0e72d5a7d33d04601",
      "txs": ["26c5d2d2f219185f18af55dca8b38e793688d8ae61a86ab397c78b0769e9c31a"],
      "sig": "a3e6fcda3caafa0083396f24bc5d198e1375ed7e62ba957673104ba7800f88ae6b0420273174f7b22fe82edd00dbc8190de9f0564b5608208e92cefd48d9f408"
    },
    {
      "height": 396,
      "prev": "afaccda78033a68bd9298a5d3d5c5040869f7e3cf288909f6c385887c47e84b0",
      "ts": 1793113778971,
      "txRoot": "13e04bcbfca47659cfd1d1fc07ee93125cc8d67ac5ca889b8578dc559f4d378e",
      "count": 1,
      "producer": "92114fe09a90d1a3aced63f35463e4120969d1b323c41070a9ee3ca3cf6ae9a3",
      "hash": "3e8c4828eaf513d12d0096afc3db2b8a7833c6eb8e24d913df7dd6a5a66d0d63",
      "txs": ["13e04bcbfca47659cfd1d1fc07ee93125cc8d67ac5ca889b8578dc559f4d378e"],
      "sig": "a5d4de677c3fecb889b8e0e9a33ea726105da17f7563c0595a1b9dd8bedf74dd12b1183db2b47958a22e91e55331a0ef2e1d3dec1c381fa7429060622703ab0e"
    }
  ]
}

Notice that block 397's prev is block 396's hash: that is the link.

Filtered

ParameterTypeNotes
sortnew or oldNewest first (default) or oldest first.
minintegerOnly blocks with at least this many transactions.
period1h, 24h, 7d, 30d or allOnly blocks from that window.
cursorintegerThe next value of the previous page.
limitintegerPage size, clamped like the unfiltered list.

A filtered call looks at a bounded number of heights and stops, so a page can hold fewer blocks than limit (even none) while next is still set. Keep calling with cursor=<next> until next is null.

Response (GET /api/blocks?sort=old&min=1&period=24h&limit=1)
{
  "head": { "height": 399, "hash": "321578e08b05821326965df711b4d96cbcfafc0960704eac13e49d4167f1e8cd", "ts": 1793116923973 },
  "blocks": [
    {
      "height": 353,
      "prev": "80e6d40836464c3f4cba025ec73675d42732c918cb7a8f5b87709a3f00065219",
      "ts": 1793034123926,
      "txRoot": "bea17547c9147d16e96e7622f6fd086980318eacbf830020c87e456644c26aa0",
      "count": 1,
      "producer": "92114fe09a90d1a3aced63f35463e4120969d1b323c41070a9ee3ca3cf6ae9a3",
      "hash": "d784c9a78d8be81a83c78b8176154ac5435ef5b7baffe4c489463f60ccc04873",
      "txs": ["bea17547c9147d16e96e7622f6fd086980318eacbf830020c87e456644c26aa0"],
      "sig": "64b18347c567098599e1a9005b268d5dc2bfbc050540ffd230e082f89845d7792c0f6111395c90a473ea235995dfb076fe722d3653169156d22f754845f61b0e"
    }
  ],
  "next": 354,
  "scanned": 1
}
FieldTypeNotes
nextinteger or nullThe height to continue from; null when the scan reached the end.
scannedintegerHeights looked at in this call.

GET /api/block/{height}

GEThttps://mlx.app/api/block/1

One block and every transaction in it, in order. A sealed block never changes, so the response is cached as immutable.

ParameterTypeNotes
heightinteger (path)0 is the genesis block.
Response
{
  "block": {
    "height": 1,
    "prev": "b5100a4ff5e14bd01a6240ea2809770985cbb944d83d4a940f87b75efcd1f770",
    "ts": 1792312855965,
    "txRoot": "a3f53b6b0d575b5540b9310a7fd247ad5c0260e09c61bd9c3faa4ea3f530f1a3",
    "count": 1,
    "producer": "92114fe09a90d1a3aced63f35463e4120969d1b323c41070a9ee3ca3cf6ae9a3",
    "hash": "73c50d2f0bfd7df198234734ab6e9d6287653b49a6a5e61543ee5a05e80c25d5",
    "txs": ["a3f53b6b0d575b5540b9310a7fd247ad5c0260e09c61bd9c3faa4ea3f530f1a3"],
    "sig": "30159ff85784b0ead874031d7c848524f2a51f4442d420d8936ba99380e47de799c82c520cac56fbabf6dc4259b04e80995a60002bd9bd44e8b0b0b4333ee80a"
  },
  "txs": [
    {
      "hash": "a3f53b6b0d575b5540b9310a7fd247ad5c0260e09c61bd9c3faa4ea3f530f1a3",
      "tx": {
        "chain": "mlx-1",
        "from": "luca.mlx",
        "nonce": 0,
        "ts": 1792312855563,
        "pk": "53fbe3a2e4ee9ee2ad907174562e8f2fc9b2bb3e1074f8ce274935439422c4ab",
        "body": { "type": "create_account" },
        "sig": "c461d94586ddd6c63c235984332a9508b5f04f1fd8c0f7f32d35377152c4d658a4951ce88ffce0275eb4bbf33bbbeedc9755234ec22cda1b06d85ff6be89fe06"
      },
      "status": "success",
      "receivedAt": 1792312855939,
      "effects": [{ "kind": "account", "to": "luca.mlx" }],
      "height": 1,
      "index": 0
    }
  ]
}

The transactions are in the record format described on Transactions, with Mac names hidden the same way.

The block format

FieldTypeNotes
heightintegerPosition in the chain, from 0.
prevstringThe previous block's hash (64 zeros for the genesis block).
tsintegerWhen the block was sealed, ms. Block times only increase.
txRootstringMerkle root of the transaction hashes, in order (below).
countintegerNumber of transactions.
txsstring[]The transaction hashes, in the order they were applied.
producerstringPublic key that sealed the block: the sequencer's (sequencer on /api/stats).
hashstringSHA-256 of the block header (below), hex.
sigstringEd25519 signature by producer over the 32 bytes of hash, hex.

Blocks are sealed by the sequencer from its mempool, usually as a transaction is submitted (the submit call waits for it), and otherwise by the sequencer's scheduled run.

Check a block yourself

Everything needed is in two public responses: the block, and chain and sequencer from /api/stats.

  1. Rebuild the header hash

    Make the header object { chain, height, prev, ts, txRoot, count, producer }, with chain from /api/stats. Write it as canonical JSON: keys sorted, no whitespace, standard JSON escaping. Its SHA-256, as lowercase hex, must equal hash.

  2. Rebuild the Merkle root

    Start with the txs hashes in order. While more than one is left, pair them up: each pair becomes SHA-256 of the two hashes' bytes joined (32 + 32 bytes, not the hex text). An odd one out at the end of a level is paired with itself. The last hash left must equal txRoot. A block with no transactions would have SHA-256 of the empty string as its root.

  3. Check the signature

    producer must be the sequencer key from /api/stats. Verify sig as an Ed25519 signature over the 32 bytes of hash (decoded from hex), with that key.

  4. Follow the link

    Fetch the block at height - 1: its hash must equal this block's prev. Walking back to height 0 checks the whole chain.

// npm i @noble/curves @noble/hashes
import { ed25519 } from "@noble/curves/ed25519";
import { sha256 } from "@noble/hashes/sha256";
import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils";

const API = "https://mlx.app/api";
const canonical = (v) =>
  v === null || typeof v !== "object"
    ? JSON.stringify(v)
    : Array.isArray(v)
      ? "[" + v.map(canonical).join(",") + "]"
      : "{" + Object.keys(v).filter((k) => v[k] !== undefined).sort().map((k) => JSON.stringify(k) + ":" + canonical(v[k])).join(",") + "}";
const hex = (data) => bytesToHex(sha256(typeof data === "string" ? utf8ToBytes(data) : data));
const merkleRoot = (hashes) => {
  if (!hashes.length) return hex("");
  let level = hashes;
  while (level.length > 1) {
    const next = [];
    for (let i = 0; i < level.length; i += 2) next.push(hex(hexToBytes(level[i] + (level[i + 1] ?? level[i]))));
    level = next;
  }
  return level[0];
};

const { chain, sequencer } = await (await fetch(`${API}/stats`)).json();
const { block } = await (await fetch(`${API}/block/1`)).json();
const { height, prev, ts, txRoot, count, producer } = block;

console.log("hash", hex(canonical({ chain, height, prev, ts, txRoot, count, producer })) === block.hash);
console.log("root", merkleRoot(block.txs) === txRoot);
console.log("sig ", producer === sequencer && ed25519.verify(hexToBytes(block.sig), hexToBytes(block.hash), hexToBytes(sequencer)));

Each transaction in the block can be checked the same way: see Keys and signing. One exception: a Mac link (bind_device) is shown with the kind of Mac in place of its name, so its own hash can't be rebuilt from the public copy, though its hash still sits in txs and txRoot. For proof that a block is in the published log, and of an account's balances at a height, see Integrity.

Errors

StatusWhen
400/api/block/{height}: the height isn't a whole number ("Block height must be a whole number").
404/api/block/{height}: no block at that height yet ("Block not found").
500The ledger couldn't be read.

/api/blocks never answers 400: an unknown sort or period falls back to the default, and an out-of-range limit is clamped.