Contracts

The staking module is just another smart contract

Entry #03 · 2026-01-22 · Devlog

The staking module is just another smart contract

I finished Phase 2 of AsentumChain last week. Smart contracts, deterministic VM, event receipts, the works. Big milestone. Felt good. I took a walk. Ate a sandwich. Came back and started Phase 3.

Phase 3 is BFT consensus — real multi-validator block production with voting rounds and quorums and slashing. That's a big multi-turn effort. Step one in that sequence, which I called 3.1 in my roadmap, is building a validator registry: a place the chain can look up "who are the validators right now, what are their pubkeys, how much stake do they have?"

Normally this is a core-protocol concern. You write a "staking module" in the node codebase. It has its own data structures, its own transaction types, its own RPC endpoints. Cosmos SDK has an entire x/staking module — thousands of lines of Go. Ethereum's beacon chain has a dedicated state subtree just for validators. It's a big, important, special thing that deserves big, important, special machinery.

I thought about it for maybe 20 minutes and then I wrote the staking module as... a smart contract. Sitting at a reserved address, 0x0000000000000000000000000000000000000001, that you call with bond(pubKey, amount) the same way you'd call transfer(to, amount) on an ERC-20.

And genuinely, literally, every single piece of existing infrastructure just worked for it on the first try. I want to talk about why this feels so good, because I think there's a deeper lesson about how to build systems underneath.

the reserved address pattern

The idea: deploy a system contract at a known address during genesis. The protocol wires it up automatically. Users interact with it through regular contract calls. The chain itself treats it as a special read source when it needs to know things like "who is the current validator set?"

This isn't new. Ethereum has "precompiles" at low addresses for things like elliptic curve operations. Cosmos has "module accounts" for staking, distribution, and governance. NEAR has system contracts. Solana has programs owned by the runtime. The pattern is well-established.

But I don't think I fully appreciated how POWERFUL it was until I actually did it.

Here is the entire list of things I had to write to add validator bonding and unbonding to the chain:

  1. A JavaScript file with init, bond(pubKey, amount), unbond(amount), getValidators() methods. About 100 lines. It's a regular smart contract that happens to store a list of addresses and stakes.
  2. A helper that deploys the contract at genesis to the reserved address and runs its init method with the initial validator set (just the faucet for now, bonded with 10% of its balance). About 50 lines.
  3. A method on the node class that runs a read-only VM call against the staking contract's getValidators method and returns the result. About 15 lines.
  4. An RPC endpoint at GET /validators that returns that. About 10 lines.
  5. Three CLI commands: validators list, validators bond, validators unbond. Maybe 80 lines total, most of it argument parsing.

Total: about 250 lines of net new code. For an entire validator registry module, from "nothing" to "Alice just bonded 1,000 ASE and you can see her in the set."

the "everything just worked" moment

Here is the part that made me grin out loud, actually-out-loud, at 11pm on a Thursday. Because the staking module is a regular contract, all of this worked automatically, with zero extra code:

  • Event emission. The contract calls emit('Bond', { validator, amount, totalStake }). That event lands in the block receipt. You can curl /receipts/:txhash and see it rendered as JSON. Nothing about it was special-cased. The staking event goes through the same path as the token contract event from Phase 2.
  • Cross-node determinism. A replica running the HTTP sync loop pulls the block containing the bond call, re-runs the VM against its own local state, rebuilds the receipt, verifies the receiptsRoot field in the block header against the producer's. All the Phase 2 determinism machinery just fires. The replica arrives at byte-identical state as the producer, including who the validators are.
  • The browser explorer. Opening http://127.0.0.1:9045/ in a browser, you see the bond transaction with its CALL badge in the phosphor-green tx detail view, the decoded method name and arguments inline, the Bond event rendered underneath with the sender, amount, and new total stake. I did not touch a single line of the explorer code for any of this. It just showed up.
  • The CLI deploy/call flow. I didn't even have to write a dedicated "bond" transaction code path. asentum call <contract> bond --from alice --args [...] already existed from Phase 2. I just hardcoded the reserved address and renamed the command to asentum validators bond.
  • Gas metering. Bonding charges gas like any other call. Unbonding too. Priced automatically by number of storage writes and event emissions.
  • Idempotent block application. When the replica re-runs the bond tx during HTTP sync, it computes the same new storage root for the staking contract as the producer did. This is the foundation of future consensus — every node must agree on who the validators are — and it comes for free because the VM is deterministic.

I set up the contract, ran asentum validators list against a fresh node. It showed the faucet as validator #1, bonded at block 0 with 100,000 ASE. I created an account called alice, funded her from the faucet, ran asentum validators bond --from alice --amount 1000. Waited 3 seconds for the next block. Ran asentum validators list again. Two validators. The second one was Alice.

That's it. That's the entire feature. About 20 minutes from "let me think about this" to "Alice is on the validator set, observable from the browser explorer, persisted to disk across restarts, replicating to the replica node, emitting events in block receipts."

why this matters (the actual lesson)

There's a trap in systems design where you build special machinery for special things. The staking module is "important," so it deserves its own protocol mechanism. The governance module is "important," so it deserves its own transaction type. The treasury module is "important," so it gets its own RPC endpoint category. Before you know it, you've built ten subsystems that each have their own way of reading and writing state, their own way of persisting data, their own way of being queried over RPC. Everyone who wants to understand the chain has to learn ten different patterns.

What you actually want is a small set of extremely general primitives, and then you compose them. If your smart contract VM is good enough, the staking module IS a smart contract. The governance module IS a smart contract. The treasury module IS a smart contract. Each one is independently deployable, independently auditable, replaceable via a governance vote, and follows the same pattern every other contract follows. The "core protocol" is just: the VM, the state store, the block production loop, the tx format. That's it. Everything else is code that users could technically write themselves.

This is not a new insight. Unix had it. Pipes and processes. Every modern system has some version of this principle. But applying it to blockchain infrastructure — treating the VM as the one extensible primitive and everything else as a contract — still feels way more radical than it should, because the existing ecosystem has so much special-cased, protocol-level machinery that people forget it's optional.

And here's the part I keep coming back to. The real test isn't that I wrote 250 lines and got a validator registry. The real test is that when I boot a fresh node, genesis happens, block 0 is produced with the staking contract's initial storage baked into the state root, and the validator set is queryable BEFORE the first non-genesis block has ever been mined. Deterministically. Across every node running the same VM version. With zero special protocol machinery for "validators are a thing the chain knows about."

Phase 3.2 is proposer selection and voting rounds next. Here's my prediction. Those are going to be contracts too. The consensus engine is going to READ from them, but the data itself is going to live in contract storage. Every governance decision about the validator set — who's active, who's jailed, what the minimum bond is, how slashing works — is going to be state inside a contract you can asentum view from your terminal.

Small extensible core. Everything else is a contract.

That's the vibe. That's the whole game.

— milkie

Don't miss the next entry.

Join the launch list and we'll send you a note whenever there's a new devlog entry, a research drop, or a real milestone.