Create and Execute PTBs on Sui with the Sui CLI
The new CLI command streamlines programming on Sui for those who prefer the terminal and Bash scripts.
A new command in Sui's command line interface (CLI) lets users create and execute Programmable Transaction Blocks (PTBs) directly from the terminal or a Bash script. This new command gives developers greater flexibility in implementing and executing PTBs.
PTBs give developers a very powerful programming tool not available on other blockchains. The ability to chain multiple transactions together in a single executable may be taken for granted in the wider world of software engineering, but on blockchains it represents a fundamental leap forward.
Until now, it was only possible to work with PTBs using one of the available SDKs. Now, developers who prefer working through the terminal and writing scripts have new means of using this powerful feature.
Check out the Sui CLI PTB command documentation for details and examples.
The example below shows the commands necessary to create a simple PTB. It creates three new coins with values 1000
, 2000
, and 3000
MIST from the gas coin, and transfers them to the address: 0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331 (note the @ in front of the address to indicate this is an address and not a hexadecimal).
sui client ptb \
--assign to_address @0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd \
--split-coins gas [1000,2000,3000] \
--assign coins \
--transfer-objects to_address [coins.0, coins.1, coins.2] \
--gas-budget 5000000 \
--summary
Here is the result message:
╭──────────────────────────────────────────────────────╮
│ PTB Execution Summary │
├──────────────────────────────────────────────────────┤
│ Digest: 7S7XpkGns8mQ4mBUzjdM32xqNHGF2P1ErHSLbb4Chn2j │
│ Status: success │
│ Gas Cost Summary: │
│ Storage Cost: 3952000 │
│ Computation Cost: 1000000 │
│ Storage Rebate: 978120 │
│ Non-refundable Storage Fee: 9880 │
╰──────────────────────────────────────────────────────╯
Sui CLI PTB
The main philosophy behind CLI PTB support is to enable developers to build and execute PTBs from the command line. You can also write Bash scripts to construct and execute PTBs just as you would do from the command line, providing great flexibility when it comes to automating different tasks.
The sui client ptb
command accepts the following options:
sui client ptb -h
Build, preview, and execute programmable transaction blocks. Depending on your shell, you might have to use quotes around arrays or other passed values. Use --help to see examples for how to use the core functionality of this command.
Usage: sui client ptb [OPTIONS]
Options:
--assign <NAME> <VALUE> Assign a value to a variable name to use later in the PTB.
--gas-coin <ID> The object ID of the gas coin to use. If not specified, it will try to use the first gas coin that it finds that has at least
the requested gas-budget balance.
--gas-budget <MIST> The gas budget for the transaction, in MIST.
--make-move-vec <TYPE> <[VALUES]> Given n-values of the same type, it constructs a vector. For non objects or an empty vector, the type tag must be specified.
--merge-coins <INTO_COIN> <[COIN OBJECTS]> Merge N coins into the provided coin.
--move-call <PACKAGE::MODULE::FUNCTION> <TYPE> <FUNCTION_ARGS> Make a move call to a function.
--split-coins <COIN> <[AMOUNT]> Split the coin into N coins as per the given array of amounts.
--transfer-objects <[OBJECTS]> <TO> Transfer objects to the specified address.
--publish <MOVE_PACKAGE_PATH> Publish the Move package. It takes as input the folder where the package exists.
--upgrade <MOVE_PACKAGE_PATH> Upgrade the move package. It takes as input the folder where the package exists.
--preview Preview the list of PTB transactions instead of executing them.
--summary Show only a short summary (digest, execution status, gas cost). Do not use this flag when you need all the transaction data
and the execution effects.
--warn-shadows Enable shadow warning when the same variable name is declared multiple times. Off by default.
--json Return command outputs in json format.
-h, --help Print help (see more with '--help')
Concepts and features
Besides using existing traditional PTB-related concepts, there are a few new concepts needed for this command.
- Reuse results or custom defined variables using the
--assign
command. - Preview the input commands in the PTB instead of executing the PTB.
- Warn on declaring a variable with the same identifier multiple times.
- Benefit from name resolution for common packages such as
sui
,std
, anddeepbook
(instead of using their addresses). - Error messages provide a concise and precise explanation of what is wrong in the PTB syntax.
- Variable names cannot be
address
,bool
,vector
,some
,none
,gas
,u8
,u16
,u32
,u64
,u128
, oru256
.
Examples
Let’s dig through some examples to showcase the Sui CLI PTB tool's power. Note that these examples were tested using Bash, and your mileage might vary depending on your shell and how it treats and parses strings and quotes (e.g., you might need to use quotation marks around arrays in zsh or fish, but not in Bash).
Nested or multi-types
When you need to work with nested types, you can simply wrap the types you need in <
and >
. For example, if an argument expects an Option<vector<u256>>
you can write std::option::Option<vector<u256>>
.
Here is an example:
sui client ptb
--make-move-vec "<std::option::Option<vector<u256>>>" "[vector[]]" \
--gas-budget 5000000
If you need to pass multiple types for a call, you can do so by using a comma between the types, as in <u64,u8,bool,string>
.
Name resolution
CLI PTB uses name resolution for common packages like sui
, std
, and deepbook
, so you can use them directly instead of their addresses: 0x2
, 0x1
, or 0xdee9
. You can still use the traditional package::module::function
way to call a specific function. For example, you can use both std::option
or 0x1::option
.
sui client ptb \
--assign A none \
--move-call std::option::is_none "<u64>" A \
--gas-budget 5000000
You can also refer to addresses in your local wallet by their alias instead of their full address.
Assign data to variables
The --assign
argument binds values to variables. There are a few ways you can use it:
- Assign a value to a variable:
sui client ptb \
--assign myvar 100 \
--gas-budget 5000000
- Assign a variable to the result of the previous transaction. In the following example, split-coins results in three new objects, which will be accessible via the coins variable due to the usage of
--assign
.
sui client ptb \
--split-coins gas "[1000,2000,3000]" \
--assign coins \
--gas-budget 5000000
- Assign a variable to a different variable
sui client ptb \
--assign myvar vector[100, 200] \
--assign my_addr @0x0fe375fff0ee40d20c54a7f2478b9b5c7eaa3625b761 \
--assign old_address my_addr \
--gas-budget 5000000
Publish package and call function
In the Sui repository, the first package example is a simple Move project. In a nutshell, this example publishes that package, which creates a Forge
object that you can use to create swords with the new_sword
function. The code below assumes that you have a Sui Testnet environment set up and sufficient gas for your address.
First, access the example folder and publish the package.
cd sui/examples/move/first_package
sui client ptb
--move-call sui::tx_context::sender \
--assign "sender" \
--publish "." \
--assign upgrade_cap \
--transfer-objects [upgrade_cap] sender \
--gas-budget 50000000
Publishing this package results in the following transaction data:
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING first_package
Successfully verified dependencies on-chain against source.
╭──────────────────────────────────────────────────────╮
│ PTB Execution Summary │
├──────────────────────────────────────────────────────┤
│ Digest: 8ULSyjJXhX5CPiEge3a7T2kLSA3YA8V44BAGZPdknJ1h │
│ Status: success │
│ Gas Cost Summary: │
│ Storage Cost: 9978800 │
│ Computation Cost: 1000000 │
│ Storage Rebate: 978120 │
│ Non-refundable Storage Fee: 9880 │
╰──────────────────────────────────────────────────────╯
From the previous result, we can use the digest and pass it to sui client tx-digest
to find the packageId
and the objectId
for the Forge
object. We want to forge a new sword and transfer it to the sender of this transaction.
Next, we look up the function new_sword
from the package we published to understand what arguments we need to pass to the function. Its signature is:
/// Constructor for creating swords
public fun new_sword(
forge: &mut Forge,
magic: u64,
strength: u64,
ctx: &mut TxContext,
): Sword
Now that the package exists onchain, let’s use a PTB to create a sword. The first thing to do is to get the sender’s address with the move-call
command and assign the result of that call to the sender
variable. We can use assign
for the magic
and strength
arguments, and assign the Forge
object ID to the variable forge_obj
.
Finally, we call the new_sword
function through the move-call
command, pass all the right function arguments, assign the result of the move-call
to the sword
variable, and transfer the new sword
object to the sender.
sui client ptb \
--move-call sui::tx_context::sender \
--assign sender \
--assign magic 10u64 \
--assign strength 50u64 \
--assign forge_obj @0xd5d19fde64ea43876add02efb2a769372869f30118 \
--move-call 0xbe6edeefea02371d35fdde5a45033d8d8e2e00b3eb911b23fa::example::new_sword forge_obj magic strength \
--assign sword \
--transfer-objects "[sword]" sender \
--gas-budget 5000000 \
--summary
Because we've set the --summary
flag, the execution result reads:
╭──────────────────────────────────────────────────────╮
│ PTB Execution Summary │
├──────────────────────────────────────────────────────┤
│ Digest: HYqWpN9sYBGLawj4BE9VnRM7qaezUFA8aLrGBbjf72L7 │
│ Status: success │
│ Gas Cost Summary: │
│ Storage Cost: 3739200 │
│ Computation Cost: 1000000 │
│ Storage Rebate: 2309868 │
│ Non-refundable Storage Fee: 23332 │
╰──────────────────────────────────────────────────────╯
PTBs in a Bash script
To facilitate automation and reusability, you can copy and paste the PTB commands from the terminal into a Bash script and run it.
#!/usr/bin/bash
sui client ptb \
--assign to_address @0x02a212de6a9dfa3a69e22387acfbafbb1a9e59
--split-coins gas "[1000,2000,3000]" \
--assign coins \
--transfer-objects "[coins.0, coins.1, coins.2]" to_address \
--gas-budget 5000000
The above code can be run as a normal Bash script.
Mint 100 NFTs and transfer them using PTBs in a Bash script
This example uses a Bash script to mint 100 NFTs and transfer them to 100 addresses. The NFT is defined in this smart contract posted to GitHub. We publish the package, and get the num_issuer_cap
and the package
IDs. The project defines the function mint in the module num
, which we declare.
The script then loads the addresses from a text file where each address is on a new line. It then builds a string of move-call
commands, calling the mint
function and transferring the object to an address, all in a for loop. Finally, the PTB is executed.
Note that, being a Bash script, you can pass in environment variables as you’d do with any other Bash script.
num_issuer_cap=0x3e8a046807edd63b75014b9aff0f8dc30b76344a4dfdb1ae4655932e0514086b
package=0x610834b5fa960b297a5caf6af56bcc045d67d158889da5b511a20031307ce099
module=num
fun=mint
echo "Loading addresses from file"
readarray -t addresses < addresses.txt
cmd=""
for address in ${addresses[@]}; do
cmd+="--move-call $package::$module::$fun @$num_issuer_cap --assign X --transfer-objects [X] @$address "
done
echo "Executing PTB"
sui client ptb \
$cmd \
--gas-budget 5000000000 \
--summary
The end result is:
bash-5.2$ bash working-demo.sh
Loading addresses from file
Executing PTB
╭──────────────────────────────────────────────────────╮
│ PTB Execution Summary │
├──────────────────────────────────────────────────────┤
│ Digest: 7THBdpRHWSTiWExxSB7A3m9E7PDP9kQaD16N8xXevdcA │
│ Status: success │
│ Gas Cost Summary: │
│ Storage Cost: 132376800 │
│ Computation Cost: 1000000 │
│ Storage Rebate: 2392632 │
│ Non-refundable Storage Fee: 24168 │
╰──────────────────────────────────────────────────────╯
Purchase of a domain on SuiNS
The following example registers a Sui domain name by first creating a zero coin that has no SUI, to be used for move-call, and after that it transfers the newly created domain name object to a specific address.
sui client ptb \
--assign suins_object_id @0x300369e8909b9a6464da265b9a5a9ab6fe2158a040e84e808628cde7a07ee5a3 \
--split-coins gas "[0]" \
--assign zero_coin \
--move-call 0x4255184a0143c0ce4394a3f16a6f5aa5d64507269e54e51ea396d569fe8f1ba5::register::register suins_object_id '"mysui.sui"' 1u8 zero_coin @0x6 \
--assign suins_name \
--transfer-objects "[suins_name]" @0x0fe375fff0ee40d20c54a7f2478b9b5c7eaa3625b7611f9661ec5faefb4a6fea \
--gas-budget 50000000
The result of this command is:
╭──────────────────────────────────────────────────────╮
│ PTB Execution Summary │
├──────────────────────────────────────────────────────┤
│ Digest: 5D2W4beXGWzF4Cn69y6ihnTvoyBC99vb5zGhpxAhdq1z │
│ Status: success │
│ Gas Cost Summary: │
│ Storage Cost: 10039600 │
│ Computation Cost: 1000000 │
│ Storage Rebate: 5394708 │
│ Non-refundable Storage Fee: 54492 │
╰──────────────────────────────────────────────────────╯
Preview a PTB instead of executing
The --preview
flag, useful for complex PTBs, lists the transactions in the PTB instead of executing it. The preview displays a well-formatted table with each transaction and its arguments after parsing the entire input. It also displays any errors. This helps when dealing with complex PTBs, as it will produce the list of transactions instead of executing the PTB.
sui client ptb \
--assign to_address @0x02a212de6a9dfa3a69e22387acfbafbb1a9e59 \
--split-coins gas [1000,2000,3000] \
--assign coins \
--transfer-objects [coins.0, coins.1, coins.2] to_address \
--gas-budget 5000000 \
--summary \
--preview
And the result of this call would be the following:
Warnings and errors
When using the --assign
command and the --warn-shadows
flag, the tool issues a warning when it encounters shadowed variables. That is, if there are multiple declarations for a given variable name, the tool warns the developer that the same variable is declared multiple times. Here is an example:
sui client ptb \
--assign a 10 \
--assign a 15 \
--gas-budget 5000000 \
--warn-shadows
The image below shows the warnings produced when building a PTB:
One area where we dedicated a lot of effort is on how to report useful errors. We aimed for errors to include sufficient information about what is wrong, pointing exactly to the argument that leads to that error, and in some cases even providing a hint on how to fix it.
For example, if the user calls the std::Option::is_none
function and passes the module name with an uppercase instead of lowercase, the tool will produce the following message:
sui client ptb \
--move-call std::Option::is_none none \
--gas-budget 5000000
And the error message will show the following:
Here is another example which showcases the error produced when the user calls --assign X
without passing a variable, after a command that does not produce any result:
sui client ptb \
--assign X \
--gas-budget 5000000
The error in this case would be the following:
Here is a another example regarding reserved words in the tool:
sui client ptb --assign address 5 --gas-budget 5
The error message here would show: