The ZEQ coin
The ZEQ coin is the bearer instrument. Not the envelope — the envelope is an indivisible receipt for one compute, denominated in credits. The coin is the divisible, carryable object, denominated in plancks.
Both live in the same table, tally_tokens, discriminated by kind. The coin reuses the envelope's
entire bearer machinery — spend_seq, home_origin, the Ed25519 node co-signature, transfer,
export, redeem, migrate, failover. Divisibility is the only thing the coin adds, and every
divisibility endpoint is guarded to kind='coin': try to split an envelope and you get
409 not_divisible.
The planck
export const PLANCKS_PER_ZEQ = 10n ** 43n; // economyConfig.ts
export const ZEQ_COIN_QUANTA = 10n ** 43n; // zeqTimeMeter.ts — pinned equal
Forty-three decimal places, Planck-scale by design. All coin arithmetic is BigInt — 1043
is far past a float's ~17 significant digits, and conservation has to be bit-exact.
Do not confuse this with the credit quantum. The credit ledger uses QUANTA_PER_CREDIT = 10^18.
The two are deliberately not unified: 1018 is credits, 1043 is coins. An
earlier build borrowed 1018 for the coin path; that was corrected.
A coin row carries its face in value_plancks and zero in value_credits. An envelope is the
mirror image. faceOf(row) reads whichever field the kind calls for, which is what lets one bearer
code path serve both.
Denominations
export const DENOMINATIONS = [
{ label: "100", plancks: 100n * PLANCKS_PER_ZEQ },
{ label: "50", plancks: 50n * PLANCKS_PER_ZEQ },
{ label: "10", plancks: 10n * PLANCKS_PER_ZEQ },
{ label: "5", plancks: 5n * PLANCKS_PER_ZEQ },
{ label: "1", plancks: 1n * PLANCKS_PER_ZEQ },
];
denominate() is a greedy partition into these notes plus one "change" bucket for any remainder.
Denominations are cosmetic labels. The value that moves is always the exact plancks on each row;
denom only says which standard note a row happens to equal.
Coin token ids are ZT-<machine-slug>-c<14 hex>, derived deterministically from a lineage seed, so a
retried request hits ON CONFLICT (token_id) DO NOTHING instead of double-minting.
POST /api/tally/coins/mint-from-balance
Re-attribution, not new supply. This debits whole ZEQ from your machine's integer
tally_supply.tokens_remaining and mints coin rows summing to exactly the same plancks. The capped
supply is untouched; only the representation of a balance changes from an integer to bearer tokens.
curl -sX POST https://zeq.me/api/tally/coins/mint-from-balance \
-H "Authorization: Bearer $ZEQ_KEY" -H "Content-Type: application/json" \
-d '{"amount": 137}'
amount is whole ZEQ and is floored; zeq is accepted as an alias. Response:
{
"ok": true,
"minted_zeq": 137,
"total_plancks": "1370000000000000000000000000000000000000000000",
"coins": [
{ "token_id": "ZT-yourslug-c9f3c1ab27d40e", "denom": "100", "value_plancks": "1000000000000000000000000000000000000000000000" },
{ "token_id": "ZT-yourslug-c4b81de07c2a39", "denom": "10", "value_plancks": "100000000000000000000000000000000000000000000" },
{ "token_id": "ZT-yourslug-c7e2049fb1cc86", "denom": "10", "value_plancks": "100000000000000000000000000000000000000000000" },
{ "token_id": "ZT-yourslug-c1a6635dd90f47", "denom": "10", "value_plancks": "100000000000000000000000000000000000000000000" },
{ "token_id": "ZT-yourslug-c05fbc7e8341da", "denom": "5", "value_plancks": "50000000000000000000000000000000000000000000" },
{ "token_id": "ZT-yourslug-cd3820a5f6b19c", "denom": "1", "value_plancks": "10000000000000000000000000000000000000000000" },
{ "token_id": "ZT-yourslug-c62ce4718abf05", "denom": "1", "value_plancks": "10000000000000000000000000000000000000000000" }
],
"zeqond": 2301712291,
"note": "Coin bearer tokens minted from your integer balance — same plancks re-attributed, capped supply untouched."
}
The token ids above are shaped, not sampled: coin_token_count is 0 on every node checked, so
there is no real coin to quote. The field names, the note text and the denomination breakdown are
verbatim from routes/tallyProtocol.ts.
The debit is one atomic UPDATE … WHERE tokens_remaining >= amount, and the handler asserts
Σ minted plancks == amount × 10^43 inside the transaction before committing.
| Failure | Status | Body |
|---|---|---|
| Missing or non-positive amount | 400 | {"ok":false,"error":"amount (whole ZEQ, > 0) required"} |
| No machine on this domain | 400 | {"ok":false,"error":"no_machine","hint":"Claim/spin up a machine on this domain first."} |
| Balance short | 409 | {"ok":false,"error":"insufficient_balance","hint":"You do not hold that many whole ZEQ as an integer balance."} |
| Sum mismatch (should never happen) | 500 | {"ok":false,"error":"conservation_violation"} |
POST /api/tally/coins/:tokenId/split
Break one coin of V plancks into children summing exactly to V. Down to one planck. The
parent is consumed (status='split', spend_seq++) and the children carry lineage back to it.
Two input forms — amounts in plancks, or parts in whole ZEQ:
curl -sX POST https://zeq.me/api/tally/coins/ZT-yourslug-cd3820a5f6b19c/split \
-H "Authorization: Bearer $ZEQ_KEY" -H "Content-Type: application/json" \
-d '{"amounts": ["2500000000000000000000000000000000000000000",
"7500000000000000000000000000000000000000000"]}'
That splits 1 ZEQ into 0.25 and 0.75.
{
"ok": true,
"parent": "ZT-yourslug-cd3820a5f6b19c",
"parent_value_plancks": "10000000000000000000000000000000000000000000",
"children": [
{ "token_id": "ZT-yourslug-c8ab41f0e26d73", "denom": "change", "value_plancks": "2500000000000000000000000000000000000000000" },
{ "token_id": "ZT-yourslug-c30f97b4ce1852", "denom": "change", "value_plancks": "7500000000000000000000000000000000000000000" }
],
"zeqond": 2301712291,
"note": "Coin split — parent consumed (status='split'), children conserve the exact plancks."
}
Both children read denom: "change" because neither equals a standard note. That is expected, not a
defect — the label is cosmetic and the plancks are exact.
| Failure | Status | Body |
|---|---|---|
| Fewer than two positive amounts | 400 | {"ok":false,"error":"split needs ≥2 positive amounts (amounts:[plancks…] or parts:[zeq…])"} |
| Target is an envelope | 409 | {"ok":false,"error":"not_divisible","hint":"Only coins are divisible. Envelopes are indivisible bearer proofs — they cannot be split."} |
| Not yours / not live / not homed here | 409 | {"ok":false,"error":"not_splittable","hint":"Coin must be one you own, live (not exported/split), a coin, movable, and homed here."} |
| Amounts don't sum to the face | 400 | {"ok":false,"error":"sum_mismatch","hint":"The split amounts must sum EXACTLY to the coin's plancks. Nothing was split."} |
The eligibility check is the lock: it rides in the WHERE of the same UPDATE that consumes the
parent, so it cannot race.
POST /api/tally/coins/merge
The inverse. Two or more coins you hold collapse into one of Σ plancks; the inputs are consumed
(status='merged').
curl -sX POST https://zeq.me/api/tally/coins/merge \
-H "Authorization: Bearer $ZEQ_KEY" -H "Content-Type: application/json" \
-d '{"token_ids": ["ZT-yourslug-c8ab41f0e26d73", "ZT-yourslug-c30f97b4ce1852"]}'
{
"ok": true,
"merged": ["ZT-yourslug-c8ab41f0e26d73", "ZT-yourslug-c30f97b4ce1852"],
"child": { "token_id": "ZT-yourslug-cf14d0e93b6a27", "denom": "1", "value_plancks": "10000000000000000000000000000000000000000000" },
"total_value_plancks": "10000000000000000000000000000000000000000000",
"zeqond": 2301712291,
"note": "Coins merged — inputs consumed (status='merged'), child conserves the exact Σ plancks."
}
Ids are deduplicated. Any ineligible input aborts the whole merge — nothing is consumed:
| Failure | Status | Body |
|---|---|---|
| Fewer than two ids | 400 | {"ok":false,"error":"merge needs ≥2 coin token_ids"} |
| An envelope in the set | 409 | {"ok":false,"error":"not_divisible","hint":"Only coins merge. An envelope in the set is an indivisible bearer proof."} |
| One input not eligible | 409 | {"ok":false,"error":"coin_ineligible","token_id":"ZT-…","hint":"Every input must be a live coin you own, movable, homed here. Nothing was merged."} |
POST /api/tally/coins/transfer
Reassign the holder online. One atomic UPDATE; spend_seq advances exactly once.
curl -sX POST https://zeq.me/api/tally/coins/transfer \
-H "Authorization: Bearer $ZEQ_KEY" -H "Content-Type: application/json" \
-d '{"token_id": "ZT-yourslug-cf14d0e93b6a27", "to": "ZEQ7YH3B198MM6", "visibility": "private"}'
to_origin is optional and tags a recipient living on a peer domain. visibility /publish is a
per-transfer override of the sender machine's publish default; value transfer never depends on
it.
{
"ok": true,
"token_id": "ZT-yourslug-cf14d0e93b6a27",
"to": "ZEQ7YH3B198MM6",
"to_origin": null,
"recipient_remote": false,
"coin_value": 1,
"transfer_id": "…",
"sender_slug": "yourslug",
"transfer_zeqond": 2301712291,
"visibility": "private",
"spend_seq": 1,
"home_origin": "https://zeq.me",
"receipt": "…",
"claim_ticket": null
}
receipt is a portable reassignment receipt over
token_id | from | to | to_origin | spend_seq | zeqond | home_origin. claim_ticket is minted only
for a remote recipient (a local one authenticates on this chain anyway) and is pinned to
spend_seq, so it is single-use.
| Failure | Status | Note |
|---|---|---|
| Not the holder / not active | 403 | "You do not hold this envelope (or it is not an active coin)." |
| Soulbound light proof | 403 | soulbound_light_proof — movable = false rows never move |
| Staked in an open vote | 409 | envelope_locked_in_vote, with the vote id |
| Homed on another chain | 409 | not_home_chain, with home_origin and this_origin |
Home-chain authority is the invariant behind that last one: home_origin names the one chain
permitted to serialize this object for life. Nothing else may reassign it.
POST /api/tally/coins/:tokenId/export
Take the coin offline. The online row is escrowed to status='bearer' and spend_seq++, so the
transfer path can no longer move it: the object is now live in exactly one place, the .ZEQ file.
curl -sX POST https://zeq.me/api/tally/coins/ZT-yourslug-cf14d0e93b6a27/export \
-H "Authorization: Bearer $ZEQ_KEY"
{
"ok": true,
"coin": {
"kind": "zeq-coin",
"v": 1,
"token_kind": "coin",
"token_id": "ZT-yourslug-cf14d0e93b6a27",
"token_hash": "…64 hex…",
"value_zeq": 1,
"value_plancks": "10000000000000000000000000000000000000000000",
"home_origin": "https://zeq.me",
"node_sig": "…",
"node_pubkey": "…",
"node_sig_alg": "ed25519",
"spend_seq": 2,
"bearer_ticket": "…64 hex…",
"exported_by": "ZEQ…",
"export_zeqond": 2301712291,
"custody": [ { "…": "genesis entry, prev_hash == token_hash" } ]
},
"note": "Coin exported to bearer form — escrowed (cannot be spent online) until redeemed. Whoever holds this .ZEQ can pull it onto their machine."
}
Three things make this an actual bearer instrument:
The ticket is not bound to a ZID
bearer_ticket is an HMAC over token_id | 'BEARER' | spend_seq. Whoever holds the file redeems it. It is pinned to the height, so redeeming kills it.
The custody chain verifies offline
Append-only, SHA-256 hash-linked hops, each Ed25519-signed by the serving node with the key inline. Genesis is pinned to the coin's own hash, so the trail cannot be grafted onto another coin.
The file is HITE-sealed
AES-256-GCM with Argon2id, de-identified. A .ZEQ at rest reveals nothing about the holder or the value until it is opened with its PIN.
409 not_exportable if the coin is not yours, not live, not movable, unvalued, or homed elsewhere.
POST /api/tally/coins/redeem
Pull the coin back live. Present the .ZEQ package — either as the body, or wrapped as
{ "coin": { … } }.
curl -sX POST https://zeq.me/api/tally/coins/redeem \
-H "Authorization: Bearer $ZEQ_KEY" -H "Content-Type: application/json" \
-d @coin.zeq.json
Required fields: token_id (ZT-…), spend_seq, a 64-hex bearer_ticket, and home_origin.
custody is verified if present.
{
"ok": true,
"redeemed": {
"token_id": "ZT-yourslug-cf14d0e93b6a27",
"token_kind": "coin",
"value_zeq": 1,
"value_plancks": "10000000000000000000000000000000000000000000",
"new_spend_seq": 3,
"owner": "ZEQ…"
},
"note": "Coin pulled onto your machine — live again in your wallet. Any copy of that .ZEQ is now stale (spend height advanced)."
}
The double-spend guard
The whole offline-safety argument is one WHERE clause:
UPDATE tally_tokens
SET status = 'active', issued_to = :caller,
spend_seq = spend_seq + 1, last_transfer_zeqond = :z
WHERE token_id = :id AND status = 'bearer' AND spend_seq = :presentedSeq
Exactly one redeem at that exact height succeeds, because it bumps the height. Any copy of the
file carries the same spend_seq and matches zero rows the second time →
409 not_redeemable. A forged or superseded ticket is rejected earlier with
403 invalid_or_superseded_bearer_ticket. A tampered custody chain gives
400 custody_tampered.
If home_origin is a different (recognised peer) domain, redeem routes into a cross-domain
migrate instead: it calls the origin's POST /api/tally/coins/migrate-out, which atomically burns
the coin there (status='migrated') and returns an Ed25519-signed migration authorization the
destination verifies before minting a fresh local row. That call is idempotent on the origin side, so
a destination that burned but failed to mint can safely retry.
The conservation invariant
Every one of these operations preserves total plancks by construction, and each enforces it inside its own transaction before committing:
GET /api/tally/coins/conservation recomputes it live on any node. It has its own page —
conservation — with the current response and every term explained.
Read next
- Conservation — check the books yourself, live.
- Issuance — where the ZEQ a coin represents came from.
- Envelopes — the other object in
tally_tokens, and why it is indivisible. - HITE encryption — what seals a
.ZEQfile at rest.