Sui Programmable Transaction Blocks Basics
This example starts by constructing a transaction block to send SUI. To construct transactions
blocks, import the TransactionBlock
class and construct it:
import { TransactionBlock } from '@mysten/sui.js/transactions';
const txb = new TransactionBlock();
You can then add transactions to the transaction block.
// create a new coin with balance 100, based on the coins used as gas payment
// you can define any balance here
const [coin] = txb.splitCoins(txb.gas, [100]);
// transfer the split coin to a specific address
txb.transferObjects([coin], '0xSomeSuiAddress');
You can attach multiple transactions of the same type to a transaction block, as well. For example, to get a list of transfers and iterate over them to transfer coins to each of them:
interface Transfer {
to: string;
amount: number;
}
// procure a list of some Sui transfers to make
const transfers: Transfer[] = getTransfers();
const txb = new TransactionBlock();
// first, split the gas coin into multiple coins
const coins = txb.splitCoins(
txb.gas,
transfers.map((transfer) => transfer.amount),
);
// next, create a transfer transaction for each coin
transfers.forEach((transfer, index) => {
txb.transferObjects([coins[index]], transfer.to);
});
After you have the transaction block defined, you can directly execute it with a signer using
signAndExecuteTransactionBlock
.
client.signAndExecuteTransactionBlock({ signer: keypair, transactionBlock: txb });
Transactions
Programmable Transaction blocks have two key concepts: inputs and transactions.
Transactions are steps of execution in the transaction block. Each Transaction in a TransactionBlock takes a set of inputs, and produces results. The inputs for a transaction depend on the kind of transaction. Sui supports following transactions:
txb.splitCoins(coin, amounts)
- Creates new coins with the defined amounts, split from the provided coin. Returns the coins so that it can be used in subsequent transactions.- Example:
txb.splitCoins(txb.gas, [100, 200])
- Example:
txb.mergeCoins(destinationCoin, sourceCoins)
- Merges the sourceCoins into the destinationCoin.- Example:
txb.mergeCoins(txb.object(coin1), [txb.object(coin2), txb.object(coin3)])
- Example:
txb.transferObjects(objects, address)
- Transfers a list of objects to the specified address.- Example:
txb.transferObjects([txb.object(thing1), txb.object(thing2)], myAddress)
- Example:
txb.moveCall({ target, arguments, typeArguments })
- Executes a Move call. Returns whatever the Sui Move call returns.- Example:
txb.moveCall({ target: '0x2::devnet_nft::mint', arguments: [txb.pure.string(name), txb.pure.string(description), txb.pure.string(image)] })
- Example:
txb.makeMoveVec({ type, objects })
- Constructs a vector of objects that can be passed into amoveCall
. This is required as there’s no way to define a vector as an input.- Example:
txb.makeMoveVec({ objects: [txb.object(id1), txb.object(id2)] })
- Example:
txb.publish(modules, dependencies)
- Publishes a Move package. Returns the upgrade capability object.
Passing inputs to a transaction
Transaction inputs can be provided in a number of different ways, depending on the transaction, and the type of value being provided.
JavaScript values
For specific transaction arguments (amounts
in splitCoins
, and address
in transferObjects
)
the expected type is known ahead of time, and you can directly pass raw javascript values when
calling the transaction method. appropriate Move type automatically.
// the amount to split off the gas coin is provided as a pure javascript number
const [coin] = txb.splitCoins(txb.gas, [100]);
// the address for the transfer is provided as a pure javascript string
txb.transferObjects([coin], '0xSomeSuiAddress');
Pure values
When providing inputs that are not on chain objects, the values must be serialized as
BCS (opens in a new tab), which can be done using txb.pure
eg,
txb.pure.address(address)
or txb.pure(bcs.vector(bcs.U8).serialize(bytes))
.
txb.pure
can be called as a function that accepts a SerializedBcs object, or as a namespace that
contains functions for each of the supported types.
const [coin] = txb.splitCoins(txb.gas, [txb.pure.u64(100)]);
const [coin] = txb.splitCoins(txb.gas, [txb.pure(bcs.U64.serialize(100))]);
txb.transferObjects([coin], txb.pure.address('0xSomeSuiAddress'));
txb.transferObjects([coin], txb.pure(bcs.Address.serialize('0xSomeSuiAddress')));
Object references
To use an on chain object as a transaction input, you must pass a reference to that object. This can
be done by calling txb.object
with the object id. Transaction arguments that only accept objects
(like objects
in transferObjects
) will automatically treat any provided strings as objects ids.
For methods like moveCall
that accept both objects and other types, you must explicitly call
txb.object
to convert the id to an object reference.
// Object IDs can be passed to some methods like (transferObjects) directly
txb.transferObjects(['0xSomeObject'], 'OxSomeAddress');
// txb.object can be used anywhere an object is accepted
txb.transferObjects([txb.object('0xSomeObject')], 'OxSomeAddress');
txb.moveCall({
target: '0x2::nft::mint',
// object IDs must be wrapped in moveCall arguments
arguments: [txb.object('0xSomeObject')],
});
When building a transaction block, Sui expects all objects to be fully resolved, including the object version. The SDK will automatically lookup the current version of objects for any provided object reference when building a transaction block. This greatly simplifies building transactions, but requires additional RPC calls. You can optimize this process by providing a fully resolved object reference instead:
// for owned or immutable objects
txb.object(Inputs.ObjectRef({ digest, objectId, version }));
// for shared objects
txb.object(Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable }));
Transaction results
You can also use the result of a transaction as an argument in a subsequent transactions. Each transaction method on the transaction builder returns a reference to the transaction result.
// split a coin object off of the gas object
const [coin] = txb.splitCoins(txb.gas, [100]);
// transfer the resulting coin object
txb.transferObjects([coin], address);
When a transaction returns multiple results, you can access the result at a specific index either using destructuring, or array indexes.
// destructuring (preferred, as it gives you logical local names)
const [nft1, nft2] = txb.moveCall({ target: '0x2::nft::mint_many' });
txb.transferObjects([nft1, nft2], address);
// array indexes
const mintMany = txb.moveCall({ target: '0x2::nft::mint_many' });
txb.transferObjects([mintMany[0], mintMany[1]], address);
Get transaction block bytes
If you need the transaction block bytes, instead of signing or executing the transaction block, you
can use the build
method on the transaction builder itself.
Important: You might need to explicitly call setSender()
on the transaction block to ensure
that the sender
field is populated. This is normally done by the signer before signing the
transaction, but will not be done automatically if you’re building the transaction block bytes
yourself.
const txb = new TransactionBlock();
// ... add some transactions...
await txb.build({ client });
In most cases, building requires your SuiClient to fully resolve input values.
If you have transaction block bytes, you can also convert them back into a TransactionBlock
class:
const bytes = getTransactionBlockBytesFromSomewhere();
const txb = TransactionBlock.from(bytes);