Script Transactions
In addition to calling published entry functions, you can submit compiled Move scripts as transactions. Scripts are standalone pieces of Move bytecode that execute once and are not stored on-chain. They offer more flexibility than entry function calls because a script can invoke multiple public functions, use arbitrary logic, and work with types across different modules — all within a single transaction.
Scripts vs. Entry Functions
Section titled “Scripts vs. Entry Functions”| Aspect | Entry Functions | Scripts |
|---|---|---|
| Deployment | Must be published as part of a Move module on-chain | Not deployed; bytecode is included directly in the transaction |
| Reusability | Callable by any transaction referencing the module | Single-use; must be included in each transaction |
| Flexibility | Limited to the function’s defined signature and logic | Can call multiple public functions, use conditionals, and compose logic freely |
| Compilation | Compiled and published with the module | Compiled separately using the Aptos CLI |
| Use cases | Standard operations (transfers, staking, module interactions) | Complex one-off operations, batched calls, migrations |
Compiling a Move Script
Section titled “Compiling a Move Script”Before you can submit a script transaction, you need to compile the Move source into bytecode. Use the Aptos CLI to compile your script.
-
Write your Move script.
Create a file named
my_script.movewith the following structure:script {use std::signer;use aptos_framework::aptos_account;use aptos_framework::coin;use aptos_framework::aptos_coin::AptosCoin;fun main(sender: &signer, recipient: address, amount: u64) {// Transfer APT from sender to recipientaptos_account::transfer(sender, recipient, amount);// Check the sender's remaining balancelet _balance = coin::balance<AptosCoin>(signer::address_of(sender));}} -
Compile the script using the Aptos CLI.
Terminal window aptos move compile --named-addresses std=0x1,aptos_framework=0x1This produces a compiled bytecode file (
.mv) in thebuild/directory. You will include this bytecode in your script transaction payload.
Submitting a Script Transaction
Section titled “Submitting a Script Transaction”Once you have the compiled bytecode, use the TransactionPayload::Script variant to build and submit the transaction.
-
Load the compiled script bytecode.
Read the
.mvfile produced by the compiler into a byte vector.let script_bytes = std::fs::read("build/my_script/bytecode_scripts/main.mv")?; -
Build the script payload.
Construct a
TransactionPayload::Scriptwith the bytecode, type arguments, and script arguments.use aptos_sdk::types::{TransactionPayload, ScriptPayload, ScriptArgument};let script_payload = TransactionPayload::Script(ScriptPayload::new(script_bytes,vec![], // Type arguments (empty if the script has no type parameters)vec![ScriptArgument::Address(bob.address()),ScriptArgument::U64(10_000_000),],)); -
Build, sign, and submit using the standard transaction flow.
use aptos_sdk::transaction_builder::TransactionBuilder;let raw_txn = TransactionBuilder::new_with_payload(script_payload,aptos.get_chain_id().await?,).sender(alice.address()).sequence_number(aptos.get_sequence_number(alice.address()).await?).max_gas_amount(10_000).gas_unit_price(100).expiration_timestamp_secs(aptos.get_latest_ledger_info().await?.timestamp() + 60,).build();let signed_txn = aptos.sign_transaction(&alice, raw_txn)?;let result = aptos.submit_and_wait(signed_txn).await?;let success = result.data.get("success").and_then(|v| v.as_bool()).unwrap_or(false);println!("Script transaction success: {}", success);
When to Use Scripts
Section titled “When to Use Scripts”Consider using scripts instead of entry function calls when:
- You need to call multiple functions across different modules in a single atomic transaction.
- You need to use return values from one function call as input to another within the same transaction.
- You are performing a one-off operation (such as a data migration or airdrop) that does not warrant deploying a new module.
- You need to invoke a
publicfunction that is not marked asentry, which cannot be called directly via entry function payloads.
Full Working Example
Section titled “Full Working Example”/// This example demonstrates how to submit a compiled Move script as/// a transaction using the Aptos Rust SDK.use aptos_sdk::{Aptos, AptosConfig};use aptos_sdk::account::Ed25519Account;use aptos_sdk::types::{TransactionPayload, ScriptPayload, ScriptArgument};use aptos_sdk::transaction_builder::TransactionBuilder;
#[tokio::main]async fn main() -> anyhow::Result<()> { // Connect to testnet let aptos = Aptos::new(AptosConfig::testnet())?;
// Generate and fund accounts let alice = Ed25519Account::generate(); let bob = Ed25519Account::generate(); aptos.fund_account(alice.address(), 100_000_000).await?; aptos.fund_account(bob.address(), 100_000_000).await?;
println!("Alice: {}", alice.address()); println!("Bob: {}", bob.address());
// Load compiled script bytecode // Replace this path with the actual path to your compiled .mv file let script_bytes = std::fs::read("build/my_script/bytecode_scripts/main.mv")?;
// Build the script payload let script_payload = TransactionPayload::Script(ScriptPayload::new( script_bytes, vec![], vec![ ScriptArgument::Address(bob.address()), ScriptArgument::U64(10_000_000), ], ));
// Build the raw transaction let raw_txn = TransactionBuilder::new_with_payload( script_payload, aptos.get_chain_id().await?, ) .sender(alice.address()) .sequence_number(aptos.get_sequence_number(alice.address()).await?) .max_gas_amount(10_000) .gas_unit_price(100) .expiration_timestamp_secs( aptos.get_latest_ledger_info().await?.timestamp() + 60, ) .build();
// Sign and submit let signed_txn = aptos.sign_transaction(&alice, raw_txn)?; let result = aptos.submit_and_wait(signed_txn).await?;
let success = result .data .get("success") .and_then(|v| v.as_bool()) .unwrap_or(false); println!("\nScript transaction success: {}", success);
// Verify balances println!("\n=== Final Balances ==="); println!("Alice: {} octas", aptos.get_balance(alice.address()).await?); println!("Bob: {} octas", aptos.get_balance(bob.address()).await?);
Ok(())}