Mainnet halts are rarely caused by one isolated bug. They usually expose a boundary where several subsystems made different assumptions. The May 2026 Sui halts are a good example.
Shortly after Sui rolled out Address Balance and gasless stablecoin transfers, the mainnet halted three times within roughly two days. The first two halts were tied to the boundary between Address Balance, gas charging, gas smashing, and settlement. The third surfaced during validator restarts and epoch transition, exposing a separate randomness / DKG persistence issue.
At first glance, gasless stablecoin transfer sounds like a wallet feature: let users send USDC without first buying SUI. That is a real UX improvement. It removes one of the most awkward parts of stablecoin payments on a gas-token chain.
But on Sui, that UX improvement reaches deep into the execution layer. Gas payment is not just a fee field. It involves coin objects, object versions, replay protection, failed-transaction handling, and checkpoint settlement. Address Balance changes how fungible funds move through that pipeline.
This article starts from the incidents and works backward: why Address Balance exists, how it enables gasless stablecoin transfers, where compatibility with the old coin-object world becomes risky, and what developers should take away from the rollout.
1. Why Address Balance Exists
Sui's asset model is object-oriented by default. A Coin<T> is a versioned object. Legacy payment flows are built around selecting, consuming, splitting, merging, and updating coin objects.
That model is powerful. It gives Sui strong ownership semantics and helps parallel execution: unrelated owned objects can move independently. But the same model can make simple payments feel stateful.
A wallet or payment app may need to:
- choose which coin objects fund a transfer;
- split or merge coins to match the desired amount;
- keep fresh object references;
- avoid reusing the same coin or gas object in concurrent transactions;
- make sure the user has SUI before sending a stablecoin.
For a user who just wants to send USDC, that is unnecessary friction. The user thinks in balances: "I have 100 USDC, send 10." The chain historically exposed something closer to a set of coin objects.
Address Balance adds a fungible-balance layer on top of Sui's object model. Instead of requiring every unit of a fungible asset to appear as a separate Coin<T> object, it provides a canonical balance for each (address, coin type) pair. Funds sent through sui::coin::send_funds or sui::balance::send_funds merge into the recipient's balance for that asset.
This does not replace every Coin<T> path. Coin objects, address balances, and compatibility mechanisms coexist. That is part of the design: existing wallets, contracts, SDKs, and indexers cannot all migrate at once.
The important shift is that fungible assets no longer always need to be represented as concrete coin objects in the transaction path. That is what makes a cleaner stablecoin payment UX possible.
2. How the New Payment Path Works
Address Balance looks like an account balance, but Sui does not become a traditional account-based chain. The core mechanism is the accumulator.
Simplified:
user transaction:
deposit -> emit Merge accumulator event
withdraw -> emit Split accumulator event
checkpoint / commit settlement:
collect accumulator events
aggregate by (owner, Balance<T>)
create system settlement transaction
settlement transaction:
update AccumulatorRoot dynamic fields
User transactions do not directly write the shared AccumulatorRoot. If every address-balance operation wrote that shared object directly, parallelism would suffer. Instead, user transactions emit accumulator events. Settlement transactions aggregate and persist those changes later.
The main Move framework surface is small:
balance::send_funds<T>(Balance<T>, recipient)deposits aBalance<T>into the recipient's address balance.balance::redeem_funds<T>(Withdrawal<Balance<T>>)converts a withdrawal into aBalance<T>.coin::send_funds<T>(Coin<T>, recipient)converts a coin into a balance and deposits it into address balance.coin::redeem_funds<T>(Withdrawal<Balance<T>>)converts an address-balance withdrawal into aCoin<T>.
The transaction format adds CallArg::FundsWithdrawal: reserve up to N from the sender's or sponsor's Balance<T>. During execution, this input becomes a Move-side sui::funds_accumulator::Withdrawal<Balance<T>>. It is not an ordinary owned object. It is a withdrawal handle. Only after it is redeemed through redeem_funds does it produce a Split accumulator event.
This gives the scheduler something it can reason about before execution: the maximum possible outflow. It can reserve funds conservatively without locking an entire account.
Gasless stablecoin transfer is built on top of this machinery. For allowed stablecoin types, a qualifying peer-to-peer transfer can execute with:
gasPayment = []
gasPrice = 0
gasBudget = 0
That does not mean arbitrary free computation. Gasless transfers are intentionally narrow. The token must be allowed by protocol configuration. The PTB shape must match a small set of balance and coin operations. The transaction cannot write ordinary objects. Input coins must be consumed or converted into address balances. There is also a minimum transfer amount, and gas-paying transactions are prioritized during congestion.
Those boundaries are security assumptions. Without them, gasPrice = 0 would become a generic free-computation and spam surface.
Address-balance gas payment also introduces a replay-protection requirement. A transaction that pays gas from address balance may have no gas coin object in gas_data.payment. If a stateless transaction has no owned object input anchoring it, it needs TransactionExpiration::ValidDuring, a chain identifier, and a nonce so it cannot be replayed across time or networks.
This is the tradeoff: the user no longer needs to manage SUI gas coins for simple stablecoin transfers, but the execution layer must now reason about balance withdrawals, stateless transaction validity, and deferred settlement.
3. Where Compatibility Gets Risky
Sui cannot switch the whole ecosystem from coin objects to address balances overnight. Existing SDKs, wallets, indexers, and Move contracts still speak in Coin<T> and object references. The transition therefore needs compatibility.
Some compatibility is straightforward. Balance APIs now need to distinguish total balance, coin object balance, and address balance. A wallet that only scans owned Coin<T> objects can undercount a user after funds arrive through address balance. Indexers also need to process accumulator events, not only object diffs: Split is address-balance outflow, and Merge is address-balance inflow.
Some compatibility is more subtle. Existing contracts that accept Coin<T> can still be called by redeeming a coin from address balance first:
const [coin] = tx.moveCall({
target: '0x2::coin::redeem_funds',
typeArguments: ['0x2::sui::SUI'],
arguments: [tx.withdrawal({ amount: 1_000_000_000n })],
});
tx.transferObjects([coin], recipient);
Conversely, an old flow that produces a Coin<T> can fold it back into address balance through coin::send_funds.
The highest-risk compatibility layer is coin reservation.
Traditional gas payment uses concrete SUI coin objects:
gas_data.payment = [Coin<SUI> object refs]
When there are multiple gas coins, the execution layer performs gas smashing: it combines multiple gas coins into one target coin, deletes the other gas coins, and charges gas from the target coin.
Address Balance adds another shape:
gas_data.payment = [real coin object, synthetic reservation object, ...]
The synthetic reservation object is not a real on-chain coin. It is an ObjectRef-shaped compatibility value whose digest encodes an address-balance withdrawal reservation. After parsing it, the execution layer treats it as reserved SUI from the sender's address balance.
That is where assumptions start to overlap. Gas smashing was built around coin objects. Coin reservation looks like an object reference, but it is not an ordinary owned object. It can enter paths originally designed for gas coins, while its economic effect comes from address balance.
This is also why explorers and RPCs can be easy to misread. suix_getCoins or an explorer UI may show a coinObjectId, but that value can come from compatibility rather than from a user transaction creating or transferring a normal owned Coin<T>.
A mainnet example illustrates the issue. In transaction ECjUCiAP9YMYFyQrEKUb2JVyWovPyqN6rPGXRz42pUQn, the user transaction had:
objectChanges = []balanceChanges: sender-100000 USDC, recipient+100000 USDCgasData.payment = [],gasPrice = 0,gasBudget = 0- accumulator events for
Balance<USDC>
The recipient later appeared in suix_getCoins with a coinObjectId whose previousTransaction was EvgW7KsrN8jaBUkuCdeo4NfiB9baZDyGTXidwxFbt4BV, a system settlement transaction. That settlement transaction called accumulator_settlement::settlement_prologue and accumulator_settlement::settle_u128, creating or modifying accumulator dynamic fields under 0x...0acc. Meanwhile, suix_getOwnedObjects filtered by 0x2::coin::Coin<USDC> returned empty for the recipient.
That combination is closer to an Address Balance RPC compatibility representation than to a normal coin object created by the user transaction.
The compatibility layer is useful. It keeps older coin-object flows working while address balances roll out. But it also brings address-balance side effects into execution logic that previously handled coin object mutation. That boundary is exactly where the first two halts occurred.
4. What Actually Broke
The public timeline is short:
- 2026-05-28, about 07:00-13:30 PT: mainnet halt. A boundary bug between v1.72 Address Balance and gas charging / gas smashing triggered settlement underflow.
- 2026-05-29, about 05:00-08:30 PT: second halt. The interim fix covered only part of the
InsufficientFundsForWithdrawshape. Another cancellation reason could maskInsufficientFundsForWithdraw, and the same class of underflow appeared again. - 2026-05-29, about 13:30-19:20 PT: third halt. Validators restarted to deploy the fix, exposing a randomness / DKG state persistence bug. Epoch change could not complete.
The first incident can be summarized as:
TX1:
drain sender address balance to 0
TX2:
gas payment = [real coin A, real coin B, address-balance reservation R]
scheduler/execution sees address balance no longer enough
TX2 is marked InsufficientFundsForWithdraw
bug:
TX2 still runs gas smashing path
reservation R emits a Split accumulator event
transaction fails, but Split event reaches checkpoint settlement
settlement:
current balance = 0
merge = 0
split = R
checked arithmetic underflows
system settlement transaction aborts
every validator hits the same deterministic abort
The important point is not that Sui allowed an invalid balance update. It did not. Checked arithmetic prevented the underflow from passing silently. The problem was where the failure happened: inside a system settlement transaction. Once that transaction aborted deterministically, honest validators stopped at the same checkpoint.
This is a liveness failure, not a theft-of-funds failure. Funds remained protected, but the chain stopped making progress.
The bug was also publicly triggerable. It did not require validator keys or admin privileges. It required transactions competing for the same address balance, one transaction entering InsufficientFundsForWithdraw, and a hybrid gas payment containing both real coins and a reservation. This is not the same as a simple "balance < amount" case, which would fail before consensus. The relevant shape involved concurrent transactions competing for the same address-balance reservation space.
The first hotfix pruned address-balance entries from gas payment once a transaction entered an IFFW early abort, while keeping real coins. The second halt showed that this was too narrow. A transaction can have multiple early cancellation reasons; if the fix only checks the surfaced error, IFFW can be masked. The more robust fix treats IFFW as a reason to bypass the executor / gas-smashing path and produce deterministic zero-gas failure effects.
The third halt was different. It came from randomness / DKG state during epoch change. Validators restarted to deploy the second fix. DKG participation for the next epoch did not meet the threshold, so randomness was disabled as designed. A latent persistence bug meant the "DKG failed/disabled" verdict was not remembered correctly after later restarts. Randomness-dependent transactions could neither execute nor be cancelled, the queue could not drain, and end-of-epoch logic waited for a DKG that would never complete.
The emergency fix added a force-epoch-close operator lever. That detail matters because production reliability is not only about the new feature. It is also about emergency upgrades, validator restarts, low-frequency epoch transitions, and operational recovery.
5. What Developers Should Take Away
The point of this analysis is not that gasless stablecoin transfers were a bad idea. The demand is real. Payment UX matters. Stablecoin users should not need to understand gas coins before sending dollars.
The lesson is that payment UX can become consensus-critical when it changes gas payment and settlement. The implementation bar has to match that risk.
For wallets and payment apps:
- Treat Address Balance and coin objects as coexisting asset representations. Show total balance, coin balance, and address balance clearly so users do not think funds have disappeared.
- Precheck gasless eligibility. Do not set
gasPrice = 0just because the token is USDC. Validate PTB shape, allowlisted functions, absence of ordinary object writes, minimum transfer amount, and gas budget. - For address-balance gas payment, handle
ValidDuringand nonce explicitly. Do not reuse the same nonce for distinct stateless transactions. - In sponsored transactions, do not assume
tx.gasis always the right abstraction. Address-balance gas payment uses empty gas payment (setGasPayment([])), whiletx.gasrepresents the gas coin argument. Prefer higher-level APIs such astx.coin()andtx.balance()where applicable, and review anyGasCoinusage explicitly.
For indexers and deposit monitors:
- Process accumulator events. Balance-change algorithms that only inspect object diffs are incomplete.
- Do not require
objectChangesto be non-empty. For gasless stablecoin transfers, the main signal should bebalanceChanges:owner == watched address,coinType == target coin type,amount > 0means incoming funds, andamount < 0means outgoing funds. - Treat
objectChanges, compatibilitycoinObjectIds, and settlement transactions as enrichment or reconciliation signals, not as the only evidence of payment.
For payment businesses:
- Do not monitor only whether a transaction digest was submitted successfully. Monitor checkpoint progression, finality latency, epoch transitions, randomness/DKG state, and gasless rejection rate.
- Keep a paid fallback. During congestion, gas-paying transactions are prioritized over gasless stablecoin transfers. High-value or SLA-sensitive payments may need a paid path.
For security teams:
- Model failed transaction side effects explicitly. In this incident, the dangerous path was not a successful withdrawal. It was a failed path that still left a settlement-impacting accumulator event.
- Treat gas payment as a consensus boundary. It handles DoS protection, fee conservation, object lifecycle, balance deduction, and failed-transaction behavior.
- Preserve replay determinism during hotfixes. Nodes replaying historical checkpoints under different binaries must still produce the same effects.
6. Conclusion
Address Balance is a meaningful protocol improvement for payment-oriented use cases. It addresses real friction: coin object UX, concurrent gas coin management, and the need for users to hold SUI before transferring stablecoins. Gasless stablecoin transfer is not just product language. It depends on concrete execution-layer mechanisms: allowlist, PTB shape validation, address-balance withdrawal, replay protection, zero gas budget, and accumulator settlement.
The May 2026 halts show the cost of making that improvement safely. The first two incidents came from address-balance reservations entering gas smashing in a way that let failed transactions leave settlement-impacting accumulator events. The third showed that emergency fixes themselves depend on validator restart and epoch-close paths, which are rare but critical.
Gasless transfers are worth building. Better payment UX is worth building. But the return is not free. What Sui had to give in return was a much higher burden on execution-layer invariants, gas accounting, settlement design, protocol gating, and operational recovery.
That is the real lesson of Address Balance: the closer a UX improvement gets to gas payment and settlement, the more it must be treated as core protocol engineering, not as an ordinary product feature.
FAQs
What is Address Balance on Sui?
Address Balance is a fungible-balance layer added on top of Sui's object model. Rather than requiring every unit of a fungible asset to exist as a discrete Coin
How do gasless stablecoin transfers work on Sui?
Qualifying peer-to-peer stablecoin transfers can set gas price, gas budget, and gas payment all to zero. The token type must be protocol-allowlisted, the transaction must match a narrow set of allowed PTB shapes, and no ordinary objects can be written. Gas is effectively covered by the protocol for these transfers, removing the requirement for users to hold SUI before sending stablecoins.
How should indexers and deposit monitors handle Address Balance transactions?
They should process accumulator events rather than relying solely on object diffs. For gasless stablecoin transfers, objectChanges may be empty, so balanceChanges is the primary signal for detecting incoming or outgoing funds. Compatibility coinObjectId values from settlement transactions should be treated as reconciliation signals rather than authoritative evidence of payment.
What is the key security lesson from the Sui halts?
Failed transactions can still produce side effects. In this case, a transaction that entered an insufficient-funds early abort still emitted a Split accumulator event, which reached checkpoint settlement and caused underflow. Any system where gas payment intersects with deferred settlement needs to explicitly model what failed transaction paths leave behind, not just what successful ones produce.
References
- Sui Foundation, "Sui Launches Gasless Stablecoin Transfers," 2026-05-20: https://blog.sui.io/sui-launches-gasless-stablecoin-transfers/
- Sui Docs, "Gasless Stablecoin Transfers": https://docs.sui.io/develop/transaction-payment/gasless-stablecoin-transfers
- Sui Docs, "Using Address Balances": https://docs.sui.io/onchain-finance/asset-custody/address-balances/using-address-balances
- Sui Docs, "Migrating to Address Balances": https://docs.sui.io/onchain-finance/asset-custody/address-balances/migrate-address-balances
- SIP-58, "Sui Address Balances": https://github.com/sui-foundation/sips/blob/main/sips/sip-58.md
- Sui Foundation, "Sui Mainnet Halts Resolved After Major Upgrade," 2026-05-31: https://blog.sui.io/sui-mainnet-halts-resolved-after-major-upgrade/



