Skip to main content

Paying for Transactions

Every Sui transaction pays for 2 things: computational execution and long-term object storage. How you source those funds (from coins, address balances, a sponsor, or at zero cost) depends on what the transaction does and who pays.

Coin selection with coinWithBalance

When you build a transaction with the TypeScript SDK, you rarely need to pick individual coin objects. The coinWithBalance helper (and the tx.coin() shorthand) resolves the required amount automatically:

import { Transaction, coinWithBalance } from '@mysten/sui/transactions';

const USDC = '0xdba34...::usdc::USDC';

const tx = new Transaction();

// coinWithBalance draws from address balances first, then coin objects
const payment = coinWithBalance({ type: USDC, balance: 5_000_000n }); // 5 USDC
tx.transferObjects([payment], recipient);

Under the hood:

  1. The SDK checks the sender's address balance for the requested coin type.
  2. If the address balance covers the amount, it creates a withdrawal, with no coin objects needed.
  3. If the address balance is insufficient, it falls back to selecting and splitting coin objects.

This means you can build transactions without knowing which coin objects the sender owns.

For SUI gas specifically, passing an empty array to setGasPayment tells the network to draw gas from the address balance:

tx.setGasPayment([]); // Gas paid from address balance — no coin lookup required

setGasPayment([]) alone is not enough for fully offline transaction building. You also need to set gasPrice, gasBudget, and ValidDuring expiration, and all transaction inputs must be resolvable without network queries (no unresolved coinWithBalance or tx.coin() intents that require balance lookups). See Paying for gas from address balances for the full set of requirements.

See Using Address Balances for the full API (TypeScript, Rust, CLI, and Move) and Gas Smashing for the legacy coin-merge approach.

Gas payment strategies

Sui offers 4 ways to pay for gas, each suited to different use cases:

StrategyWho paysUser holds SUI?Best for
Standard gasUserYesGeneral transactions
Address balance gasUserYes (in address balance)Offline-built transactions, no coin lookup
Sponsored gasApp / relayerNoOnboarding, free-to-play, enterprise
Gasless stablecoinNobody (zero fee)NoPeer-to-peer stablecoin transfers

Standard gas

The sender provides one or more Coin<SUI> objects to cover execution and storage costs. The SDK resolves this automatically when you don't explicitly configure gas. See Gas Fees for the fee formula and budget rules.

Address balance gas

Instead of selecting coin objects, the network withdraws gas from the sender's SUI address balance. This removes the need for coin lookups at gas-selection time. To use it, set tx.setGasPayment([]) along with gasPrice, gasBudget, and ValidDuring expiration. When all other transaction inputs are also known at build time (no unresolved coinWithBalance intents), the transaction can be built fully offline. See Paying for gas from address balances for requirements.

A third party (the sponsor) pays gas on behalf of the user. The user builds the transaction, and the sponsor provides gas payment. Both sign. This is ideal for onboarding new users who don't hold SUI. See Sponsored Transactions for the full flow, including data structures, sequence diagrams, and risk considerations.

With address balances, sponsored transactions become simpler: no gas coin locking risk, no coin inventory management, and the user can sign before the sponsor.

Gasless stablecoin transfers

Peer-to-peer transfers of allowlisted stablecoins (USDC, USDSUI, and others) execute at zero gas cost. Eligibility requires:

  • The PTB calls only allowlisted balance or coin operations (primarily balance::send_funds).
  • gasPayment is empty and gasPrice = 0.
  • No objects are written.

The gRPC and GraphQL transports detect eligibility automatically during simulation and set gasBudget = 0. See Gasless Stablecoin Transfers for the full allowlist and integration guide.

import { SuiGrpcClient } from '@mysten/sui/grpc';
import { Transaction } from '@mysten/sui/transactions';

const USDC = '0xdba34...::usdc::USDC';
const client = new SuiGrpcClient({ network: 'mainnet', baseUrl: 'https://fullnode.mainnet.sui.io:443' });

const tx = new Transaction();
tx.setSender(senderAddress);
tx.moveCall({
target: '0x2::balance::send_funds',
typeArguments: [USDC],
arguments: [tx.balance({ type: USDC, balance: 1_000_000n }), tx.pure.address(recipient)],
});

// SDK detects gasless eligibility and sets gasPrice = 0 and gasBudget = 0 automatically
const result = await client.signAndExecuteTransaction({ transaction: tx, signer: keypair });

Composing DeFi operations

Programmable transaction blocks (PTBs) let you chain multiple operations into a single atomic transaction. Outputs from earlier commands feed directly into later ones, with no intermediate state and no partial execution risk.

Swap and transfer

Split a coin, swap part of it on a DEX, and transfer the result, all in one transaction:

const tx = new Transaction();

// 1. Source funds using coinWithBalance (auto-selects from address balance or coins)
const [usdc] = tx.splitCoins(coinWithBalance({ type: USDC, balance: 200_000_000n }), [
tx.pure.u64(200_000_000),
]);

// 2. Swap USDC → SUI through a DEX pool
const [sui] = tx.moveCall({
target: '0xDEX_PACKAGE::router::swap_exact_input',
typeArguments: [USDC, '0x2::sui::SUI'],
arguments: [tx.object('0xPOOL_ID'), usdc, tx.pure.u64(0)], // 0 = min output (set real slippage)
});

// 3. Transfer the swapped SUI to the recipient
tx.transferObjects([sui], recipient);

Swap, save, and send

Compose 3 operations (a swap, a DeFi deposit, and a transfer) into one signed transaction:

const tx = new Transaction();

// Source 2000 USDC
const [allUsdc] = tx.splitCoins(
coinWithBalance({ type: USDC, balance: 2_000_000_000n }),
[tx.pure.u64(2_000_000_000)],
);

// 1. Split off 200 USDC and swap to SUI
const [swapPortion] = tx.splitCoins(allUsdc, [tx.pure.u64(200_000_000)]);
const [sui] = tx.moveCall({
target: '0xDEX::router::swap',
typeArguments: [USDC, '0x2::sui::SUI'],
arguments: [tx.object('0xPOOL'), swapPortion, tx.pure.u64(0)],
});

// 2. Split 900 USDC and deposit into a yield protocol
const [savePortion] = tx.splitCoins(allUsdc, [tx.pure.u64(900_000_000)]);
tx.moveCall({
target: '0xYIELD::vault::deposit',
typeArguments: [USDC],
arguments: [tx.object('0xVAULT'), savePortion],
});

// 3. Split 100 USDC and send to Alice (splitCoins returns Coin<T>, so use coin::send_funds)
const [sendPortion] = tx.splitCoins(allUsdc, [tx.pure.u64(100_000_000)]);
tx.moveCall({
target: '0x2::coin::send_funds',
typeArguments: [USDC],
arguments: [sendPortion, tx.pure.address(aliceAddress)],
});

// Remaining 800 USDC stays with the sender
tx.transferObjects([allUsdc, sui], tx.pure.address(senderAddress));

If any step fails (slippage breach, paused pool, invalid address), the entire transaction reverts. See Payment Intents for the Resolve, Plan, Execute pattern and more composition examples.

Key composition rules

  • Outputs chain forward: The result of splitCoins or a moveCall can be passed as input to the next command.
  • All-or-nothing: If any command fails, the entire PTB reverts.
  • One gas payment: The whole batch shares a gas budget, and the sponsor (user, app, or relayer) pays once.
  • Up to 1,024 commands: A single PTB can contain up to 1,024 commands.

See Building Transactions for the full PTB construction API.

Decision flowchart

Use this to pick the right gas strategy for your transaction:

  1. Is the transaction a basic stablecoin transfer of an allowlisted token?
  2. Does your app want to pay gas on behalf of the user?
  3. Does the user have a SUI address balance?
    • Yes: use address balance gas (tx.setGasPayment([])). No coin lookup needed.
    • No: use standard gas. The SDK auto-selects coin objects.

For most transactions, use coinWithBalance for non-gas coins, and the SDK handles coin-vs-balance selection for you. The exception is gasless stablecoin transfers, which use tx.balance() with balance::send_funds so the transaction stays eligible for zero gas.