Building Transactions
Construct programmable transaction blocks with the Transaction API
Every interaction with the Sui network goes through a transaction. Sui transactions are a sequence of commands called programmable transaction blocks (PTBs) that execute on inputs to produce a result. Commands within a transaction can call Move functions, transfer objects, split and merge coins, and more. You can chain the result of one command into a subsequent command, composing complex operations in a single transaction.
All commands in a transaction execute atomically. If any command fails, the entire transaction is rolled back and none of its effects are applied.
For more information on transaction model, see the Sui documentation on PTBs and inputs and results.
Creating a transaction
Import the Transaction class and create a new instance:
import { Transaction } from '@mysten/sui/transactions';
const tx = new Transaction();Sending SUI
Use tx.balance() and balance::send_funds to send SUI; this deposits directly into the
recipient's address balance:
import { Transaction } from '@mysten/sui/transactions';
import { MIST_PER_SUI } from '@mysten/sui/utils';
const tx = new Transaction();
tx.moveCall({
target: '0x2::balance::send_funds',
typeArguments: ['0x2::sui::SUI'],
arguments: [tx.balance({ balance: 1n * MIST_PER_SUI }), tx.pure.address('0xRecipientAddress')],
});If the recipient needs a Coin<T> object, use tx.coin() with transferObjects instead:
tx.transferObjects([tx.coin({ balance: 1n * MIST_PER_SUI })], '0xRecipientAddress');Both tx.balance() and tx.coin() automatically draw from your coin objects and address balances.
You don't need to manage individual coins yourself. See Coins and Balances
for full details.
Sending other tokens
For non-SUI tokens, use the same patterns for passing the coin type:
const tx = new Transaction();
// Send to address balance (preferred)
tx.moveCall({
target: '0x2::balance::send_funds',
typeArguments: ['0xPackageId::module::CoinType'],
arguments: [
tx.balance({ balance: 1_000_000, type: '0xPackageId::module::CoinType' }),
tx.pure.address('0xRecipientAddress'),
],
});
// Or send as a coin object
tx.transferObjects(
[tx.coin({ balance: 1_000_000, type: '0xPackageId::module::CoinType' })],
'0xRecipientAddress',
);Calling Move functions
Use moveCall to call any function in a published Move package. The Sui framework at 0x2 provides
many built-in functions you can call directly. For example, to split a coin:
const tx = new Transaction();
const [newCoin] = tx.moveCall({
target: '0x2::coin::split',
typeArguments: ['0x2::sui::SUI'],
arguments: [tx.object('0xCoinId'), tx.pure.u64(1000)],
});
tx.transferObjects([newCoin], '0xRecipientAddress');The target format is packageId::moduleName::functionName.
To call functions in your own published packages, use the same pattern with your package ID:
tx.moveCall({
target: '0xYourPackageId::module::function_name',
arguments: [tx.pure.string('hello'), tx.object('0xSomeObjectId')],
});Using codegen for type-safe calls
The @mysten/codegen package generates type-safe TypeScript functions from your Move
packages. Instead of writing moveCall with string targets and manual argument construction, use
tx.add() with generated functions:
import { Transaction } from '@mysten/sui/transactions';
import * as counter from './contracts/counter/counter';
const tx = new Transaction();
tx.add(
counter.increment({
arguments: {
counter: '0x123...', // Counter object ID
},
}),
);This gives you IDE autocompletion, compile-time type checking for arguments, and eliminates incorrect target strings. See the codegen documentation for setup instructions.
Return values
Helpers and commands return results that you can use as input to subsequent commands. For example,
tx.coin() creates a coin with the requested balance:
const tx = new Transaction();
const coin = tx.coin({ balance: 1_000_000 });
// Use that result as input to another command
tx.transferObjects([coin], '0xRecipientAddress');moveCall works the same way. When a Move function returns a value, you can capture it and pass it
to the next command:
const [nft] = tx.moveCall({
target: '0xPackageId::nft::mint',
arguments: [tx.pure.string('My NFT'), tx.pure.string('Description')],
});
tx.transferObjects([nft], '0xRecipientAddress');When a command returns multiple values, destructure or index into the result:
// Destructuring
const [nft1, nft2] = tx.moveCall({ target: '0xPackageId::nft::mint_pair' });
// Or indexing
const result = tx.moveCall({ target: '0xPackageId::nft::mint_pair' });
const firstNft = result[0];
const secondNft = result[1];Always access elements by index or destructuring. Do not use the spread operator (...result) or
pass results to Array.from(), which causes an infinite loop.
Type arguments
Some Move functions have generic type parameters. Pass them with typeArguments:
tx.moveCall({
target: '0x2::coin::split',
typeArguments: ['0x2::sui::SUI'],
arguments: [tx.object('0xCoinId'), tx.pure.u64(1000)],
});Chaining commands
The result of any command can be used as input to a subsequent command. This is what makes transactions programmable. You can compose multiple operations into a single atomic transaction:
const tx = new Transaction();
// Mint an NFT
const [nft] = tx.moveCall({
target: '0xPackageId::nft::mint',
arguments: [tx.pure.string('My NFT')],
});
// Set a property on it
tx.moveCall({
target: '0xPackageId::nft::set_description',
arguments: [nft, tx.pure.string('A nice NFT')],
});
// Transfer it to someone
tx.transferObjects([nft], '0xRecipientAddress');Batch transfers
Send tokens to multiple recipients in a single transaction:
import { Transaction } from '@mysten/sui/transactions';
const transfers = [
{ to: '0xAlice', amount: 1_000_000_000 },
{ to: '0xBob', amount: 2_000_000_000 },
{ to: '0xCarol', amount: 500_000_000 },
];
const tx = new Transaction();
for (const transfer of transfers) {
// Send to address balances (preferred)
tx.moveCall({
target: '0x2::balance::send_funds',
typeArguments: ['0x2::sui::SUI'],
arguments: [tx.balance({ balance: transfer.amount }), tx.pure.address(transfer.to)],
});
}
// Or send as coin objects
for (const transfer of transfers) {
tx.transferObjects([tx.coin({ balance: transfer.amount })], transfer.to);
}Composable transaction building with thunks
The tx.add() method accepts thunks, which are functions that receive the transaction and add
commands to it. This enables building reusable, composable transaction pieces:
function mintNft(name: string) {
return (tx: Transaction) => {
return tx.moveCall({
target: '0xPackage::nft::mint',
arguments: [tx.pure.string(name)],
});
};
}
const tx = new Transaction();
const [nft] = tx.add(mintNft('My NFT'));
tx.transferObjects([nft], '0xRecipientAddress');See SDK Building for more information on building composable transaction libraries.
Serializing transactions
build() produces BCS bytes for signing. With a client it resolves object versions, gas, and
intents like tx.coin() first. If everything is already set, no client is needed (see
Building Offline):
const bytes = await tx.build({ client: grpcClient });
// Only the inputs and commands, without sender or gas data
const kindBytes = await tx.build({ client: grpcClient, onlyTransactionKind: true });toJSON() produces an editable representation for storage or for passing between a frontend and
backend. It resolves intents like tx.coin() but leaves sender and gas data as they are:
const json = await tx.toJSON({ client: grpcClient });
const restored = Transaction.from(json);
// Or keep tx.coin() intents for the receiver to resolve
const jsonWithIntents = await tx.toJSON({ supportedIntents: ['CoinWithBalance'] });Executing transactions
Once you've built a transaction, see Signing and Execution for all the
ways to sign and submit it, such as keypairs, dapp-kit hooks, sponsored transactions,
and more.