Multisig Governance Tutorial
Background
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.
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://aptos.dev/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"