Building Our Own Blockchain: From UTXO to StateCell
Calculating rights based on how long someone has held a token requires more than a current balance. It also requires a record of the past. In the review of the time-weighted ownership contract, I explored a design that recorded the block number and resulting balance whenever a balance changed. Deploying a smart contract on an existing blockchain was enough to experiment with revenue distributions that accounted for holding periods.
But what if several assets need the same kind of record? Each asset needs a way to preserve its history, define how to query it, and read those records again. If the ledger understood asset ownership and state changes through a common structure, how much of that work could it support directly?
That question was one of the reasons I began designing Nigo Protocol as its own blockchain. Building a ledger meant making an early choice: what should count as a unit of stored state, and how should a change to that state be expressed?
This first post in the StateCell series explains the starting point for that choice. It is about bringing the explicit state transitions of UTXO and the development experience made possible by smart contracts onto the same ledger.
From recording holding periods to designing a ledger
The holding-period implementation demonstrated what smart contracts made possible. I could express and experiment with new ways of calculating rights without changing the rules of the entire blockchain. That research could have continued through better contracts or an off-chain indexer that organized the records.
I wanted to extend the question to the ledger itself. I wanted a common foundation for ownership, transfers, and the relationships between states before and after a change—one that multiple programs could use. Choosing to build a chain meant defining how that foundation stored information and validated changes. It also meant taking responsibility for making those rules work identically on every node.
The storage model comes before the choice of database. It defines what units of information exist in the ledger, what a transaction can read and change, and how the states before and after a change are connected. Those decisions shape ownership verification, history tracking, program execution, and parallel processing.
UTXO was a useful place to start.
Two ways to send 60 from A's 100 to B
Suppose A holds 100 units of an asset and B holds 0. A wants to send 60 to B. To keep the comparison simple, the transfer examples in this post exclude fees and any additional issuance or burning.
If balances are stored as numbers associated with accounts, the transfer can be represented like this:
Before After
A's balance 100 40
B's balance 0 60
The program executing the transaction subtracts 60 from A's balance and adds 60 to B's. This is a simplified view of the balance changes; an actual account-based ledger also performs checks such as signature and transaction-order validation.
UTXO expresses the same transfer using a different unit. UTXO stands for Unspent Transaction Output: an output from an earlier transaction that has not yet been spent. That output becomes a unit of assets available to a later transaction. The balance shown in a wallet is the sum of the quantities in its spendable outputs. Bitcoin's transaction structure
If A's 100 is held in a single output, u0, A consumes that output and creates two new ones.
The labels u0, u1, and u2 are simply names used to distinguish the outputs in this example.
Input: u0 — A's 100
│ consume
▼
Create new outputs
├─ u1 — B's 60
└─ u2 — A's 40
Instead of changing the number inside u0 from 100 to 40, the transaction spends the entire output
containing 100. The 60 for B and the 40 returned to A as change become separate new outputs.
The input quantity, 100, equals the sum of the outputs, 60 + 40.
Once this transaction has been applied, u0 cannot be used again. If A tries to send assets to someone
else using the same u0, the ledger rejects it as a double spend. “Consumed” does not mean that the
historical transaction record is erased. It means that the output is no longer treated as available to spend.
For convenience, the example calls this “A's output.” An actual Bitcoin output has spending conditions, commonly satisfied by a signature from the corresponding key. A UTXO does not simply contain an owner's name.
Making independent transactions visible
After the transfer, B has 60 in u1, and A has 40 in u2. Suppose each now sends some of their assets
to someone else.
B's transaction: consume u1 (60)
→ C: 10, B's change: 50
A's transaction: consume u2 (40)
→ D: 15, A's change: 25
The transactions use different outputs. Applying one does not change the other's input or calculated result. The final quantities—B=50, C=10, A=25, D=15—still add up to the original 100. When there is no additional shared state, as in this example, and each transaction can identify the state needed for its validation through separate inputs, the two transactions can be validated independently.
By contrast, two transactions that both try to use u1 are not independent. There is also a dependency
between the first transaction that creates u1 and B's later transaction that consumes it: the first must
come before the second. Following the connections between inputs and outputs reveals these dependencies.
What interested me about UTXO was this explicit description of the state a transaction uses and the dependencies between transactions. It creates opportunities to identify independent branches and process them in parallel. It also makes conflicts, such as attempts to spend the same output twice, easier to identify. To benefit from that parallelism, the application's state must itself be expressible as independent outputs. State separation and parallelism in eUTXO
Account-based models can also parallelize transactions by identifying state accesses that do not overlap. For an arbitrary contract, however, the state it reads and writes may only become apparent as the call executes. Designing parallel execution involves different work depending on whether dependencies are visible in the transaction format or must be discovered by analyzing or tracing execution.
Knowing more before execution
Explicit inputs and outputs also help the person constructing a transaction. When A wants to make the
original transfer of 60, the wallet can select u0 and construct a transaction specifying the recipients
and quantities of u1 and u2. Before submitting it to a node, A can inspect what will be consumed,
who will receive the outputs, and how much each recipient will get.
Here, it helps to distinguish determinism, which lets every node reach the same result, from predictability, which lets the transaction author assess the result in advance.
The first means every node obtains the same result given the same starting state, execution conditions, and transaction order. Account-based blockchains need this property too. If one node says A has 40 left and another says A has 30, they cannot maintain the same ledger. Even parallel execution must produce results consistent with execution in the prescribed order.
The second concerns how much the person submitting a transaction can determine about its result
beforehand. If the state and conditions needed for validation can be fixed through the transaction's
inputs, the transaction depends less on unrelated state changes elsewhere. In the example, u0 remains
that particular output containing 100 until it is spent. Another transaction does not change its quantity
to 80 while leaving it available as the same output.
Constraining the validation context in this way makes validation before submission easier. Whether the transaction actually becomes part of the ledger still depends on validation during block execution—including whether its inputs remain available and conditions such as its validity period are satisfied—and on consensus. Cardano's explanation of determinism likewise covers both assessing a transaction's result in advance and the conditions for its inclusion in the ledger. Transaction costs and determinism
Off-chain transaction construction fits into this process. With the required output information, a transaction can be prepared, reviewed and signed in a separate environment, and submitted to the network later. Bitcoin's transaction examples separate construction, signing, and broadcasting, and explain offline signing. Transaction construction and offline signing examples
Off-chain construction and signing are not exclusive to UTXO. Ethereum transactions are also signed with a private key before being submitted to the network. What interested me about UTXO was less where the signature was made than the ability to include the state to be consumed and the proposed outputs in the transaction, then review their relationship in advance. Signing and submitting Ethereum transactions
Keeping the development model that smart contracts made possible
Even if UTXO is an appealing structure, asset transfers alone cannot express everything a ledger needs to support. The holding-period research already involved more than balances: checkpoints to record, intervals to calculate, revenue-distribution policies, and records of whether claims had been made. As applications grow, they introduce more state and more rules.
Smart contracts let developers add those rules as programs. The protocol does not have to anticipate every asset's policies; developers can create and deploy new behavior. They can also call existing contracts and use their functionality as part of another program. Ethereum describes this composability as an important property of smart contract development. Smart contract composability
Developers using the Ethereum Virtual Machine, or EVM, are especially familiar with writing programs in
terms of functions and state variables, and calling functions on other contracts. For example, when A
calls transfer(B, 60), the token contract updates balances according to its rules. The caller expresses
an intention through a function call, and the execution environment reads the state at that point to
calculate the result.
When inputs and outputs are constructed in advance, the developer handles the proposed state transition more directly. When behavior is expressed as a function call, the program reads the state it needs during execution and calculates the next state. The choice changes how responsibilities are divided between the client and the execution environment.
UTXO can support smart contracts too. Cardano's eUTXO model, referenced earlier, combines outputs with data and script validation. The problem I wanted to address in Nigo Protocol was therefore not how to add programs to UTXO for the first time. It was how to support explicit state transitions while also accommodating familiar contract execution on the same ledger.
A common unit for asset outputs and contract state
The smallest unit of state designed around these requirements is StateCell. A cell is an identifiable piece of state in the ledger. It contains an identifier, an owner, information identifying the structure of its value, and the value itself.
For Nigo's Native assets—the domain that the ledger handles directly as assets—cells can represent asset outputs just as in the UTXO example. A transaction consumes the cell containing A's 100 and creates cells containing B's 60 and A's 40. A Cell transaction explicitly lists the inputs it will use and the outputs it will create. The ledger and any required programs validate that transition.
EVM contract state is also recorded in the same StateCell-based storage. The EVM reads and writes state at storage locations called slots, and Nigo represents each underlying 256-bit slot as one cell. The existing compiler and EVM rules still determine how Solidity variables, arrays, and mappings are laid out across those slots.
There is a difference from asset outputs here. If a contract stores A's balance in a slot, the earlier
transfer(B, 60) call changes that slot's value from 100 to 40. The storage layer consumes the existing
cell and handles the update as a replacement that creates a cell with the same ID and a value of 40.
B's balance of 60 is likewise reflected in the cell for B's corresponding slot. Not every kind of state
is forced to receive a new output ID on every update as an asset UTXO would.
This allows both asset input-output transitions and contract execution results to be recorded through the common structure of cell consumption and creation. Each retains its own way of executing changes, while sharing the foundation that stores and commits the results.
A common storage unit does not imply identical permissions to change it. A user cannot arbitrarily consume an EVM slot as if it were an asset output and replace its value. Changes must pass through the relevant contract's execution and validation rules. Making StateCell a ledger unit requires designing both what it stores and who can change it.
Nor does storing EVM state as cells make every ordinary EVM call's state accesses known before submission. Nigo currently treats ordinary EVM transactions as a sequential execution boundary. StateCell provides a common unit for analyzing parallelism, but independence still has to be established for each execution model.
Common state, programmable rules
Returning to the question that began with holding-period research, what I wanted from the ledger was a foundation for handling state changes across assets through a common structure. Expressing what was consumed and what was created in the same format provides a starting point for working with those relationships at the ledger level, before interpreting each program's distinct internal structure.
The way holding periods translate into rights, or how revenue-distribution intervals are defined, can remain matters for programs and policies. How much history to preserve and how to query it also need separate designs. A unit of current state called StateCell does not, by itself, complete holding-period calculations or proofs of past state.
The direction behind StateCell is to represent assets and contract state in a common unit, make explicit transitions visible in transactions where possible, and let programs introduce new application rules. Each domain's permissions and validation responsibilities are then defined on top of that foundation.
The next post will examine those responsibilities in detail: what a cell's owner means, how ownership
relates to permission to change state, and how responsibilities are divided among the protocol core,
Native Programs, and EVM contracts.
One question remains. Even when state can be divided into small units, what happens when many transactions still need to update the same state? An AMM is one example: changes to a pool's reserves affect the price of the next trade. Contention over shared state is a design problem to revisit after explaining the StateCell architecture.
Part 1 of the StateCell design series. The example quantities are illustrative assumptions used to explain the state model, not measurements of throughput or operational performance.