Skip to content

Multisig Governance Tutorial

This section builds upon the Arguments in JSON tutorial. If you have not done that, please complete that tutorial first.

This tutorial likewise references the CliArgs example package.

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)

Since Ace’s account was created during the Arguments in JSON tutorial, start by mining a vanity address account for Bee too:

Terminal window
aptos key generate \
--vanity-prefix 0xbee \
--output-file bee.key
Output
Terminal window
{
"Result": {
"PublicKey Path": "bee.key.pub",
"PrivateKey Path": "bee.key",
"Account Address:": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc"
}
}

Store Bee’s address in a shell variable, so you can call it inline later on:

Terminal window
# Your exact address should vary
bee_addr=0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc

Fund Bee’s account using the faucet:

Terminal window
aptos account fund-with-faucet --account $bee_addr
Output
Terminal window
{
"Result": "Added 100000000 Octas to account beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc"
}

Ace can now create a multisig account:

Terminal window
aptos multisig create \
--additional-owners $bee_addr \
--num-signatures-required 2 \
--private-key-file ace.key \
--assume-yes
Output
Terminal window
{
"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:

Terminal window
# Your address should vary
multisig_addr=0x57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5

Use the assorted multisig_account.move view functions to inspect the multisig:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::num_signatures_required \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"Result": [
"2"
]
}
Terminal window
aptos move view \
--function-id 0x1::multisig_account::owners \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"Result": [
[
"0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc",
"0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46"
]
]
}
Terminal window
aptos move view \
--function-id 0x1::multisig_account::last_resolved_sequence_number \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"Result": [
"0"
]
}
Terminal window
aptos move view \
--function-id 0x1::multisig_account::next_sequence_number \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"Result": [
"1"
]
}

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:

Terminal window
aptos move build-publish-payload \
--named-addresses test_account=$multisig_addr \
--json-output-file publication.json \
--assume-yes
Output
Terminal window
{
"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:

Terminal window
aptos multisig create-transaction \
--multisig-address $multisig_addr \
--json-file publication.json \
--store-hash-only \
--private-key-file ace.key \
--assume-yes
Output
Terminal window
{
"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:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::last_resolved_sequence_number \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"Result": [
"0"
]
}

However, the next sequence number has been incremented because a transaction has been enqueued:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::next_sequence_number \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"Result": [
"2"
]
}

The multisig transaction enqueued on-chain can now be inspected:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::get_transaction \
--args \
address:"$multisig_addr" \
u64:1
Output
Terminal window
{
"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

Section titled “Enqueue a governance parameter transaction”

Now have Bee enqueue a governance parameter setter transaction, storing the entire transaction payload on-chain:

Terminal window
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
Terminal window
{
"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:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::next_sequence_number \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"Result": [
"3"
]
}

Now both the publication and parameter transactions are pending:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::get_pending_transactions \
--args \
address:"$multisig_addr"
Output
Terminal window
{
"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
}
]
}
}
]
]
}

Since only Ace has voted on the publication transaction (which he implicitly approved upon proposing) the transaction can’t be executed yet:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::can_be_executed \
--args \
address:"$multisig_addr" \
u64:1
Output
Terminal window
{
"Result": [
false
]
}

Before Bee votes, however, she verifies that the payload hash stored on-chain matches the publication entry function JSON file:

Terminal window
aptos multisig verify-proposal \
--multisig-address $multisig_addr \
--json-file publication.json \
--sequence-number 1
Output
Terminal window
{
"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:

Terminal window
aptos multisig approve \
--multisig-address $multisig_addr \
--sequence-number 1 \
--private-key-file bee.key \
--assume-yes
Output
Terminal window
{
"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:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::can_be_executed \
--args \
address:"$multisig_addr" \
u64:1
Output
Terminal window
{
"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:

Terminal window
aptos multisig execute-with-payload \
--multisig-address $multisig_addr \
--json-file publication.json \
--private-key-file bee.key \
--max-gas 10000 \
--assume-yes
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

Section titled “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:

Terminal window
aptos move view \
--function-id 0x1::multisig_account::can_be_executed \
--args \
address:"$multisig_addr" \
u64:2
Output
Terminal window
{
"Result": [
false
]
}

Before Ace votes, however, he verifies that the payload stored on-chain matches the function arguments he expects:

Terminal window
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
Terminal window
{
"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:

Terminal window
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
Terminal window
{
"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://aptos.dev/move/move-on-aptos/cli#multisig-governance"
}

Ace approves the transaction:

Terminal window
aptos multisig approve \
--multisig-address $multisig_addr \
--sequence-number 2 \
--private-key-file ace.key \
--assume-yes
Output
Terminal window
{
"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:

Terminal window
aptos multisig execute \
--multisig-address $multisig_addr \
--private-key-file ace.key \
--max-gas 10000 \
--assume-yes
Output
Terminal window
{
"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"