Migrating from Coin to Address Balances
Address balances introduce a canonical balance system for fungible assets tied to Sui addresses. This supplements the Coin<T> model with direct address-owned balances, simplifying transaction construction and eliminating coin selection complexity.
For the full specification, see SIP-58: Sui Address Balances. For implementation details on sending, withdrawing, and paying gas from address balances, see Using Address Balances.
Read this first
This rollout initially has very limited impact:
- Nothing is deprecated or removed. All transactions that are currently valid continue to be valid. Coin objects are not forcibly migrated to address balances.
- No contracts need to be rewritten. Contracts can continue to accept
Coin<T>and&mut Coin<T>as before. - Coins can still be sent through
transfer::public_transferor theTransferObjectscommand.
Impact for wallets, exchanges, and custody providers
Anyone operating a wallet (custodial, non-custodial, or exchange wallets) might be impacted by this change. Users can now send funds to your address through send_funds() instead of transferring a coin object. If a wallet receives funds this way, but does not have proper support for address balances, the user will not be able to see or access the funds. The user might then believe the funds have been lost. The funds cannot actually be lost, but this can cause confusion for affected users.
Initially, this should not happen often because most of the ecosystem will continue to transfer coin objects. However, because there is no way to prevent funds from arriving at any given wallet via an address balance transfer, wallet implementors should be prepared.
User funds can also become split across both coins and address balances. Balance queries (through JSON-RPC, gRPC, or GraphQL) show the combined total. To send funds that include the address balance portion, you must either:
- Use
coinWithBalancefrom the TypeScript SDK (v2+), which automatically draws from both sources. - Implement manual withdrawal logic as described in Using Address Balances.
Changes to balance queries
Balance query responses now include address balance information alongside coin object totals.
JSON-RPC
The suix_getBalance and suix_getAllBalances methods now return a fundsInAddressBalance field. The totalBalance field includes both coin objects and address balance funds:
{
"coinType": "0x2::sui::SUI",
"coinObjectCount": 2,
"totalBalance": "99998990120",
"lockedBalance": {},
"fundsInAddressBalance": "5000000"
}
To get only the coin-based balance, subtract fundsInAddressBalance from totalBalance.
gRPC
The GetBalance and ListBalances methods from StateService now return separate fields for coin and address balances:
coinBalance: Total held in coin objects.addressBalance: Amount held in address balance.balance: Sum of the two.
{
"balance": {
"coinType": "0x2::sui::SUI",
"balance": "99998990120",
"addressBalance": "5000000",
"coinBalance": "99993990120"
}
}
GraphQL
The balance and balances fields on address types now include addressBalance, coinBalance, and totalBalance fields. See GraphQL Reference - IAddressable.balance for the full schema.
Changes to balance computation from checkpoint data
It is recommended that you use the balance changes provided by JSON RPC or gRPC.
If you compute balance changes from checkpoint data, you must now process accumulator events from TransactionEffects in addition to coin object diffs.
The Sui framework provides a helper for this process:
use sui_types::balance_change::{derive_balance_changes, BalanceChange};
let balance_changes: Vec<BalanceChange> = derive_balance_changes(
&effects,
&input_objects,
&output_objects,
);
Each BalanceChange contains the address, coin type, and a signed amount (negative for spent, positive for received).
See the reference implementation for the full algorithm.
Accessing accumulator events
Accumulator events are embedded in TransactionEffects. Access them through the TransactionEffectsAPI trait:
use sui_types::effects::TransactionEffectsAPI;
let events = effects.accumulator_events();
Each event contains the target address, a balance type, an operation (Split for withdrawal, Merge for deposit), and the amount. See sui-types/src/effects/mod.rs for the full API.
Backward compatibility
Existing contracts
Contracts that accept Coin<T> or Balance<T> remain callable without changes. The redeem_funds functions (in the sui::coin and sui::balance modules respectively) convert withdrawals to the expected types within the PTB. For example:
inputs: [withdrawal<0x2::sui::SUI>(100)]
commands:
0: 0x2::coin::redeem_funds(inputs(0))
1: 0x2::transfer::public_transfer(result(0))
Legacy clients
A JSON-RPC compatibility layer presents synthetic coin objects representing address balance reservations. This preserves basic functionality for clients that cannot upgrade but should not be relied upon for new development.
Converting existing coins to address balances
Migration is optional. The SDK automatically selects coins or address balances as needed. To consolidate existing coin objects into an address balance, see Converting Existing Coins to Address Balances in the usage guide.