The protocol
Everything a moeba is lives on the ledger in a form anyone can read: one URI, a handful of token Remarks, and thirty-two bytes of genome. This page lays the bytes out so a wallet, an explorer or a marketplace can read them without asking us — and gives the API for those who would rather ask.
Written for a reader who writes code. The same material for a keeper is in the whitepaper and How it works. Where this page and the ledger disagree, the ledger is right.
A rehearsal. This species runs on Xahau testnet before its release: the population may be wiped, the issuing account replaced and the rules changed without notice, and nothing born here carries over. Why →
Where a moeba is
A moeba is a URIToken on Xahau issued by one account, the species: rMmx5nHzoVJAoMTFWkgCCMrZryTaD682tr on Xahau testnet. A URIToken from any other issuer is not a moeba, whatever its URI says; that check comes before any other. Three places hold everything:
- The URI — the identity: model, kind, generation, birth ledger, fertility, rules version, maturity deadline, serial, commitment. Fifty-two bytes, immutable by construction, because a URIToken's id is derived from its issuer and its URI together.
- The Remarks — six named records, three immutable (birth, audit, rules) and three the issuer rewrites at a keeper's signed request (use, terms, name). Only the issuer can write a Remark on its own token, so each record is a statement by the species.
- Hook state on the species account — the birth queue and the counters. Live, not part of any creature; read it through
account_namespaceor the API.
One genetic model has ever been minted on chain: v0.1-32-deep, model byte 1. The URI reserves a byte for the model so a wider genome can be added later without touching a creature that exists; a reader that meets a model byte it does not know should say so rather than guess. Everything below describes v0.1-32-deep.
Numbers are big-endian throughout. Hex is upper-case where the ledger shows it; base64url is the RFC 4648 §5 alphabet, no padding, and a decoder must reject a string that does not re-encode to itself.
The identity URI
urn:creature:birth:2: followed by 52 bytes as base64url: 91 characters in all. A URIToken's URI is stored as hex on the ledger; decode the hex to ASCII first.
| Bytes | Field | Meaning |
|---|---|---|
0 | version | 02. Version 1 tokens (44-byte payload, prefix urn:creature:birth:1:) exist only on earlier testnet populations and carry no deadline or serial. |
1 | model | 1-based index into the model table: 1 = v0.1-32-deep, 2 = v0.1-64 (reserved, never minted). 0 or above the table is invalid. |
2 | kind | 0 founder, 1 born of two parents. |
3 | generation | 0 for a founder; otherwise 1 + the greater parent's generation, saturating at 255. |
4–7 | birthLedger | u32. The ledger in which the birth's randomness was fixed and its genome settled. The ledger whose hash was the randomness is named in c.audit/1; it closed a moment earlier. |
8–9 | fertility | u16. Births this creature may parent in total. The count used so far is in c.use/1. |
10–11 | rules | u16. The rules version; the profile's hash is in c.rules/1. |
12–15 | maturityAt | u32, ledger close time (seconds since the Ripple epoch, 2000-01-01). Mature when the current ledger's close time is at or past it. A founder's is in the past. |
16–19 | serial | u32. The admission number; monotonic, never reused, so a burned creature cannot be re-minted. |
20–51 | commitment | 32 bytes. The hash of the validated request that committed this birth; never all zero. One birth per commitment. |
The token id is Xahau's URIToken keylet — a SHA-512-half over the ledger-space prefix, the issuer's AccountID and the URI bytes — so the id is fixed the moment the URI is, and the species can retry a failed mint without producing a second token.
The records
Six Remarks by name. Each value begins with a version byte; a reader that meets a version it does not know reports it as unknown rather than parsing on assumption. Values are at most 256 bytes. The three immutable records and the URI are covered by the token's Digest.
| Remark | Written | Layout |
|---|---|---|
c.birth/1 | at mint, immutable | [0] version 02 · [1–2] development u16 · [3] parent count, 0 or 2 · [4–23] breeder AccountID, 20 zero bytes for a founder · [24–55], [56–87] the parents' token ids when count is 2 (P then Q; P supplies the child's first copy, Q its second) · then the genome, 32 bytes. 56 bytes for a founder, 120 for a birth. A version-1 record without the breeder exists only on earlier testnet populations. |
c.audit/1 | at mint, immutable | [0] version 01 · [1] source: 0 laboratory (never on a species deployment), 1 retired and never to be reused, 2 ledger-beacon · [2–5] the source ledger, u32 · [6–37] the entropy, 32 bytes. 38 bytes. For ledger-beacon the entropy is that ledger's hash; you can fetch the ledger and compare. |
c.rules/1 | at mint, immutable | [0] version 01 · [1–32] the profile hash. 33 bytes. The profile is the text that describes the rules of birth; the hash is in the Hook's own state too. |
c.use/1 | by the species, after each birth it parents | [0] version 01 · [1–2] births used, u16. Absent means zero on a token whose three immutable records are present; on an incomplete token it means unknown, and unknown is not fertile. |
c.breed/1 | by the species at the keeper's request (F=05); removed at F=04 | [0] version 01 · [1–20] the owner who published, AccountID · [21–24] price per birth in drops, u32 · [25–28] expiry ledger, u32, 0 for none. 29 bytes. The terms bind only while the account named is the token's owner: a transfer closes the creature with no further write. |
c.name/1 | by the species at the keeper's request (F=06), once | [0] version 01 · [1–24] UTF-8, NUL-padded to 24 bytes. Trim trailing NULs. Not in the Digest: it says nothing about what the creature is. |
The Digest. SHA-512-half( "KIN/digest/1\0" ‖ len(uri) ‖ uri ‖ len(birth) ‖ birth ‖ len(audit) ‖ audit ‖ len(rules) ‖ rules ), each length a u32, the URI as ASCII. It is fixed before the mint, so it cannot name the mint transaction; what it commits to is the identity and the three immutable records in one fixed order.
The genome
Thirty-two bytes: two haplotypes of 16 bytes, the first from parent P, the second from parent Q. Within a haplotype the 36 loci are packed at the bit offsets below, MSB-first — bit 0 of the haplotype is the top bit of its first byte — using 128 of 128 bits. A locus's value on haplotype h is the bits-wide unsigned integer at h × 128 + offset.14 loci are discrete and choose an allele from a table; the rest are quantitative and add up into 22 traits.
| # | Offset | Bits | Locus | Kind | Alleles (index: name, dominance, founder weight /256) or trait |
|---|---|---|---|---|---|
| Body (LG1) | |||||
| 0 | 0 | 3 | BODY_PLANBody plan | discrete | 0 elongate d3 w1121 compact d2 w802 discoid d1 w483 serpentine d0 w164 bifid (bifid (forked)) d5 mutation-only5 colonial (colonial (lobed)) d6 mutation-only6 polyp d7 mutation-only7 reserved |
| 1 | 3 | 3 | SYMMETRYSymmetry | discrete | 0 bilateral d2 w1601 radial3 (threefold radial) d1 w402 radial4 (fourfold radial) d3 w403 asymmetric d0 w164 radial5 (fivefold radial) d4 mutation-only5 spiral d5 mutation-only6 radial6 (sixfold radial) d6 mutation-only7 reserved |
| 2 | 6 | 3 | REG_SEGMENTSegment rule | discrete | 0 linear d1 w1281 doubled d2 w642 paired d3 w483 suppressed d0 w164 hyper d4 mutation-only5 fused d5 mutation-only6 reserved7 reserved |
| 3 | 9 | 4 | SEGMENT_COUNTSegment count | quantitative | trait SEGMENT_COUNT (Segment count) |
| 4 | 13 | 4 | ELONGATIONElongation | quantitative | trait ELONGATION (Elongation) |
| 5 | 17 | 4 | TAPERTaper | quantitative | trait TAPER (Taper) |
| 6 | 21 | 4 | BODY_SIZEBody size | quantitative | trait BODY_SIZE (Body size) |
| 7 | 25 | 3 | HEAD_FORMHead form | discrete | 0 rounded d2 w1201 pointed d3 w722 flat d1 w483 crested d0 w164 horned d4 mutation-only5 double d5 mutation-only6 reserved7 reserved |
| 8 | 28 | 4 | HEAD_SIZEHead size | quantitative | trait HEAD_SIZE (Head size) |
| Limbs and senses (LG2) | |||||
| 9 | 32 | 3 | APPENDAGE_FAMILYLimb type | discrete | 0 fins d2 w961 legs d3 w802 tentacles d1 w483 none d0 w324 spines d4 mutation-only5 wings d5 mutation-only6 frills d6 mutation-only7 reserved |
| 10 | 35 | 3 | REG_APPENDAGELimb placement | discrete | 0 per_segment (one per segment) d2 w1121 fixed (fixed count) d1 w962 terminal (at the ends) d3 w323 alternating d0 w164 radial_all (all around) d4 mutation-only5 none_reg (none) d5 mutation-only6 reserved7 reserved |
| 11 | 38 | 4 | APPENDAGE_COUNTLimb count | quantitative | trait APPENDAGE_COUNT (Limb count) |
| 12 | 42 | 4 | APPENDAGE_LENGTHLimb length | quantitative | trait APPENDAGE_LENGTH (Limb length) |
| 13 | 46 | 4 | APPENDAGE_SPREADLimb spread | quantitative | trait APPENDAGE_SPREAD (Limb spread) |
| 14 | 50 | 4 | BRANCHINGBranching | quantitative | trait BRANCHING (Branching) |
| 15 | 54 | 3 | TAIL_FORMTail form | discrete | 0 tapered d2 w1281 forked d3 w642 blunt d1 w483 none d0 w164 fan d4 mutation-only5 split3 (three-way split) d5 mutation-only6 reserved7 reserved |
| 16 | 57 | 4 | TAIL_LENGTHTail length | quantitative | trait TAIL_LENGTH (Tail length) |
| 17 | 61 | 3 | EYE_FORMEye arrangement | discrete | 0 paired d2 w1441 single d1 w482 multiple d3 w483 none d0 w164 stalked d4 mutation-only5 ring d5 mutation-only6 reserved7 reserved |
| Colour and pattern (LG3) | |||||
| 18 | 64 | 4 | PIGMENT_ABase hue | quantitative | trait PIGMENT_A (Base hue) |
| 19 | 68 | 4 | PIGMENT_BAccent hue | quantitative | trait PIGMENT_B (Accent hue) |
| 20 | 72 | 4 | SATURATIONSaturation | quantitative | trait SATURATION (Saturation) |
| 21 | 76 | 3 | BRIGHTNESSBrightness | quantitative | trait BRIGHTNESS (Brightness) |
| 22 | 79 | 3 | PIGMENT_CLASSPigment class | discrete | 0 cool d3 w961 warm d2 w802 deep d1 w483 pale d0 w324 iridescent d4 mutation-only5 albino d5 mutation-only6 melanic d6 mutation-only7 magenta d7 mutation-only |
| 23 | 82 | 3 | PATTERN_FAMILYPattern | discrete | 0 plain d0 w961 stripes d2 w642 spots d3 w643 gradient d1 w324 rings d4 mutation-only5 mottled d5 mutation-only6 labyrinth d6 mutation-only7 eyespots d7 mutation-only |
| 24 | 85 | 3 | REG_PATTERNPattern rule | discrete | 0 global (whole body) d1 w1281 segmental (per segment) d2 w802 appendage_only (limbs only) d3 w323 sparse d0 w164 fractal d4 mutation-only5 masked d5 mutation-only6 reserved7 reserved |
| 25 | 88 | 4 | PATTERN_DENSITYPattern density | quantitative | trait PATTERN_DENSITY (Pattern density) |
| 26 | 92 | 4 | PATTERN_CONTRASTPattern contrast | quantitative | trait PATTERN_CONTRAST (Pattern contrast) |
| Movement and surface (LG4) | |||||
| 27 | 96 | 3 | MOVEMENT_FAMILYMovement | discrete | 0 undulate (undulating) d2 w1121 pulse (pulsing) d1 w642 crawl (crawling) d3 w483 drift (drifting) d0 w324 jitter (jittering) d4 mutation-only5 spiral_swim (corkscrew swimming) d5 mutation-only6 glide (gliding) d6 mutation-only7 reserved |
| 28 | 99 | 3 | ACCENT_PLACEMENTAccent placement | discrete | 0 none d1 w1121 appendages (on the limbs) d2 w802 head_tail (head and tail) d3 w483 alternate (alternating) d0 w164 inverted d4 mutation-only5 gradient d5 mutation-only6 reserved7 reserved |
| 29 | 102 | 4 | MOVE_AMPLITUDEMovement amplitude | quantitative | trait MOVE_AMPLITUDE (Movement amplitude) |
| 30 | 106 | 4 | MOVE_FREQUENCYMovement speed | quantitative | trait MOVE_FREQUENCY (Movement speed) |
| 31 | 110 | 4 | MOVE_STIFFNESSStiffness | quantitative | trait MOVE_STIFFNESS (Stiffness) |
| 32 | 114 | 3 | MOVE_PHASEMovement phase | quantitative | trait MOVE_PHASE (Movement phase) |
| 33 | 117 | 3 | SURFACE_TEXTURESurface | discrete | 0 smooth d1 w1281 scaled d2 w642 ridged d3 w483 velvet (velvety) d0 w164 plated d4 mutation-only5 translucent d5 mutation-only6 crystalline d6 mutation-only7 bristled d7 mutation-only |
| 34 | 120 | 4 | SURFACE_RELIEFSurface relief | quantitative | trait SURFACE_RELIEF (Surface relief) |
| 35 | 124 | 4 | CURVATURECurvature | quantitative | trait CURVATURE (Curvature) |
A reserved allele index is invalid: a genome that carries one is malformed, and no birth produces one. Mutation-only alleles have founder weight 0 and appear only through the mutation pathways at birth.
Expression
What the two copies come to, per locus. This is all a reader needs to name a moeba and list its traits; the body that is drawn from the traits is presentation and may change, the traits do not.
- Discrete. Read the allele index on each haplotype. If they are equal, that allele shows. If not, the one with the higher dominance rank in the table shows; the other is carried and can be inherited.
- Quantitative. For each trait, over the loci that feed it:
sum = Σ (a + b + ⌊|a − b| · k / 256⌋),max = Σ 2 · (2^bits − 1), and the trait is⌊sum · 255 / max⌋, an integer 0–255.kis the trait's dominance deviation from the spec, 0 for every trait in v0.1-32-deep, so it is purely additive. - The name. The taxon is a Latin binomial fixed by four discrete loci —
BODY_PLAN,APPENDAGE_FAMILY,SYMMETRYandPATTERN_FAMILY— with a fixed table of genus and epithet stems; the variant is the words forPIGMENT_CLASSandMOVEMENT_FAMILY; the fingerprint is a 32-bit FNV-1a over the 32 genome bytes, folded to 20 bits ((h ^ h >>> 20) & 0xFFFFF) and written as four Crockford base32 characters, so two moebas of one taxon still differ. The API returns all of it, so a reader need not carry the stem tables.
Asking the species
Every request except the poke is an XAH Payment to the species account with hook parameters: one-letter names (as hex: F is 46, P is 50) and values as below. The species' Hooks read them and nothing else; memos and destination tags mean nothing to it. No partial payments, no SendMax, no paths: the amount must be the amount delivered.
F | Asks for | Other parameters | Amount | Who |
|---|---|---|---|---|
01 | A founder | — | founder price + budget | anyone |
02 | A birth | P, Q: the parents' token ids, 32 bytes each | birth fee + budget + Q's or P's price if it is another keeper's | the keeper of at least one parent; the other must be theirs too or open under valid terms |
04 | Withdraw terms | P: the token id | enough for one SetRemarks (the site sends 0.1 XAH) | its keeper |
05 | Publish terms | P: token id; A: price per birth, u32 drops; X: expiry ledger, u32, optional | as above | its keeper |
06 | Give a name | P: token id; N: UTF-8, 1–24 bytes, printable, no leading or trailing space | name fee + record write | its keeper, once |
| — | Poke: sweep a stuck birth, restart a stopped clock | an Invoke with Destination the species account; no parameters, no amount | the transaction fee only | anyone |
The amounts are quoted from the ledger at the moment of the request and refused below the quote; an overpayment is kept as budget. The protocol fee for a birth, the founder curve and the name fee are Hook parameters on the species account and are read for you at /v1/issuer (birthFeeDrops, founderCurve, nameFeeDrops, tickDrops). The budget a birth carries is 16 × tickDrops for the clock plus 37 emitted transactions at the network's emission price; what a birth does not spend stays with the species. The founder price is FFEE × (FMUL/100)^⌊n/FTRN⌋ with n the founders admitted so far, all from founderCurve. The site's own arithmetic for this is in its request pages; a wallet can copy it or over-quote slightly.
A request is accepted when the Payment succeeds and the species' Hooks return accept; a refusal is a tecHOOK_REJECTED with the reason in the Hook execution's return string, in plain words. After admission nothing more is signed: the species' own clock fixes the randomness, breeds, mints and delivers by Remit. A receiving account with lsfDisallowIncomingRemit set is refused at admission, since the child could never land.
The API
Everything above is what the ledger says; the API says the same in words and pictures, from an index that re-reads the ledger every few seconds. It is read-only, allows any origin, and every JSON answer carries headLedger so you know what ledger you are looking at. This deployment's is at https://api.xahmoeba.com.
| Route | Answer |
|---|---|
GET /v1/moebas/{id} | The record — id, model, genome, kind, generation, parents, breeder, owner, fertility and use, maturity, entropy, rules hash, digest, terms, offer — with name (taxon, variant, fingerprint, given, display) and traits (every expressed locus with its label, group, allele and value) already worked out. |
GET /v1/moebas/{id}/metadata.json | Token metadata in the shape NFT readers expect: name, description, image, external_url, attributes as { trait_type, value }. The protocol's facts sit under xahmoeba. |
GET /v1/moebas/{id}.svg, .png?size= | Its structure diagram, drawn from the genome; a still, on paper, with nothing interactive. size is the height, 64–2048. Immutable for a day. |
GET /v1/moebas?owner=&kind=&living=&forSale=&open=&limit=&cursor= | A page of records in mint order; ?ids=A,B,C for several. |
GET /v1/moebas/{id}/history, /owners | Every transaction that touched it and what each did; who has held it, with prices. |
GET /v1/population | The whole population in one document, gzipped, with an ETag. |
GET /v1/issuer, /v1/queue, /v1/server | The species' parameters and prices; the birth queue as it stands; the network's reserves and base fee. |
GET /v1/ledgers | Server-sent events, one per ledger close. |
Each client has a budget — a few hundred requests a minute, a fresh picture costing several — and gets 429 with Retry-After over it. Pictures and the population are cacheable; please cache them. A marketplace that shows moebas needs only metadata.json and the picture, and can verify either against the ledger with this page.
Try it on a real one
Choose a moeba, or paste a token id. Its URI is decoded byte by byte on this device by the rules above, the genome is unpacked locus by locus, and each API link opens what the index says about it.
If this page and the ledger disagree, the ledger is right and this page has a fault. Tell us.