Skip to main content

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

1 ZEQ=1043 plancks1\ \text{ZEQ} = 10^{43}\ \text{plancks}
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.

FailureStatusBody
Missing or non-positive amount400{"ok":false,"error":"amount (whole ZEQ, > 0) required"}
No machine on this domain400{"ok":false,"error":"no_machine","hint":"Claim/spin up a machine on this domain first."}
Balance short409{"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.

FailureStatusBody
Fewer than two positive amounts400{"ok":false,"error":"split needs ≥2 positive amounts (amounts:[plancks…] or parts:[zeq…])"}
Target is an envelope409{"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 here409{"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 face400{"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:

FailureStatusBody
Fewer than two ids400{"ok":false,"error":"merge needs ≥2 coin token_ids"}
An envelope in the set409{"ok":false,"error":"not_divisible","hint":"Only coins merge. An envelope in the set is an indivisible bearer proof."}
One input not eligible409{"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.

FailureStatusNote
Not the holder / not active403"You do not hold this envelope (or it is not an active coin)."
Soulbound light proof403soulbound_light_proofmovable = false rows never move
Staked in an open vote409envelope_locked_in_vote, with the vote id
Homed on another chain409not_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:

issuer+pool+user_held  =  issued\text{issuer} + \text{pool} + \text{user\_held} \;=\; \text{issued}

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.

  • 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 .ZEQ file at rest.