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_proof — movable = 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.