One Ledger, Three Ways to Execute Transactions
A wants to send 20 of their 100 asset units to B. After the transaction, A will have 80 and B will have 20. The goal is simple, but the request sent to the ledger depends on the execution model.
The request could specify the intended result: “Consume this record and create records for A's 80 and B's 20.” Or it could say, “Execute this contract's transfer function,” leaving the code to compute the result. If someone else spends A's asset on A's behalf, there must also be a way to check that permission and its conditions.
Nigo addresses these differences through three execution models: Direct Cell, Native Program, and EVM. This post follows one transfer example to examine what each request contains, who constructs the next state, and how the results are applied to the same ledger.
Why a common record format has different execution models
Nigo represents asset and contract state in small record units called StateCells. Some cells hold an asset's owner and quantity; others hold values managed by a contract. From UTXO to StateCell explained why these different kinds of state share a common format.
Even after unifying the record format, a choice remains: how to turn the current records into the next ones. For a transfer signed by the owner, established ownership and quantity rules may be enough. A transaction from an authorized spender needs additional spending conditions. In a contract function call, code reads stored values and computes the changes.
Nigo therefore uses a common unit for recording state while dividing responsibility for requesting and validating changes. Three scenarios illustrate the difference. Each starts with A=100 and B=0 and ends with A=80 and B=20. They are independent comparisons, not three successive transfers of another 20. We assume a fee amount of 0 and show only changes to the asset being transferred, with no additional issuance or burning.
Direct Cell: transfers the ledger's rules can validate
First, A signs and sends the transaction directly. Assume the asset is a Native Asset, whose type and quantity the ledger manages directly, and its policy permits transfers signed by the owner. If A's 100 is held in one cell, the main contents of the transaction are:
Signer: A
Record to consume
A's asset: 100
Records to create
A's asset: 80
B's asset: 20
The request already specifies which record to use and what to leave behind. Core, the component that enforces Nigo's common ledger rules, checks that the input is still available, that the signer owns it, and that direct transfer is allowed for this asset. It also checks that the outputs are valid new records and that each asset's quantity is conserved. In this example, the input of 100 must equal the outputs of 80+20.
If the checks pass, the existing cell holding 100 is consumed and new cells holding 80 and 20 are created. Rather than changing the existing cell's quantity to 80, the transaction explicitly identifies the records to use and the records to create. Nigo calls this Direct Cell.
This kind of transfer does not require the user to deploy a separate contract or the node to execute a program. A wallet or transaction builder prepares the inputs and outputs, and Core evaluates them against the ledger's rules. Transaction size and processing limits, as well as execution metering, also apply to this path.
The scope of this path is clear. It can handle a transfer signed directly by A, but it does not allow A's input to be consumed with S's signature alone. If A has authorized S to spend, rules are needed to evaluate that approval.
Native Program: adding spending conditions to proposed changes
Now suppose A has already authorized S to spend up to 30 units of A's asset. A still owns the 100. What S has received is permission to spend some of it. S wants to use that permission to send 20 to B.
The party constructing the transaction proposes both the asset transfer and the allowance reduction.
Signer: S
Validation program: the program governing
this asset's spending approval rules
Proposed changes
Asset: A's 100 → A's 80, B's 20
Approval: A's allowance for S, 30 → 10
Core constructs the context needed for validation, including the actual records and policies referenced by the request and the signer's identity. The program receives this information and checks whether S has a valid approval, whether spending 20 stays within the allowance of 30, and whether the remaining allowance decreases to 10. A program that validates changes proposed by a transaction in this way is a Native Program.
Core still has checks to perform after the program approves. The validated records must match those that will actually change, and the changes must respect the permitted state boundaries and asset conservation rules. A change that turns 100 into 80 and 25 cannot be applied to the ledger merely because an individual program approved it.
This division of responsibility leaves room to extend spending conditions. Code implementing allowances and spending conditions belongs in the program, while common rules such as asset accounting and state access belong in Core. This is where the distinction between ownership and permission to change state, discussed in StateCell Ownership, becomes part of the execution structure.
The program checks the proposed changes using the information it receives. During validation, it does not look up arbitrary ledger state or call other contracts to construct new application state. The party constructing the transaction must therefore prepare the inputs to use and the outputs to leave afterward. Wallets, SDKs, and the node's request-building tools can help, but the transaction being signed contains a concrete proposal.
Native Programs can be written in Solidity and run in a restricted environment of the EVM, the virtual machine that executes contract code. Here, Solidity and the EVM are tools for writing and running code that checks proposed changes. Their role in execution differs from that of an ordinary contract that reads and writes storage.
EVM: computing the next state by executing a function
Consider expressing the same operation through a token contract T. Assume T's storage already records A's balance of 100, B's balance of 0, and the allowance of 30 that A granted to S. This is a separate case expressing the same transfer through contract data, not a process that moves the earlier Native Asset into T.
S's request now looks different:
Signer: S
Call target: token contract T
Call: transferFrom(A, B, 20)
Here, transferFrom is a function that lets an approved spender transfer someone else's tokens.
S supplies the source holder, recipient, and quantity. The request does not need to specify the resulting balances
of 80 and 20 or the remaining allowance of 10 as output cells in advance.
When the node's EVM executes T's code, T's function reads A's balance and S's allowance from storage, checks the transfer conditions, then computes and stores the new balances and allowance. The result defined by the example function is A=80, B=20, and allowance=10.
An ordinary EVM contract can combine storage reads and calls to other contracts to express application logic. The caller constructs the function inputs, and the contract developer implements which state the function reads and how it changes that state. This is a way of building applications around function execution.
Nigo represents this contract's stored values as StateCells as well. But T's code determines whether a number in its storage is a balance or an allowance, and when it should increase or decrease. Core does not interpret all these numbers as Native Asset quantities and check their conservation. The ledger enforces EVM execution rules and the protocol's value-transfer and fee rules; T governs its token's rules.
Send proposed changes, or specify a function to execute?
The three models differ in what a request contains and where the next state is constructed. The table compares only how they handle application state, omitting additional signature and fee information.
| Execution model | Application information in the request | How the next state is constructed |
|---|---|---|
| Direct Cell | Inputs to consume and outputs to create | Core checks changes proposed during transaction construction |
| Native Program | Inputs, outputs, and program invocation information | The program and Core check changes proposed during transaction construction |
| EVM | Call target and function inputs | The node executes the contract to compute the changes |
Direct Cell expresses transfers that the ledger's basic rules can handle in a straightforward way. Native Programs retain explicit proposed changes while extending application conditions through code. The EVM provides a development model that computes the next state through storage access and function calls.
These choices place work in different parts of the system. With Direct Cell and Native Programs, inputs and outputs must be assembled when constructing the transaction. The state to be used and the intended result are therefore visible in the transaction. With the EVM, the caller prepares the function inputs and leaves state access and result computation to contract execution.
Nigo provides all three models so that this allocation of responsibility need not be fixed to a single approach. Basic transfers use Core's rules. Native Programs add application conditions to explicitly proposed changes. The EVM processes state through function execution. The paths actually available depend on the asset's policy and the rules governing its state. For example, even when a Native Asset permits Direct transfers, S's use of an allowance granted by A requires a program to check that approval.
The three models here classify execution behavior. The protocol's detailed transaction formats make further distinctions, including signed request formats in the EVM family, so their count differs from the number of execution models.
Three paths converge on the same ledger
A request arriving at a node goes through preliminary checks for inclusion in a block. During block execution, it is checked again against the state at that point, because an input available when the transaction was prepared may have been spent in the meantime. Admission of a request and finalization in the ledger are separate stages.
At execution time, the transaction type selects the appropriate path. A Direct transaction does not pass through a Native Program and then proceed to an ordinary EVM contract call. The paths branch as follows:
A simplified view of processing valid transactions. The three paths differ in how they produce changes; those changes then follow a common process of application to a block and finalization.
While a transaction is being processed, its state changes are collected in a temporary workspace. In the successful example, A's 80 and B's 20 are applied together, along with the remaining allowance of 10 when an approval is used. For assets managed directly by the ledger, the changes can be expressed as cell consumption and creation. For EVM storage, they can be expressed as cell changes that replace values at the corresponding locations.
If program execution fails, those application changes are discarded. Execution records, fees, and other effects required to process the transaction follow each path's separate rules. A transaction rejected because its signature or inputs are invalid is also distinguished from a valid transaction whose program execution fails.
The execution path produces a receipt containing state changes and results such as success or failure. The block execution layer collects the results in the prescribed transaction order to construct the block state. Once consensus finalizes the block, each node's storage layer atomically stores the state, transactions, and execution results. Core's transaction processing, block execution, consensus, and durable storage each contribute their part to this process.
The three paths therefore share more than the StateCell record format. They also share a process for grouping each transaction's changes and applying the results to the ledger in a defined order. This allows different ways of requesting and executing changes on a common ledger.
One question about Native Programs deserves a closer look. How can the ledger verify that the changes a program approved are the same changes it will actually apply? How Does a Native Program Validate Proposed Transaction Changes? continues the spending approval example to examine how Core matches the information supplied for validation with the returned result.