# Using on-chain multisigs on Cosmos SDK ## Intro In this article we'll go over the creation of a multisig wallet, and how to perform some actions with it. ## Setup We'll create a multisig with 3 members with a 2/3 passing threshold. First create the 3 members, Alice, Bob and Carol: ```bash! simd keys add alice --keyring-backend test --home ./.testnets/node0/simd/ simd keys add bob --keyring-backend test --home ./.testnets/node0/simd/ simd keys add carol --keyring-backend test --home ./.testnets/node0/simd/ ``` And we initialize them with some tokens (sent from one of our nodes): ```bash! simd tx bank send $(simd keys show node0 --address --keyring-backend=test --home ./.testnets/node0/simd/) $(simd keys show alice --address --keyring-backend=test --home ./.testnets/node0/simd/) 100stake --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ simd tx bank send $(simd keys show node0 --address --keyring-backend=test --home ./.testnets/node0/simd/) $(simd keys show bob --address --keyring-backend=test --home ./.testnets/node0/simd/) 100stake --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ simd tx bank send $(simd keys show node0 --address --keyring-backend=test --home ./.testnets/node0/simd/) $(simd keys show carol --address --keyring-backend=test --home ./.testnets/node0/simd/) 100stake --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ ``` Now we craft our initialization message, in it we'll include the members' addresses, their weights and the configuration of our multisig. ```json { "members": [ { "address": "cosmos1pr26h2vq9adq3acvh37pz6wtk65u3y8798scq0", "weight": 1000 }, { "address": "cosmos1j4p2xlg393rg4mma0058alzgvkrjdddd2f5fll", "weight": 1000 }, { "address": "cosmos1vaqh39cdex9sgr46ef0tdln5cn0hdyd3s0lx4l", "weight": 1000 } ], "config": { "threshold": 2000, "quorum": 2000, "voting_period": 86400, "revote": false, "early_execution": true } } ``` In the configuration we set the threshold and quorum to the same, 2/3 of the members must vote yes to pass the proposal. Other configurations can set the quorum and threshold to different values to mimic how organizations work. We've also set `early_execution` to true, to allow executing as soon as the proposal passes. Voting period is in seconds, so we've set that to 24h. And finally `revote` was set to false, because we don't want to allow members to change their vote mid-way through. To initialize the multisig, we have to run the `accounts init` passing the account type and the json we created. ```bash! initcontents=$(cat init.json) simd tx accounts init multisig $initcontents --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ --from alice ``` If everything goes well, we'll get back a tx hash, and we'll check the tx result to get our newly created multisig's address. ```bash! simd q tx 472B5B4E181D2F399C0ACE4DEEB26FE4351D13E593ED8E793B005C48BFD32621 --output json | jq -r '.events[] | select(.type == "account_creation") | .attributes[] | select(.key == "address") | .value' ``` In this case, the address is `cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu`. We can now send tokens to it, just like to a normal account. ```bash! simd tx bank send $(simd keys show node0 --address --keyring-backend=test --home ./.testnets/node0/simd/) cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu 10000stake --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ ``` ## Proposals ### Create proposal In this multisig, every action is a proposal. We'll do a simple proposal to send tokens from the multisig to Alice. ```json { "proposal": { "title": "Send 1000 tokens to Alice", "summary": "Alice is a great multisig member so let's pay her.", "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu", "to_address": "cosmos1pr26h2vq9adq3acvh37pz6wtk65u3y8798scq0", "amount": [ { "denom": "stake", "amount": "1000" } ] } ] } } ``` > The content of messages was created using a simple `tx send` command and passing the flag `--generate-only` so we could copy the message. Now we send the tx that will create the proposal: ```bash! propcontents=$(cat createprop.json) simd tx accounts execute cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu cosmos.accounts.defaults.multisig.v1.MsgCreateProposal $propcontents --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ --from alice ``` This will again return a tx hash that we can use to find out the newly created proposal. ```bash! simd q tx 5CA4420B67FB040B3DF2484CB875E030123662F43AE9958A9F8028C1281C8654 --output json | jq -r '.events[] | select(.type == "proposal_created") | .attributes[] | select(.key == "proposal_id") | .value' ``` In this case, because this is the first proposal, we'll get that the proposal ID is 0. We can use this to query it. ```bash! simd q accounts query cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu cosmos.accounts.defaults.multisig.v1.QueryProposal '{"proposal_id":1}' ``` We get back all the details from the proposal, including the end of the voting period and the current status of the proposal. ```yaml response: '@type': /cosmos.accounts.defaults.multisig.v1.QueryProposalResponse proposal: messages: - '@type': /cosmos.bank.v1beta1.MsgSend amount: - amount: "1000" denom: stake from_address: cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu to_address: cosmos1pr26h2vq9adq3acvh37pz6wtk65u3y8798scq0 status: PROPOSAL_STATUS_VOTING_PERIOD summary: Alice is a great multisig member so let's pay her. title: Send 1000 tokens to Alice voting_period_end: "1717064354" ``` ### Vote on the proposal Just like before, we'll use `tx accounts execute`, but this time to vote. As we have a 2/3 passing threshold, we have to vote with at least 2 members. ```bash! simd tx accounts execute cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu cosmos.accounts.defaults.multisig.v1.MsgVote '{"proposal_id":0, "vote":"VOTE_OPTION_YES"}' --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ --from alice --yes simd tx accounts execute cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu cosmos.accounts.defaults.multisig.v1.MsgVote '{"proposal_id":0, "vote":"VOTE_OPTION_YES"}' --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ --from bob --yes ``` ### Execute the proposal Once we got enough votes, we can execute the proposal. ```bash! simd tx accounts execute cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu cosmos.accounts.defaults.multisig.v1.MsgExecuteProposal '{"proposal_id":0}' --fees 5stake --chain-id $CHAINID --keyring-backend test --home ./.testnets/node0/simd/ --from bob --yes ``` Querying the tx hash will get us information about the success or failure of the proposal execution. ```yaml - attributes: - index: true key: proposal_id value: "0" - index: true key: yes_votes value: "2000" - index: true key: no_votes value: "0" - index: true key: abstain_votes value: "0" - index: true key: status value: PROPOSAL_STATUS_PASSED - index: true key: reject_err value: <nil> - index: true key: exec_err value: <nil> - index: true key: msg_index value: "0" type: proposal_tally ``` Now checking the multisig and Alice's balance, we'll see that the send was performed correctly. ```bash! simd q bank balances cosmos1uds6tz96dxfllz7tz3s3tm8tlg6x95g0mc2987sx6psjz98qlpss89sheu balances: - amount: "9000" denom: stake pagination: total: "1" ``` ```bash! simd q bank balances $(./build/simd keys show alice --address) balances: - amount: "1080" denom: stake pagination: total: "1" ```