Freezing the node executable before writing a new asset policy
Suppose you want to add a condition to an asset-transfer program: each account can use only a limited amount during a given interval, then use its budget again in the next interval. If every new condition requires changing the node's code and distributing a new executable, extending the business rules remains tied to changing the ledger engine.
The article on Native Program transition validation explained how a program evaluates usage conditions while Core checks that its decision matches the actual ledger changes. To test whether that division of responsibilities works for new applications, we need a stronger condition than running an example the node already knows.
On September 27, 2026, we froze the node executable and started it before writing a new policy. We then checked both successful and rejected requests against that same executable. This article asks what we had to add to support the new rules, and what could stay unchanged.
A budget that becomes available again every 32 blocks
The new policy is called WindowBudget. It allows each account to use a budget of up to 100 within a window of 32 blocks. Block height is a block's position in the growing ledger. One window in this example runs from height 96 through 127; the next runs from 128 through 159. We do not assign a fixed number of minutes to those 32 blocks.
The policy has a consume operation that only records budget usage, and a transfer operation that sends assets to another person. Both operations draw on the same account's budget. However, consume(40) only records 40 units of usage. It neither destroys 40 asset units nor moves them to someone else.
Consider A holding 200 units of asset X and B holding none. There is no additional issuance or burning of X, and A has not used any budget in the current window. Transfers of this asset must pass the budget policy's validation. NIGO for fees is funded separately and is not included in the X balances below.
The amounts and heights below are an illustration of the actual policy rules. Assume the first three requests are both prepared and executed within heights 96–127.
| Request and result | Window usage | X balance A / B |
|---|---|---|
| Record 40 usage: success | 40 | 200 / 0 |
| Transfer 50 to B: success | 90 | 150 / 50 |
| Add 20 usage: rejected | 90 | 150 / 50 |
| Record 20 in the next window: success | 20 | 150 / 50 |
The third request does not fail for lack of assets. A still holds 150 X, but adding 20 to the window's usage of 90 would exceed the budget limit of 100. The policy rejects the change and preserves the previous usage and X balances. X's total supply remains 200 through all four steps. What remains unchanged here is the business state; this does not mean that a failed signed transaction leaves no fee or processing record.
Starting a new window does not trigger a job that immediately clears every account's record. The first successful write in the new window calculates and stores that window's usage. Reading an old record does not create or reset state.
A request may also be prepared in one window and execute in another. The last row therefore represents a request prepared again after reaching the new window. It is not a promise that an old proposed transition will automatically adapt to the new window and succeed.
The node had to exist before the policy
Would a successful run of this example alone prove extensibility? If the node already contained dedicated WindowBudget handling code, we might simply be running an existing policy under a new name or configuration.
We therefore fixed the order of the experiment. First, we built the node executable, a JAR, from the source selected for testing and recorded a hash identifying its contents. Only after starting the node process and confirming that it was ready to accept requests did we write the new policy.
We wrote the policy in Solidity, defined the data format for per-account usage and its calling interface, compiled the program, and supplied it through the public installation path. Creating the asset used the creation-approval process of the existing registry program.
We did not rebuild the node during this process. We added no Java code to interpret the new business rules and seeded no policy-specific data into the initial state. Each time we started the node, we checked the frozen executable's hash to confirm that it was the same file.
In this experiment, a “new policy” therefore means more than a new name. Business code and a state format added after the engine was ready worked through the existing installation, execution, and validation paths.
Unfamiliar business rules still face ledger validation
If the node does not know what WindowBudget means, can the program freely approve invalid changes?
This is where the business rules and the ledger's common rules must be separated. The policy interprets the 32-block windows, the limit of 100, and the condition that both functions share the same usage counter. The reason an account with usage of 90 cannot add another 20 also lives inside the policy.
Core obtains the current state and execution context from the ledger and supplies them to the program. Its common rules check transaction signatures, input validity, authority over the state being changed, and the match between the effects the program authorizes and the actual inputs and outputs. Passing a program's own business checks does not permit it to change another asset's inputs or unauthorized state.
This policy's configuration and usage are stored as bytes in Cells, the common state format. The Solidity program interprets those bytes as business values such as the window and its usage. Without adding a business-specific parser to the node, Core could still check ownership, modification authority, and transition rules for those records.
The example's transfer of 50 X requires two results together. The asset balances must change from 200/0 to 150/50, and A's window usage must increase from 40 to 90. The policy checks the relationship between these changes; Core checks that the authorized result matches the actual ledger changes. This division lets new business rules coexist with common ledger rules.
Rejection and restart told us more than one successful call
Installing a policy and calling it once does not establish that it manages usage correctly. An implementation that ignores previous usage could also pass its first request.
The checks included both permitted requests and requests that exceeded the budget. Over-budget requests were signed and submitted, and their failure results were checked. We then read the business state again to verify that rejected usage had not been retained. In the next window, we prepared fresh requests and checked that the budget could be used again.
Native requests and EVM calls through ethers and viem also checked the same policy state. The budget shared by consume and transfers in the example is not a separate allowance for each entry point. The rules must use the same account's business state regardless of the request path. Rather than detailing the connections between execution domains here, the experiment asked whether a new rule could also be used consistently through the existing calling paths.
The experiment ran four combinations: H2 and RocksDB storage, each with ZERO and FIXED monetary-fee policies. After the initial run in each combination, we shut down the node normally and reopened the same database. We read the stored state, processed further transactions, and compared the results again after a second normal restart.
This checks that the new policy's business state does not exist only in one process's memory. Normal restarts do not establish durability under a power cut. Nor does this test policy become a standard product verified for every production environment.
What comes after proving that a rule can be added?
The experiment established a specific result: we could install new business code on a frozen engine, process the requests it allowed and rejected, and continue using its stored business state after normal restarts.
Adding a new rule and processing large inputs within the available execution budget are separate questions. Cost checks from the same period found that preparing a proposed transition for a transaction with many inputs reached the execution limit and required further improvement. We keep those results separate so that successful extension is not read as a performance guarantee. The evidence discussed here concerns the engine frozen on September 27 and the policy written afterward.
Looking at this extension mechanism alongside the asset model raises another question. How can common rules apply to both the base coin NIGO and other assets? Which path should serve owner transfers that need no separate policy validation?
The unified model that treats NIGO as one asset among others takes up those questions. Building on a program structure that can accept new rules, it examines how straightforward owner transfers and policy-validated transfers can coexist on one ledger.