# Using Address Balances

*[Documentation index](/llms.txt) · [Full index](/llms-full.txt)*

Address balances provide a canonical balance for each coin type tied directly to a Sui address. Funds sent through `sui::coin::send_funds` and `sui::balance::send_funds` merge automatically into a single balance per coin type, with no object management required. For the full specification, see [SIP-58: Sui Address Balances](https://github.com/sui-foundation/sips/blob/main/sips/sip-58.md).

## Sending funds

Use `send_funds` to deposit directly into a recipient address balance. The recipient balance increases without creating new objects, and multiple deposits from different senders all merge into one balance.

### TypeScript SDK

To deposit tokens into a recipient's address balance use `tx.balance()` with `balance::send_funds` instead of creating a coin object:

```tsx
const tx = new Transaction();

tx.moveCall({
	target: '0x2::balance::send_funds',
	typeArguments: ['0x2::sui::SUI'],
	arguments: [tx.balance({ balance: 1_000_000_000n }), tx.pure.address('0xRecipientAddress')],
});
```

Transactions built using only `tx.balance()` and eligible Move calls like `send_funds` and `redeem_funds` might qualify for zero gas fees when the network supports it.

### CLI

To send funds using [Sui CLI](/references/cli/ptb):

```bash
# Send from gas coin to address balance
sui client ptb \
    --split-coins gas '[5000000]' \
    --assign coin \
    --move-call 0x2::coin::send_funds '<0x2::sui::SUI>' coin @<RECIPIENT_ADDRESS>

# Send from another coin
sui client ptb \
    --split-coins @<COIN_ID> '[5000000]' \
    --assign coin \
    --move-call 0x2::coin::send_funds '<COIN_TYPE>' coin @<RECIPIENT_ADDRESS>
```

| **Flag** | **Description** |
|----------|----------------|
| `--to` | Recipient address. |
| `--amount` | Amount to send in MIST. |
| `--coin-type` | Coin type to send. Defaults to `0x2::sui::SUI`. |
| `--all-coins` | Send all coin balance of the specified type. Cannot combine with `--stateless`. |
| `--stateless` | Build a stateless transaction using address balance only (no owned object inputs). Uses `ValidDuring` expiration for replay protection. |

By default, the command selects coins first and falls back to address balance if coins are insufficient. With `--stateless`, it draws exclusively from the address balance.

### Move functions

The Sui framework provides `send_funds` in both the `balance` and `coin` modules:

```move
// Send a Balance<T> to an address balance
public fun send_funds<T>(balance: Balance<T>, recipient: address)

// Send a Coin<T> to an address balance (converts to balance internally)
public fun send_funds<T>(coin: Coin<T>, recipient: address)
```

Learn more at [sui-framework/sources/balance.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/balance.move#L102)) and [sui-framework/sources/coin.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/coin.move#L176).

## Withdrawing funds

Create a withdrawal input and redeem it to get a `Coin<T>`:

```tsx
const tx = new Transaction();

const [coin] = tx.moveCall({
	target: '0x2::coin::redeem_funds',
	typeArguments: ['0x2::sui::SUI'],
	arguments: [tx.withdrawal({ amount: 1_000_000_000 })],
});

tx.transferObjects([coin], '0xRecipientAddress');
```

Get a `Balance<T>` directly:

```tsx
const [balance] = tx.moveCall({
	target: '0x2::balance::redeem_funds',
	typeArguments: ['0x2::sui::SUI'],
	arguments: [tx.withdrawal({ amount: 1_000_000_000 })],
});
```

For coin types other than SUI, pass the `type` parameter:

```tsx
const [coin] = tx.moveCall({
	target: '0x2::coin::redeem_funds',
	typeArguments: ['0xPackageId::module::USDC'],
	arguments: [tx.withdrawal({ amount: 1_000_000, type: '0xPackageId::module::USDC' })],
});
```

To use address balance funds in a transaction, withdraw them and redeem them using `Coin<T>` or `Balance<T>`.

### TypeScript SDK

If you are not explicitly selecting coin objects for gas or as transaction arguments, the TypeScript SDK automatically checks both coin objects and address balances and draws from the correct sources.

```typescript

const tx = new Transaction();
tx.transferObjects([tx.coin({ balance: requiredAmount })], recipient);
```

### TypeScript SDK: Manual withdrawals

For explicit control over address balance withdrawals, use [`tx.withdrawal()`](https://sui-typescript-docs-git-mh-address-balance-docs-mysten-labs.vercel.app/sui/transactions/reference#address-balance-withdrawals). The withdrawal must be redeemed through `redeem_funds` to produce a usable `Coin<T>` or `Balance<T>`:

```typescript

const tx = new Transaction();

// Withdraw SUI from address balance and redeem to Coin
const [coin] = tx.moveCall({
	target: '0x2::coin::redeem_funds',
	typeArguments: ['0x2::sui::SUI'],
	arguments: [tx.withdrawal({ amount: 1_000_000_000 })], // 1 SUI in MIST
});

// Withdraw a custom coin type
const coinType = '0xPackageId::module::CoinType';
const [customCoin] = tx.moveCall({
	target: '0x2::coin::redeem_funds',
	typeArguments: [coinType],
	arguments: [tx.withdrawal({ amount: 1_000_000, type: coinType })],
});

// Transfer to recipient
tx.transferObjects([coin], recipient);
```

:::info

`tx.withdrawal()` creates a `Withdrawal<Balance<T>>` capability that must be redeemed to create a coin or balance. Use `0x2::coin::redeem_funds()` or `0x2::balance::redeem_funds()` to do this. The `coinWithBalance` intent does this automatically.

:::

### Rust SDK: Manual withdrawals

Use [`FundsWithdrawalArg`](https://github.com/MystenLabs/sui/blob/main/crates/sui-types/src/transaction.rs) to withdraw from an address balance in Rust. The helper `FundsWithdrawalArg::balance_from_sender(amount, balance_type)` provides a convenient constructor:

```rust
use sui_types::transaction::{FundsWithdrawalArg, WithdrawalTypeArg, Reservation, WithdrawFrom};

let withdrawal_arg = FundsWithdrawalArg {
    reservation: Reservation::MaxAmountU64(amount),
    type_arg: WithdrawalTypeArg::Balance(coin_type),
    withdraw_from: WithdrawFrom::Sender,
};
```

In your PTB, pass the withdrawal input to either:

- `0x2::balance::redeem_funds<T>()` to obtain a `Balance<T>`.
- `0x2::coin::redeem_funds<T>()` to obtain a `Coin<T>`.

You can have multiple `FundsWithdrawalArg` inputs in a PTB, even for the same coin type:

```rust
let mut builder = ProgrammableTransactionBuilder::new();

let withdraw_arg = FundsWithdrawalArg::balance_from_sender(
    withdraw_amount,
    sui_types::gas_coin::GAS::type_tag(),
);
let withdraw_input = builder.funds_withdrawal(withdraw_arg).unwrap();

// Redeem to coin
let coin = builder.programmable_move_call(
    SUI_FRAMEWORK_PACKAGE_ID,
    Identifier::new("coin").unwrap(),
    Identifier::new("redeem_funds").unwrap(),
    vec!["0x2::sui::SUI".parse().unwrap()],
    vec![withdraw_input],
);

builder.transfer_arg(recipient, coin);
```

Learn more at [sui-types/src/programmable_transaction_builder.rs](https://github.com/MystenLabs/sui/blob/main/crates/sui-types/src/programmable_transaction_builder.rs).

### Splitting and joining withdrawals

You can split and merge withdrawals within a PTB:

```move
// Split a sub-withdrawal from an existing withdrawal
public fun withdrawal_split<T: store>(withdrawal: &mut Withdrawal<T>, sub_limit: u256): Withdrawal<T>

// Join two withdrawals together (must have same owner)
public fun withdrawal_join<T: store>(withdrawal: &mut Withdrawal<T>, other: Withdrawal<T>)
```

## Paying for gas from address balances

With address balance gas payments, pass an empty array to `setGasPayment`. This enables fully offline transaction building because there are no coin object versions to look up:

```typescript
const tx = new Transaction();
tx.setGasPayment([]); // Empty array = use address balance for gas
```

In Rust, set `gas_data.payment` to an empty vector and use [`ValidDuring`](https://github.com/MystenLabs/sui/blob/main/crates/sui-types/src/transaction.rs) expiration:

```rust
TransactionData::V1(TransactionDataV1 {
    kind: tx_kind,
    sender,
    gas_data: GasData {
        payment: vec![],  // Empty - gas paid from address balance
        owner: sender,
        price: rgp,
        budget: 10_000_000,
    },
    expiration: TransactionExpiration::ValidDuring {
        min_epoch: Some(current_epoch),
        max_epoch: Some(current_epoch + 1),
        min_timestamp: None,
        max_timestamp: None,
        chain: chain_identifier,
        nonce: unique_nonce,
    },
})
```

Requirements for address balance gas payments:

- `gas_data.payment` must be empty.
- `expiration` must be `ValidDuring` with both `min_epoch` and `max_epoch` specified.
- `max_epoch` must be at most `min_epoch + 1` (single epoch or 1-epoch range).
- Timestamp-based expiration is not currently supported.
- The transaction kind must be `ProgrammableTransaction`.

The `nonce` field differentiates otherwise identical transactions. Unlike EVM chains, the nonce has no semantic requirements. It does not need to be sequential, and there is no nonce gap problem. It simply allows you to submit two transactions that would otherwise have the same digest. For most use cases, generate the nonce randomly or use an incrementing counter in your application:

```rust
// Random nonce
let nonce: u32 = rand::random();

// Or incrementing counter
let nonce: u32 = self.next_nonce.fetch_add(1, Ordering::SeqCst);
```

**Verify that address balance gas payments are enabled**

Before using address balance gas payments, you can verify that the feature is enabled on the network by checking the protocol configuration flag:

```typescript
const network = 'testnet';

const networkUrls = {
	mainnet: 'https://fullnode.mainnet.sui.io:443',
	testnet: 'https://fullnode.testnet.sui.io:443',
	devnet: 'https://fullnode.devnet.sui.io:443',
};

const client = new SuiGrpcClient({
	network,
	baseUrl: networkUrls[network],
});

const { response } = await client.ledgerService.getEpoch({
	readMask: {
		paths: ['protocol_config.feature_flags'],
	},
});

const enabled =
	response.epoch?.protocolConfig?.featureFlags['enable_address_balance_gas_payments'] ?? false;

console.log(`enable_address_balance_gas_payments on ${network}:`, enabled);
```

## Sponsored transactions

Address balances simplify sponsored transactions compared to coin-based sponsorship.

- No gas coin locking risk.
- The sponsor does not need to manage gas coin inventory.
- The user can sign before the sponsor, enabling simpler async flows.
- Enables permissionless public gas stations.

:::caution
The `GasCoin` argument is still valid here, and usage should still be monitored in sponsored transactions even when using address balance gas payment.
:::

With address balances, the user signs first and the sponsor signs afterward:

```typescript
// 1. User builds and signs the transaction first
const tx = new Transaction();
tx.setSender(userAddress);
tx.setGasOwner(sponsorAddress);
tx.setGasPayment([]); // Empty array = sponsor pays from address balance
// ... add commands ...

const bytes = await tx.build({ client });
const { signature: userSignature } = await userKeypair.signTransaction(bytes);

// Option A: Send transaction and signature to sponsor, sponsor can sign and execute immediately:
// (`send_tx_to_sponsor` is a placeholder, there is no such API in the SDK)
await send_tx_to_sponsor(userSignature, bytes);

// Option B: Get signature from sponsor and submit it ourselves. Sponsor signs (can happen asynchronously)
const { signature: sponsorSignature } = await sponsorKeypair.signTransaction(bytes);

const result = await client.executeTransaction({
	transaction: bytes,
	signatures: [userSignature, sponsorSignature],
});
```

Both sender and sponsor must sign. Storage rebates are credited to the sponsor address balance.

## Querying balances

### TypeScript SDK

Use `getBalance` to see both coin objects and address balance:

```tsx
const { balance } = await grpcClient.getBalance({
	owner: '0xMyAddress',
});

console.log(balance.balance); // total balance as string (coin objects + address balance)
console.log(balance.coinBalance); // balance from coin objects only
console.log(balance.addressBalance); // balance from address balance only
console.log(balance.coinType); // e.g. "0x2::sui::SUI"
```

All balance values are returned as strings. Use `BigInt(balance.balance)` for numerical types.

### gRPC

Use [`GetBalance`](/references/fullnode-protocol#sui-rpc-v2-GetBalanceRequest) and [`ListBalances`](/references/fullnode-protocol#sui-rpc-v2-ListBalancesRequest) from `StateService`:

```bash
buf curl --protocol grpc https://fullnode.testnet.sui.io/sui.rpc.v2.StateService/GetBalance \
    -d '{
        "owner": "<ADDRESS>",
        "coin_type": "0x2::sui::SUI"
    }'

# List all balances
buf curl --protocol grpc https://fullnode.testnet.sui.io/sui.rpc.v2.StateService/ListBalances \
    -d '{"owner": "<ADDRESS>"}'
```

### GraphQL

Copy the examples below into [GraphQL IDE (Testnet)](https://graphql.testnet.sui.io/graphql) to try them out:

```graphql
# Single balance
{
	address(address: "0xe4ee9c157b5eb185c2df885bd7dcb4be493630a913f4b0e0c7e8ecf77930a878") {
		balance(coinType: "0x2::sui::SUI") {
			coinType {
				repr
			}
			addressBalance
			coinBalance
			totalBalance
		}
	}
}

# All balances
{
	address(address: "0xe4ee9c157b5eb185c2df885bd7dcb4be493630a913f4b0e0c7e8ecf77930a878") {
		balances {
			nodes {
				coinType {
					repr
				}
				addressBalance
				coinBalance
				totalBalance
			}
		}
	}
}
```

For more details, see [GraphQL Reference - `IAddressable.balance`](/references/sui-api/sui-graphql/beta/reference/types/interfaces/iaddressable).

## Computing balance changes from checkpoint data

With address balances, you must also process accumulator events from `TransactionEffects` when computing balance changes:

```rust
use sui_types::balance_change::{derive_balance_changes, BalanceChange};

let balance_changes: Vec<BalanceChange> = derive_balance_changes(
    &effects,
    &input_objects,
    &output_objects,
);
```

The algorithm:

1. **Subtract input coins:** For each input coin object, subtract its value from `(owner, coin_type)`.
2. **Add output coins:** For each mutated or created coin object, add its value to `(owner, coin_type)`.
3. **Process accumulator events:** For each accumulator event with a `Balance<T>` type:
   - `Split` operation: Subtract the amount (funds withdrawn from address balance).
   - `Merge` operation: Add the amount (funds deposited to address balance).

See the [reference implementation](https://github.com/MystenLabs/sui/blob/e23cc4bb0215ec065785a19772f22eda8ada14ae/crates/sui-types/src/balance_change.rs#L86) for the full algorithm.

### Accessing accumulator events

Accumulator events are embedded in [`TransactionEffects`](https://github.com/MystenLabs/sui/blob/main/crates/sui-types/src/effects/mod.rs):

```rust
use sui_types::effects::TransactionEffectsAPI;

let events = effects.accumulator_events();
for event in events {
    let address = event.write.address.address;
    let balance_type = &event.write.address.ty;

    // Only Balance<T> types represent balance changes
    if let Some(coin_type) = Balance::maybe_get_balance_type_param(balance_type) {
        let amount = match &event.write.value {
            AccumulatorValue::Integer(v) => *v as i128,
            _ => continue,
        };

        let signed_amount = match event.write.operation {
            AccumulatorOperation::Split => -amount,  // Withdrawal
            AccumulatorOperation::Merge => amount,   // Deposit
        };

        // (address, coin_type, signed_amount) represents the balance change
    }
}
```

The [`BalanceChange`](https://github.com/MystenLabs/sui/blob/main/crates/sui-types/src/balance_change.rs) struct represents the result:

```rust
pub struct BalanceChange {
    pub address: SuiAddress,
    pub coin_type: TypeTag,
    pub amount: i128,  // Negative = spent, positive = received
}
```

## Converting existing coins to address balances

Migration is optional. To consolidate coins into address balances using [Sui CLI](/references/cli/ptb):

```bash
sui client ptb \
    --merge-coins @<COIN_1> '[@<COIN_2>, @<COIN_3>]' \
    --move-call 0x2::coin::send_funds '<0x2::sui::SUI>' @<COIN_1> @<YOUR_ADDRESS>
```

For SUI coins, you can smash them as gas and use `--move-call 0x2::coin::send_funds --gas-coin <COIN>`.

The TypeScript SDK automatically selects coins or address balances as needed. Contracts that accept `Coin<T>` or `Balance<T>` remain callable. The `redeem_funds` function converts withdrawals to the expected types within the PTB.

Using the TypeScript SDK:

```typescript

const tx = new Transaction();
tx.moveCall({
    target: '0x2::coin::send_funds',
    typeArguments: ['0x2::sui::SUI'],
    arguments: [tx.coin({ balance: totalAmount }), tx.pure.address(yourAddress)],
});
```

This might earn storage rebates from deleted coin objects.
