🧬 SteemBiota — A Deep Dive for Steem Developers
If you have ever wondered how far you can push Steem as an application platform — no L2, no sidechain, no backend — SteemBiota is a concrete answer.
It is a fully on-chain NFT ecosystem and life simulation running as a zero-build, client-side-only dApp. Every state transition is a Steem post or reply. Every ownership record is derived deterministically from chain history. The app ships as flat HTML + JS files on GitHub Pages and talks to the Steem RPC directly from the browser.
Here is how it actually works under the hood.
🏗️ Architecture: No Backend, No Build Step
The entire stack is CDN-loaded at runtime:
| Layer | Choice | Why |
|---|---|---|
| Blockchain I/O | steem-js + Keychain | Native Steem ops, no wallet custody |
| UI | Vue 3 + Vue Router 4 (CDN) | Reactive without a bundler |
| Snapshot storage | IPFS (Cloudflare / ipfs.io / Pinata) | Content-addressed, verifiable |
| Local cache | IndexedDB (idb-style raw API) | Offline-capable, zero deps |
| Hosting | GitHub Pages | Static, free, no server |
No package.json. No Webpack. No SSR. The source files — index.html, blockchain.js, state.js, components.js, accessories.js, upload.js, app.js — are what ship.
🔗 Using Steem as an NFT Ledger
Every creature and accessory is identified by its canonical author/permlink key and treated as an NFT. Minting is a getDiscussionsByCreated-discoverable top-level post tagged steembiota. Lifecycle events (feed, play, walk, breed) and ownership operations (transfer offer/accept/cancel, wear on/off) are Steem replies to that post, written via comment operations signed by Keychain.
The two-sided ownership handshake deserves particular attention:
- The current owner posts a
transfer_offerreply containing{ type: "transfer_offer", to: "recipient" }injson_metadata. - The recipient posts a
transfer_acceptreply. Only then does the GSM record the ownership change.
This means no one can force an NFT onto another account. It is anti-dumping by protocol design, not by application-layer policy.
⚙️ The Global State Machine (GSM)
Rather than scanning reply chains on every page render — which would mean hundreds of RPC calls per creature view — SteemBiota maintains a single Global State Machine: an in-memory object that is the authoritative source of truth for all ownership and equip state across a session.
// Vue-reactive ref driving the UI:
{
version: 1,
block_num: Number, // last processed Steem block
timestamp: String, // "YYYY-MM-DDTHH:mm:ss" (UTC, no Z, no ms)
ownership: {}, // "author/permlink" → current owner username
equipped: {} // "accId" → "creatureId"
}
// Module-level Map — kept outside the reactive ref:
_nftRegistry // "author/permlink" → { type: "creature"|"accessory" }
_nftRegistry intentionally lives outside the Vue reactive ref. At 100k+ entries, placing it inside the reactive object forces Vue to re-observe the entire tree on every Feed or Wear operation. The Map is serialised into the exported snapshot at checkpoint time so IPFS consumers always receive a complete registry field.
All transitions flow through a single applyOperation(state, op, blockNum, timestamp) function that is deterministic and idempotent — replaying the same event twice has no effect. This makes the GSM safe to seed from a snapshot and replay forward from any cutoff point.
🚀 Boot Sequence
Every page load runs an eight-step async bootstrap, surfaced as a syncStatus Vue ref (rendered as a slim status banner):
- IndexedDB check — load a previously verified snapshot instantly, zero RPC calls.
- On-chain checkpoint discovery — deep paginated scan of account history for
custom_jsonops with idsteembiota_checkpoint, across all community publisher accounts (not just@steembiota). - CID comparison — if the on-chain checkpoint CID matches the IDB snapshot CID, skip the IPFS download entirely.
- IPFS fetch + SHA-256 verification — three-gateway waterfall (Cloudflare → ipfs.io → Pinata) with a 5 s connect timeout and 30 s body timeout per gateway. The downloaded JSON is hashed and compared against
state_hashin the checkpoint. Mismatch = tampered file = rejected. - IDB persist — write the verified snapshot to
sb_state_snapshotstore (DB version 3). - Exhaustive replay — cursor-based
getDiscussionsByCreatedpaginator walks backwards until the snapshot cutoff. Reply-based ops are collected via a two-pass account-history scan: Pass 1 discoverstransfer_offerrecipients not yet in the known author set; Pass 2 scans the augmented set for all reply types. This closes the gap where a first-time transfer recipient'stransfer_acceptwould otherwise be silently missed. - Single atomic state write — replay runs against a local variable;
stateRef.valueis assigned exactly once at the end, eliminating UI flicker. - Multi-tab handoff — a
localStorageboot lock (post-write read-back for best-effort race mitigation) ensures only one tab runsbootstrapState(). Follower tabs receive the finalised state viaBroadcastChannel,storageevent, or 500 ms polling fallback, in that order.
📸 Checkpoint System
The checkpoint system is how the app avoids a full chain replay on every cold boot. Any logged-in user can publish a checkpoint:
{
"id": "steembiota_checkpoint",
"json": {
"version": 1,
"block_num": 105521214,
"state_hash": "<sha256-of-canonical-payload>",
"snapshot_cid": "<ipfsv1-cid>"
}
}
Canonical payload is a strict five-field object built by _buildSnapshotPayload(state, registry):
{
version, block_num, timestamp,
registry, // compact: { "author/permlink": { t: "c"|"a", o: "owner" } }
equipped // "accId" → "creatureId"
}
All three code paths that produce a snapshot — hashState(), autoUploadCheckpoint(), and generateExport() — delegate to this single helper, ensuring the hashed object, the uploaded object, and the exported file are byte-for-byte identical. Keys are recursively sorted before serialisation for cross-engine hash stability.
Checkpoint scoring groups candidates by (block_num, state_hash) and scores each group: a large trust bonus for @steembiota, a floored integer reputation score (floating-point normalisation avoids JS-engine divergence), and a floor(block_num / 1000) recency bonus. The top-scoring group wins.
Root validation — CHECKPOINT_ROOTS is a hard-coded array of genesis anchors. Any candidate checkpoint whose block_num falls at or below a root entry but whose state_hash does not match is rejected unconditionally, closing the poisoned-checkpoint attack surface.
🎨 Deterministic Procedural Rendering
Creature and accessory visuals are generated from a compact genome using a seeded PRNG. The same genome always produces the same canvas painting on any browser, any OS, any hardware. This matters: the genome is immutable (published on-chain once), but the rendering happens client-side on every view, so determinism is not optional.
Genomes encode body shape via MOR (Mean Optimal Ratio) matching — fitMor() performs a full linear scan of 10,000 MOR values to find the globally optimal body aspect ratio for the creature's proportions. Colour, limb counts, and trait expressions are all derived from the genome seed, not stored anywhere.
🔁 RPC Resilience
callWithFallback wraps every steem-js API call with a three-node rotation (api.steemit.com, api.steem.fans, api.justyy.com), a 12 s per-call timeout, and a full circuit-breaker: after exhausting all nodes, currentRPCIndex resets to 0 and an exponential backoff (1 s → 2 s → 4 s, capped at 30 s) prevents thundering-herd bursts. A successful call resets the backoff interval to 1 s.
🛠️ For Developers: What Is Reusable?
If you are building on Steem and any of this is useful, here is what transfers cleanly:
- The GSM pattern — a single deterministic state object seeded from IPFS + replayed forward from chain history works for any on-chain application that needs O(1) state lookups without a backend.
callWithFallback— drop-in multi-node RPC wrapper with timeout and circuit-breaker.- The checkpoint system —
custom_json+ IPFS + SHA-256 is a general tamper-evident snapshot pattern for any Steem dApp. - The two-pass reply scan — applicable to any app where reply authors can be unknown at scan time.
- The boot lock pattern —
localStoragepost-write read-back +BroadcastChannelis a lightweight multi-tab sync mechanism that requires zero external dependencies.
📂 Source & Live App
- 🌐 Live app: https://puncakbukit.github.io/steembiota
- 💻 Source:
https://github.com/puncakbukit/steembiota
Open source — all logic in seven flat JS/HTML files, no build step needed to read or fork.
Feedback, PRs, and community checkpoint publishers are all welcome. If you are experimenting with on-chain state machines or NFT patterns on Steem, let's compare notes.
Built for the Steem blockchain by @puncakbukit.
Assisted by https://Claude.ai/.
See also:
- By eliminating downvotes, Blurt ensures users are rewarded based on positive consensus, not through whale punishment. Please join through this link or this link with the invite code "puncakbukit."
- 🌀 SteemTwist — Steem with a Twist
- 🧬 Introducing SteemBiota — Raise, Breed & Evolve Creatures on the Steem Blockchain!
- 🎮 Reversteem — Play Reversi on the Steem Blockchain!
- @steem.amal: Charity At Your Fingertips
- Maximize curation rewards: follow our trail! Maksimalkan reward kurasi: ikuti trail kami! トレイルをフォローし、キユレーション報酬を最大化!
Upvoted! Thank you for supporting witness @jswit.