Why Did a Small Transfer Fail When the Balance Was Enough?
Recording an asset on a shared ledger and making it usable from both Native programs and the EVM does not settle whether a user can actually spend it. A transfer can be small and the balance sufficient, yet the transfer can still be blocked because that balance is spread across too many pieces.
On September 28, 2026, we confirmed and fixed such a defect in nigo-protocol. Once one owner held 257 unspent Cells of the same asset, even balance queries and small transfers through the standard path failed. A Cell is a unit of ledger state that records an owner and an amount. Here, 257 was the number of pieces an owner held, not the number of asset types or the amount being transferred.
A rule intended to bound the work of a single transaction was also limiting how many pieces a user could accumulate through earlier receipts. This post looks back at that development record. Follow-up improvements are distinguished using the implementation confirmed through October 19.
Holding 1,000 X but Unable to Send 20 X
For this example, consider a wallet separate from the 100 X scenario in the preceding post. A holds 1,000 units of asset X, and B has none. A's holdings are spread across 500 Cells of 2 X each. To send B 20 X, A needs to consume just 10 of them.
Assume the transfer's authorization conditions are satisfied, with no additional issuance or burning. The fee policy is ZERO, so the calculation below covers only X inputs and outputs. A zero fee does not remove gas limits on execution, limits on input counts, or the checks that prevent the same transaction from being applied again.
After the transfer, A retains 490 existing Cells totaling 980 X. B receives a new Cell containing 20 X. Their combined holdings remain 1,000 X. There is no reason for this operation to consume all 500 of A's Cells.
Before the fix, however, the standard policy queried all of an owner's Cells while preparing inputs. This query did not mean “return a few entries you found.” It meant “confirm that these are all the entries in this range.” Its maximum was 256 entries, so finding a 257th caused it to fail. The example wallet would therefore be blocked at the complete-range query, before it could select the 10 Cells it needed.
The figure of 500 Cells is an explanatory model. The actual defect was reproduced at 257 Cells, and the fix was separately verified with holdings split into even more pieces.
A Balance and an Input List Answer Different Questions
The central fix was not to raise the query limit. It was to separate three questions that wallets and policies ask of the ledger.
| Question | Answer needed |
|---|---|
| How much is there in total? | An exact balance from the same execution view |
| What should this transaction spend? | A bounded set of candidates and the inputs actually selected from it |
| Is this the entire range? | Complete range evidence showing that no entries are missing |
A derived balance index answers the first question. It maintains sums of the original Cell amounts by asset and owner. If A holds 500 Cells, returning 512 X—the sum of just the first 256—as the balance would be wrong. The balance must be exactly 1,000 X.
The second question uses a limited number of candidates ordered by ID. In the example, selecting 10 of those 2 X Cells provides the required 20 X. Receiving candidates does not grant authority to consume them. The existing validation process still checks whether the actual inputs remain available and whether signatures and asset policies authorize their consumption.
The third question remains intact. Some policies need to examine an entire range to evaluate a condition. Returning only part of it while treating it as complete could make an existing entry appear absent or leave an amount unaccounted for. That is why the existing complete-range query was not silently changed into a partial query.
Moving the standard path to exact balances and bounded selection does not mean every custom policy can now handle 257 or more Cells. Policies that request a complete-range query still operate under its contract and limit. Selecting some entries and proving that a range is complete cannot substitute for each other.
Too Few Candidates Does Not Mean Too Little Balance
Input selection is simple when every piece holds the same amount. In a real wallet, though, small pieces may appear early in the candidate order and larger pieces later. Even if the first batch of candidates cannot cover the requested amount, that does not establish that the wallet's total balance is insufficient.
We therefore need to distinguish an insufficient candidate selection, an insufficient total balance, and a transaction that exceeds the input-count limit. Checking the balance and endlessly retrying the same candidates is not enough. The transaction needs to find other usable inputs, or the owner needs to combine smaller pieces through an authorized transfer back to themselves.
This self-consolidation is also an ordinary transaction. It transfers the combined value of several inputs back to the same owner, so the original owner or policy authorization is still required. It does not give an administrator permission to combine someone else's assets or bypass a policy's approval requirements. Per-transaction input and gas limits remain in place.
If A consolidates some of the 980 X left after sending 20 X, the total does not change. What changes is the number of inputs needed for a later transaction. This does not guarantee that every large transfer will succeed in one transaction while receipts and concurrent spending continue. Fixing a defect that blocked small spends because of accumulated holdings is different from removing per-transaction limits.
Exact Balances Must Change Alongside Their Source
Maintaining a derived balance makes queries easier, but also creates a responsibility to keep that balance aligned with its source. Consuming and creating Cells, and adjusting the corresponding balances, are part of one atomic storage operation. Partial application is not allowed.
The same principle applies during execution. From the execution view of A's 20 X transfer, A's 980 X and B's 20 X must be visible together. If those asset changes are rolled back, the balance changes must be rolled back as well. If transaction preparation relied on an observed balance, final validation also checks that the observation is still valid. An input list from one point in time cannot be combined with a balance from another.
Adding this index does not turn the ledger into an account-balance model. Individual Cells remain the source of truth; the aggregate is calculated from them. A balance-query result cannot itself be consumed as a transaction input. Observing “there is 1,000 X” and transferring value by “consuming these 10 Cells” serve different roles.
An offline audit and repair tool that recalculates sums from the source and compares them with the index was also implemented as a September 29 follow-up. This tool does not scan the entire ledger or automatically repair it during every transaction. What to use as the recovery authority when derived information is wrong deserves a separate discussion.
Could Small Transfers Work Again?
Post-fix verification repeatedly used ordinary receipts to bring an individual account and an EVM contract account to 513 Cells each. It then checked exact balance queries, partial spends through Native and EVM execution, the public consolidation entry point, and state preservation after a clean restart. These were local regression and acceptance results across RDB and RocksDB storage, with ZERO and FIXED fee policies.
Including contract accounts mattered. An individual might be able to sign directly and manually select inputs as a workaround, but that would not solve the problem for a contract without such an interface. Spending had to work again through the normal paths used by people and applications.
This defect did not make assets disappear. It was an availability defect that prevented assets already on the ledger from being queried or used through the normal path. The fix did not establish readiness for every policy and operating environment. Its significance was separating accumulated holdings from the work of the current transaction while preserving exact balances and existing authorization rules.
The shared asset model and connections between execution domains now take us from “the same asset can be used” to “the inputs needed to spend it can actually be prepared.” Next, we need to look more closely at where a transaction fails and which effects remain. A transaction rejected while preparing inputs and one that fails during execution after inclusion in a block may leave different effects. Does rolling back asset changes also mean rolling back fees and replay-prevention state? That is the next question.