Use the Aptos CLI
The aptos
tool is a command line interface (CLI) for developing on the Aptos blockchain, debugging, and for node operations. This document describes how to use the aptos
CLI tool. To download or build the CLI, follow Install Aptos CLI.
Compiling Move
The aptos
CLI can be used to compile a Move package locally.
The below example uses the HelloBlockchain
in move-examples.
The named addresses can be either an account address, or a profile name.
$ aptos move compile --package-dir aptos-move/move-examples/hello_blockchain/ --named-addresses hello_blockchain=superuser
The above command will generate the below terminal output:
{
"Result": [
"742854F7DCA56EA6309B51E8CEBB830B12623F9C9D76C72C3242E4CAD353DEDC::Message"
]
}
Compiling and unit testing Move
The aptos
CLI can also be used to compile and run unit tests locally.
In this example, we'll use the HelloBlockchain
in move-examples.
$ aptos move test --package-dir aptos-move/move-examples/hello_blockchain/ --named-addresses hello_blockchain=superuser
The above command will generate the following terminal output:
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING Examples
Running Move unit tests
[ PASS ] 0x742854f7dca56ea6309b51e8cebb830b12623f9c9d76c72c3242e4cad353dedc::MessageTests::sender_can_set_message
[ PASS ] 0x742854f7dca56ea6309b51e8cebb830b12623f9c9d76c72c3242e4cad353dedc::Message::sender_can_set_message
Test result: OK. Total tests: 2; passed: 2; failed: 0
{
"Result": "Success"
}
Generating test coverage details for Move
The aptos
CLI can be used to analyze and improve the testing of your Move modules. To use this feature:
-
In your
aptos-core
source checkout, navigate to theaptos-move/framework/move-stdlib
directory. -
Execute the command:
$ aptos move test --coverage
-
Receive results in standard output containing the result for each test case followed by a basic coverage summary resembling:
BUILDING MoveStdlib
Running Move unit tests
[ PASS ] 0x1::vector_tests::append_empties_is_empty
[ PASS ] 0x1::option_tests::borrow_mut_none
[ PASS ] 0x1::fixed_point32_tests::ceil_can_round_up_correctly
[ PASS ] 0x1::features::test_change_feature_txn
[ PASS ] 0x1::bcs_tests::bcs_bool
[ PASS ] 0x1::bit_vector_tests::empty_bitvector
[ PASS ] 0x1::option_tests::borrow_mut_some
Test result: OK. Total tests: 149; passed: 149; failed: 0
+-------------------------+
| Move Coverage Summary |
+-------------------------+
Module 0000000000000000000000000000000000000000000000000000000000000001::bcs
>>> % Module coverage: NaN
Module 0000000000000000000000000000000000000000000000000000000000000001::fixed_point32
>>> % Module coverage: 100.00
Module 0000000000000000000000000000000000000000000000000000000000000001::hash
>>> % Module coverage: NaN
Module 0000000000000000000000000000000000000000000000000000000000000001::vector
>>> % Module coverage: 92.19
Module 0000000000000000000000000000000000000000000000000000000000000001::error
>>> % Module coverage: 0.00
Module 0000000000000000000000000000000000000000000000000000000000000001::acl
>>> % Module coverage: 0.00
Module 0000000000000000000000000000000000000000000000000000000000000001::bit_vector
>>> % Module coverage: 97.32
Module 0000000000000000000000000000000000000000000000000000000000000001::signer
>>> % Module coverage: 100.00
Module 0000000000000000000000000000000000000000000000000000000000000001::features
>>> % Module coverage: 69.41
Module 0000000000000000000000000000000000000000000000000000000000000001::option
>>> % Module coverage: 100.00
Module 0000000000000000000000000000000000000000000000000000000000000001::string
>>> % Module coverage: 81.82
+-------------------------+
| % Move Coverage: 83.50 |
+-------------------------+
Please use `aptos move coverage -h` for more detailed test coverage of this package
{
"Result": "Success"
} -
Optionally, narrow down your test runs and results to a specific package name with the
--filter
option, like so:$ aptos move test --coverage --filter vector
With results like:
BUILDING MoveStdlib
Running Move unit tests
[ PASS ] 0x1::bit_vector_tests::empty_bitvector
[ PASS ] 0x1::vector_tests::append_empties_is_empty
[ PASS ] 0x1::bit_vector_tests::index_bit_out_of_bounds
[ PASS ] 0x1::vector_tests::append_respects_order_empty_lhs -
Run the
aptos move coverage
command to obtain more detailed coverage information. -
Optionally, isolate the results to a module by passing its name to the
--module
option, for example:$ aptos move coverage source --module signer
With results:
module std::signer {
// Borrows the address of the signer
// Conceptually, you can think of the `signer` as being a struct wrapper arround an
// address
// ```
// struct signer has drop { addr: address }
// ```
// `borrow_address` borrows this inner field
native public fun borrow_address(s: &signer): &address;
// Copies the address of the signer
public fun address_of(s: &signer): address {
*borrow_address(s)
}
/// Return true only if `s` is a transaction signer. This is a spec function only available in spec.
spec native fun is_txn_signer(s: signer): bool;
/// Return true only if `a` is a transaction signer address. This is a spec function only available in spec.
spec native fun is_txn_signer_addr(a: address): bool;
}
{
"Result": "Success"
} -
Find failures and iteratively improve your testing and running these commands to eliminate gaps in your testing coverage.
Proving Move
The aptos
CLI can be used to run Move Prover, which is a formal verification tool for the Move language. The below example proves the hello_prover
package in move-examples.
aptos move prove --package-dir aptos-move/move-examples/hello_prover/
The above command will generate the following terminal output:
SUCCESS proving 1 modules from package `hello_prover` in 1.649s
{
"Result": "Success"
}
Move Prover may fail with the following terminal output if the dependencies are not installed and set up properly:
FAILURE proving 1 modules from package `hello_prover` in 0.067s
{
"Error": "Move Prover failed: No boogie executable set. Please set BOOGIE_EXE"
}
In this case, see Install the dependencies of Move Prover.
Profiling gas usage
Optionally, you can profile gas usage in the Aptos virtual machine locally rather than simulating transactions at the fullnode. This will generate a web-based gas report which you can view with your browser.
To run the gas profiler, simply append the --profile-gas
option to the Aptos CLI move publish
, move run
or move run-script
command.
See Gas Profiling for a full tutorial.
Debugging and printing stack trace
In this example, we will use DebugDemo
in debug-move-example.
Now, you can use debug::print
and debug::print_stack_trace
in your DebugDemo Move file.
You can run the following command:
$ aptos move test --package-dir crates/aptos/debug-move-example
The command will generate the following output:
Running Move unit tests
[debug] 0000000000000000000000000000000000000000000000000000000000000001
Call Stack:
[0] 0000000000000000000000000000000000000000000000000000000000000001::Message::sender_can_set_message
Code:
[4] CallGeneric(0)
[5] MoveLoc(0)
[6] LdConst(0)
> [7] Call(1)
[8] Ret
Locals:
[0] -
[1] 0000000000000000000000000000000000000000000000000000000000000001
Operand Stack:
Publishing a Move package with a named address
In this example, we'll use the HelloBlockchain
in move-examples.
Publish the package with your account address set for HelloBlockchain
.
Here, you need to change 8946741e5c907c43c9e042b3739993f32904723f8e2d1491564d38959b59ac71 to your account address.
$ aptos move publish --package-dir aptos-move/move-examples/hello_blockchain/ --named-addresses hello_blockchain=8946741e5c907c43c9e042b3739993f32904723f8e2d1491564d38959b59ac71
As an open source project, the source code as well as compiled code published to the Aptos blockchain is inherently open by default. This means code you upload may be downloaded from on-chain data. Even without source access, it is possible to regenerate Move source from Move bytecode. To disable source access, publish with the --included-artifacts none
argument, like so:
aptos move publish --included-artifacts none
You can additionally use named profiles for the addresses. The first placeholder is default
$ aptos move publish --package-dir aptos-move/move-examples/hello_blockchain/ --named-addresses hello_blockchain=default
When publishing Move modules, if multiple modules are in one package, then all the modules in this package must have the same account. If they have different accounts, then the publishing will fail at the transaction level.
Running a Move function
Now that you've published the function above, you can run it.
Arguments must be given a type with a colon to separate it. In this example, we want the input to be
parsed as a string, so we put string:Hello!
.
$ aptos move run --function-id 0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb::message::set_message --args string:hello!
{
"Result": {
"changes": [
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"authentication_key": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"self_address": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"sequence_number": "3"
},
"event": "write_resource",
"resource": "0x1::account::Account"
},
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"coin": {
"value": "9777"
},
"deposit_events": {
"counter": "1",
"guid": {
"id": {
"addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"creation_num": "1"
}
}
},
"withdraw_events": {
"counter": "1",
"guid": {
"id": {
"addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"creation_num": "2"
}
}
}
},
"event": "write_resource",
"resource": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
},
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"counter": "4"
},
"event": "write_resource",
"resource": "0x1::guid::Generator"
},
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"message": "hello!",
"message_change_events": {
"counter": "0",
"guid": {
"id": {
"addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"creation_num": "3"
}
}
}
},
"event": "write_resource",
"resource": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb::Message::MessageHolder"
}
],
"gas_used": 41,
"success": true,
"version": 3488,
"vm_status": "Executed successfully"
}
}
Additionally, profiles can replace addresses in the function id.
$ aptos move run --function-id default::message::set_message --args string:hello!
{
"Result": {
"changes": [
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"authentication_key": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"self_address": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"sequence_number": "3"
},
"event": "write_resource",
"resource": "0x1::account::Account"
},
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"coin": {
"value": "9777"
},
"deposit_events": {
"counter": "1",
"guid": {
"id": {
"addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"creation_num": "1"
}
}
},
"withdraw_events": {
"counter": "1",
"guid": {
"id": {
"addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"creation_num": "2"
}
}
}
},
"event": "write_resource",
"resource": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
},
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"counter": "4"
},
"event": "write_resource",
"resource": "0x1::guid::Generator"
},
{
"address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"data": {
"message": "hello!",
"message_change_events": {
"counter": "0",
"guid": {
"id": {
"addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb",
"creation_num": "3"
}
}
}
},
"event": "write_resource",
"resource": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb::Message::MessageHolder"
}
],
"gas_used": 41,
"success": true,
"version": 3488,
"vm_status": "Executed successfully"
}
}
Arguments in JSON
Package info
This section references the CliArgs
example package, which contains the following manifest:
[package]
name = "CliArgs"
version = "0.1.0"
upgrade_policy = "compatible"
[addresses]
test_account = "_"
[dependencies]
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "mainnet", subdir = "aptos-move/framework/aptos-framework" }
Here, the package is deployed under the named address test_account
.
Set your working directory to aptos-move/move-examples/cli_args
to follow along:
cd <aptos-core-parent-directory>/aptos-core/aptos-move/move-examples/cli_args
Deploying the package
Start by mining a vanity address for Ace, who will deploy the package:
aptos key generate \
--vanity-prefix 0xace \
--output-file ace.key
Output
{
"Result": {
"Account Address:": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"PublicKey Path": "ace.key.pub",
"PrivateKey Path": "ace.key"
}
}
The exact account address should vary for each run, though the vanity prefix should not.
Store Ace's address in a shell variable, so you can call it inline later on:
# Your exact address should vary
ace_addr=0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46
Fund Ace's account with the faucet (either devnet or testnet):
aptos account fund-with-faucet --account $ace_addr
Output
{
"Result": "Added 100000000 Octas to account acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46"
}
Now publish the package under Ace's account:
aptos move publish \
--named-addresses test_account=$ace_addr \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0x1d7b074dd95724c5459a1c30fe4cb3875e7b0478cc90c87c8e3f21381625bec1",
"gas_used": 1294,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 0,
"success": true,
"timestamp_us": 1685077849297587,
"version": 528422121,
"vm_status": "Executed successfully"
}
}
Entry functions
The only module in the package, cli_args.move
, defines a simple Holder
resource with fields of various data types:
module test_account::cli_args {
use std::signer;
use aptos_std::type_info::{Self, TypeInfo};
use std::string::String;
struct Holder has key, drop {
u8_solo: u8,
bytes: vector<u8>,
utf8_string: String,
bool_vec: vector<bool>,
address_vec_vec: vector<vector<address>>,
type_info_1: TypeInfo,
type_info_2: TypeInfo,
}
A public entry function with multi-nested vectors can be used to set the fields:
/// Set values in a `Holder` under `account`.
public entry fun set_vals<T1, T2>(
account: signer,
u8_solo: u8,
bytes: vector<u8>,
utf8_string: String,
bool_vec: vector<bool>,
address_vec_vec: vector<vector<address>>,
) acquires Holder {
let account_addr = signer::address_of(&account);
if (exists<Holder>(account_addr)) {
move_from<Holder>(account_addr);
};
move_to(&account, Holder {
u8_solo,
bytes,
utf8_string,
bool_vec,
address_vec_vec,
type_info_1: type_info::type_of<T1>(),
type_info_2: type_info::type_of<T2>(),
});
}
After the package has been published, aptos move run
can be used to call set_vals()
:
To pass vectors (including nested vectors) as arguments from the command line, use JSON syntax escaped with quotes!
aptos move run \
--function-id $ace_addr::cli_args::set_vals \
--type-args \
0x1::account::Account \
0x1::chain_id::ChainId \
--args \
u8:123 \
"hex:0x1234" \
"string:hello, world\! ♥" \
"bool:[false, true, false, false]" \
'address:[["0xace", "0xbee"], ["0xcad"], []]' \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0x5e141dc6c28e86fa9f5594de93d07a014264ebadfb99be6db922a929eb1da24f",
"gas_used": 504,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 1,
"success": true,
"timestamp_us": 1685077888820037,
"version": 528422422,
"vm_status": "Executed successfully"
}
}
The function ID, type arguments, and arguments can alternatively be specified in a JSON file:
{
"function_id": "<test_account>::cli_args::set_vals",
"type_args": ["0x1::account::Account", "0x1::chain_id::ChainId"],
"args": [
{
"type": "u8",
"value": 123
},
{
"type": "hex",
"value": "0x1234"
},
{
"type": "string",
"value": "hello, world! ♥"
},
{
"type": "bool",
"value": [false, true, false, false]
},
{
"type": "address",
"value": [["0xace", "0xbee"], ["0xcad"], []]
}
]
}
Here, the call to aptos move run
looks like:
aptos move run \
--json-file entry_function_arguments.json \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0x60a32315bb48bf6d31629332f6b1a3471dd0cb016fdee8d0bb7dcd0be9833e60",
"gas_used": 3,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 2,
"success": true,
"timestamp_us": 1685077961499641,
"version": 528422965,
"vm_status": "Executed successfully"
}
}
If you are trying to run the example yourself don't forget to substitute Ace's actual address for <test_account>
in entry_function_arguments.json
!
View functions
Once the values in a Holder
have been set, the reveal()
view function can be used to check the first three fields, and to compare type arguments against the last two fields:
struct RevealResult has drop {
u8_solo: u8,
bytes: vector<u8>,
utf8_string: String,
bool_vec: vector<bool>,
address_vec_vec: vector<vector<address>>,
type_info_1_match: bool,
type_info_2_match: bool
}
#[view]
/// Pack into a `RevealResult` the first three fields in host's
/// `Holder`, as well as two `bool` flags denoting if `T1` & `T2`
/// respectively match `Holder.type_info_1` & `Holder.type_info_2`,
/// then return the `RevealResult`.
public fun reveal<T1, T2>(host: address): RevealResult acquires Holder {
let holder_ref = borrow_global<Holder>(host);
RevealResult {
u8_solo: holder_ref.u8_solo,
bytes: holder_ref.bytes,
utf8_string: holder_ref.utf8_string,
bool_vec: holder_ref.bool_vec,
address_vec_vec: holder_ref.address_vec_vec,
type_info_1_match:
type_info::type_of<T1>() == holder_ref.type_info_1,
type_info_2_match:
type_info::type_of<T2>() == holder_ref.type_info_2
}
}
}
This view function can be called with arguments specified either from the CLI or from a JSON file:
aptos move view \
--function-id $ace_addr::cli_args::reveal \
--type-args \
0x1::account::Account \
0x1::account::Account \
--args address:$ace_addr
aptos move view --json-file view_function_arguments.json
If you are trying to run the example yourself don't forget to substitute Ace's actual address for <test_account>
in view_function_arguments.json
(twice)!
{
"function_id": "<test_account>::cli_args::reveal",
"type_args": ["0x1::account::Account", "0x1::account::Account"],
"args": [
{
"type": "address",
"value": "<test_account>"
}
]
}
{
"Result": [
{
"address_vec_vec": [
[
"0xace",
"0xbee"
],
[
"0xcad"
],
[]
],
"bool_vec": [
false,
true,
false,
false
],
"bytes": "0x1234",
"type_info_1_match": true,
"type_info_2_match": false,
"u8_solo": 123,
"utf8_string": "hello, world! ♥"
}
]
}
Script functions
The package also contains a script, set_vals.move
, which is a wrapper for the setter function:
script {
use test_account::cli_args;
use std::vector;
use std::string::String;
/// Get a `bool` vector where each element indicates `true` if the
/// corresponding element in `u8_vec` is greater than `u8_solo`.
/// Then pack `address_solo` in a `vector<vector<<address>>` and
/// pass resulting argument set to public entry function.
fun set_vals<T1, T2>(
account: signer,
u8_solo: u8,
bytes: vector<u8>,
utf8_string: String,
u8_vec: vector<u8>,
address_solo: address,
) {
let bool_vec = vector::map_ref(&u8_vec, |e_ref| *e_ref > u8_solo);
let addr_vec_vec = vector[vector[address_solo]];
cli_args::set_vals<T1, T2>(account, u8_solo, bytes, utf8_string, bool_vec, addr_vec_vec);
}
}
First compile the package (this will compile the script):
aptos move compile --named-addresses test_account=$ace_addr
Output
{
"Result": [
"acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46::cli_args"
]
}
Next, run aptos move run-script
:
aptos move run-script \
--compiled-script-path build/CliArgs/bytecode_scripts/set_vals.mv \
--type-args \
0x1::account::Account \
0x1::chain_id::ChainId \
--args \
u8:123 \
"hex:0x1234" \
"string:hello, world\! ♥" \
"u8:[122, 123, 124, 125]" \
address:"0xace" \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0x1d644eba8187843cc43919469112339bc2c435a49a733ac813b7bc6c79770152",
"gas_used": 3,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 3,
"success": true,
"timestamp_us": 1685078415935612,
"version": 528426413,
"vm_status": "Executed successfully"
}
}
aptos move run-script \
--compiled-script-path build/CliArgs/bytecode_scripts/set_vals.mv \
--json-file script_function_arguments.json \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0x840e2d6a5ab80d5a570effb3665f775f1755e0fd8d76e52bfa7241aaade883d7",
"gas_used": 3,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 4,
"success": true,
"timestamp_us": 1685078516832128,
"version": 528427132,
"vm_status": "Executed successfully"
}
}
{
"type_args": ["0x1::account::Account", "0x1::chain_id::ChainId"],
"args": [
{
"type": "u8",
"value": 123
},
{
"type": "hex",
"value": "0x1234"
},
{
"type": "string",
"value": "hello, world! ♥"
},
{
"type": "u8",
"value": [122, 123, 124, 125]
},
{
"type": "address",
"value": "0xace"
}
]
}
Both such script function invocations result in the following reveal()
view function output:
aptos move view \
--function-id $ace_addr::cli_args::reveal \
--type-args \
0x1::account::Account \
0x1::chain_id::ChainId \
--args address:$ace_addr
{
"Result": [
{
"address_vec_vec": [["0xace"]],
"bool_vec": [false, false, true, true],
"bytes": "0x1234",
"type_info_1_match": true,
"type_info_2_match": true,
"u8_solo": 123,
"utf8_string": "hello, world! ♥"
}
]
}
As of the time of this writing, the aptos
CLI only supports script function arguments for vectors of type u8
, and only up to a vector depth of 1. Hence vector<address>
and vector<vector<u8>>
are invalid script function argument types.
Multisig governance
Background
This section builds upon the Arguments in JSON section, and likewise references the CliArgs
example package.
If you would like to follow along, start by completing the Arguments in JSON tutorial steps!
For this example, Ace and Bee will conduct governance operations from a 2-of-2 "multisig v2" account (an on-chain multisig account per multisig_account.move
)
Account creation
Since Ace's account was created during the Arguments in JSON tutorial, start by mining a vanity address account for Bee too:
aptos key generate \
--vanity-prefix 0xbee \
--output-file bee.key
Output
{
"Result": {
"PublicKey Path": "bee.key.pub",
"PrivateKey Path": "bee.key",
"Account Address:": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc"
}
}
The exact account address should vary for each run, though the vanity prefix should not.
Store Bee's address in a shell variable, so you can call it inline later on:
# Your exact address should vary
bee_addr=0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc
Fund Bee's account using the faucet:
aptos account fund-with-faucet --account $bee_addr
Output
{
"Result": "Added 100000000 Octas to account beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc"
}
Ace can now create a multisig account:
aptos multisig create \
--additional-owners $bee_addr \
--num-signatures-required 2 \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"multisig_address": "57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5",
"transaction_hash": "0x849cc756de2d3b57210f5d32ae4b5e7d1f80e5d376233885944b6f3cc2124a05",
"gas_used": 1524,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 5,
"success": true,
"timestamp_us": 1685078644186194,
"version": 528428043,
"vm_status": "Executed successfully"
}
}
Store the multisig address in a shell variable:
# Your address should vary
multisig_addr=0x57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5
Inspect the multisig
Use the assorted multisig_account.move
view functions to inspect the multisig:
aptos move view \
--function-id 0x1::multisig_account::num_signatures_required \
--args \
address:"$multisig_addr"
Output
{
"Result": [
"2"
]
}
aptos move view \
--function-id 0x1::multisig_account::owners \
--args \
address:"$multisig_addr"
Output
{
"Result": [
[
"0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46"
]
]
}
aptos move view \
--function-id 0x1::multisig_account::last_resolved_sequence_number \
--args \
address:"$multisig_addr"
Output
{
"Result": [
"0"
]
}
aptos move view \
--function-id 0x1::multisig_account::next_sequence_number \
--args \
address:"$multisig_addr"
Output
{
"Result": [
"1"
]
}
Enqueue a publication transaction
The first multisig transaction enqueued will be a transaction for publication of the CliArgs
example package.
First, generate a publication payload entry function JSON file:
aptos move build-publish-payload \
--named-addresses test_account=$multisig_addr \
--json-output-file publication.json \
--assume-yes
Output
{
"Result": "Publication payload entry function JSON file saved to publication.json"
}
Now have Ace propose publication of the package from the multisig account, storing only the payload hash on-chain:
aptos multisig create-transaction \
--multisig-address $multisig_addr \
--json-file publication.json \
--store-hash-only \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0x70c75903f8e1b1c0069f1e84ef9583ad8000f24124b33a746c88d2b031f7fe2c",
"gas_used": 510,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 6,
"success": true,
"timestamp_us": 1685078836492390,
"version": 528429447,
"vm_status": "Executed successfully"
}
}
Note that the last resolved sequence number is still 0 because no transactions have been resolved:
aptos move view \
--function-id 0x1::multisig_account::last_resolved_sequence_number \
--args \
address:"$multisig_addr"
Output
{
"Result": [
"0"
]
}
However, the next sequence number has been incremented because a transaction has been enqueued:
aptos move view \
--function-id 0x1::multisig_account::next_sequence_number \
--args \
address:"$multisig_addr"
Output
{
"Result": [
"2"
]
}
The multisig transaction enqueued on-chain can now be inspected:
aptos move view \
--function-id 0x1::multisig_account::get_transaction \
--args \
address:"$multisig_addr" \
String:1
Output
{
"Result": [
{
"creation_time_secs": "1685078836",
"creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"payload": {
"vec": []
},
"payload_hash": {
"vec": [
"0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080"
]
},
"votes": {
"data": [
{
"key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"value": true
}
]
}
}
]
}
Note from the above result that no payload is stored on-chain, and that Ace implicitly approved the transaction (voted true
) upon the submission of the proposal.
Enqueue a governance parameter transaction
Now have Bee enqueue a governance parameter setter transaction, storing the entire transaction payload on-chain:
aptos multisig create-transaction \
--multisig-address $multisig_addr \
--function-id $multisig_addr::cli_args::set_vals \
--type-args \
0x1::account::Account \
0x1::chain_id::ChainId \
--args \
u8:123 \
"bool:[false, true, false, false]" \
'address:[["0xace", "0xbee"], ["0xcad"], []]' \
--private-key-file bee.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0xd0a348072d5bfc5a2e5d444f92f0ecc10b978dad720b174303bc6d91342f27ec",
"gas_used": 511,
"gas_unit_price": 100,
"sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"sequence_number": 0,
"success": true,
"timestamp_us": 1685078954841650,
"version": 528430315,
"vm_status": "Executed successfully"
}
}
Note the next sequence number has been incremented again:
aptos move view \
--function-id 0x1::multisig_account::next_sequence_number \
--args \
address:"$multisig_addr"
Output
{
"Result": [
"3"
]
}
Now both the publication and parameter transactions are pending:
aptos move view \
--function-id 0x1::multisig_account::get_pending_transactions \
--args \
address:"$multisig_addr"
Output
{
"Result": [
[
{
"creation_time_secs": "1685078836",
"creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"payload": {
"vec": []
},
"payload_hash": {
"vec": [
"0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080"
]
},
"votes": {
"data": [
{
"key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"value": true
}
]
}
},
{
"creation_time_secs": "1685078954",
"creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"payload": {
"vec": [
"0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00"
]
},
"payload_hash": {
"vec": []
},
"votes": {
"data": [
{
"key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"value": true
}
]
}
}
]
]
}
Execute the publication transaction
Since only Ace has voted on the publication transaction (which he implicitly approved upon proposing) the transaction can't be executed yet:
aptos move view \
--function-id 0x1::multisig_account::can_be_executed \
--args \
address:"$multisig_addr" \
String:1
Output
{
"Result": [
false
]
}
Before Bee votes, however, she verifies that the payload hash stored on-chain matches the publication entry function JSON file:
aptos multisig verify-proposal \
--multisig-address $multisig_addr \
--json-file publication.json \
--sequence-number 1
Output
{
"Result": {
"Status": "Transaction match",
"Multisig transaction": {
"creation_time_secs": "1685078836",
"creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"payload": {
"vec": []
},
"payload_hash": {
"vec": [
"0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080"
]
},
"votes": {
"data": [
{
"key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"value": true
}
]
}
}
}
}
Since Bee has verified that the on-chain payload hash checks out against her locally-compiled package publication JSON file, she votes yes:
aptos multisig approve \
--multisig-address $multisig_addr \
--sequence-number 1 \
--private-key-file bee.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0xa5fb49f1077de6aa6d976e6bcc05e4c50c6cd061f1c87e8f1ea74e7a04a06bd1",
"gas_used": 6,
"gas_unit_price": 100,
"sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"sequence_number": 1,
"success": true,
"timestamp_us": 1685079892130861,
"version": 528437204,
"vm_status": "Executed successfully"
}
}
Now the transaction can be executed:
aptos move view \
--function-id 0x1::multisig_account::can_be_executed \
--args \
address:"$multisig_addr" \
String:1
Output
{
"Result": [
true
]
}
Now either Ace or Bee can invoke the publication transaction from the multisig account, passing the full transaction payload since only the hash was stored on-chain:
aptos multisig execute-with-payload \
--multisig-address $multisig_addr \
--json-file publication.json \
--private-key-file bee.key \
--max-gas 10000 \
--assume-yes
Pending the resolution of #8304, the transaction simulator (which is used to estimate gas costs) is broken for multisig transactions, so you will have to manually specify a max gas amount.
Output
Also pending the resolution of #8304, the CLI output for a successful multisig publication transaction execution results in an API error if only the payload hash has been stored on-chain, but the transaction can be manually verified using an explorer.
Execute the governance parameter transaction
Since only Bee has voted on the governance parameter transaction (which she implicitly approved upon proposing), the transaction can't be executed yet:
aptos move view \
--function-id 0x1::multisig_account::can_be_executed \
--args \
address:"$multisig_addr" \
String:2
Output
{
"Result": [
false
]
}
Before Ace votes, however, he verifies that the payload stored on-chain matches the function arguments he expects:
aptos multisig verify-proposal \
--multisig-address $multisig_addr \
--function-id $multisig_addr::cli_args::set_vals \
--type-args \
0x1::account::Account \
0x1::chain_id::ChainId \
--args \
u8:123 \
"bool:[false, true, false, false]" \
'address:[["0xace", "0xbee"], ["0xcad"], []]' \
--sequence-number 2
Output
{
"Result": {
"Status": "Transaction match",
"Multisig transaction": {
"creation_time_secs": "1685078954",
"creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"payload": {
"vec": [
"0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00"
]
},
"payload_hash": {
"vec": []
},
"votes": {
"data": [
{
"key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"value": true
}
]
}
}
}
}
Note that the verification fails if he modifies even a single argument:
aptos multisig verify-proposal \
--multisig-address $multisig_addr \
--function-id $multisig_addr::cli_args::set_vals \
--type-args \
0x1::account::Account \
0x1::chain_id::ChainId \
--args \
u8:200 \
"bool:[false, true, false, false]" \
'address:[["0xace", "0xbee"], ["0xcad"], []]' \
--sequence-number 2
Output
{
"Error": "Unexpected error: Transaction mismatch: The transaction you provided has a payload hash of 0xe494b0072d6f940317344967cf0e818c80082375833708c773b0275f3ad07e51, but the on-chain transaction proposal you specified has a payload hash of 0x070ed7c3f812f25f585461305d507b96a4e756f784e01c8c59901871267a1580. For more info, see https://doc.alcove.pro/move/move-on-aptos/cli#multisig-governance"
}
Ace approves the transaction:
aptos multisig approve \
--multisig-address $multisig_addr \
--sequence-number 2 \
--private-key-file ace.key \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0x233427d95832234fa13dddad5e0b225d40168b4c2c6b84f5255eecc3e68401bf",
"gas_used": 6,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 7,
"success": true,
"timestamp_us": 1685080266378400,
"version": 528439883,
"vm_status": "Executed successfully"
}
}
Since the payload was stored on-chain, it is not required to execute the pending transaction:
aptos multisig execute \
--multisig-address $multisig_addr \
--private-key-file ace.key \
--max-gas 10000 \
--assume-yes
Output
{
"Result": {
"transaction_hash": "0xbc99f929708a1058b223aa880d04607a78ebe503367ec4dab23af4a3bdb541b2",
"gas_used": 505,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 8,
"success": true,
"timestamp_us": 1685080344045461,
"version": 528440423,
"vm_status": "Executed successfully"