Multisig wallet

Prerequisites

dYdX CLIEOA Wallets

Instantiate a multisig

Note that a multisig is defined solely by the ordered list of wallets that are parts of it and its threshold - meaning that so long as the input parameter are unchanged, the same multisig address can be instantiated anywhere, including on tools like https://multisig.keplr.app/

1. Add wallets locally

💡 You do not need to own a wallet that is part of the multisig to instantiate it locally - adding only offline ones is

To verify your local setup, list all wallets with

dydxprotocold keys show

As an example, let’s imagine we have a generated wallet A with, e.g.:

dydxprotocold keys add walletA 

And have added offline walletB and walletC

dydxprotocold keys add walletB --pubkey='{"@type":"/cosmos.crypto.secp256k1.PubKey"
dydxprotocold keys add walletC --pubkey='{"@type":"/cosmos.crypto.secp256k1.PubKey",

2. Instantiate the multisig

You need the names of the wallets, e.g. walletA , walletB and walletC and the threshold. ourMultisig is an arbitrary name, choose one that is meaningful to you

$ dydxprotocold keys add ourMultisig --multisig="walletA,walletB,walletC" --multisig-thr

- address: dydx1d02xdy...sw9fcq7jerkwe
  name: ourMultisig
  pubkey: '{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A/C+dj94...9SCD2Uu11"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A9QEyr+iQoCI...IP76IjzVG2vEMf"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"ArarpFkTpgJOnjvUen...KMU5c2zbQ0"}]}'
  type: multi

Key order matters and keys are automatically sorted by their public key. At the time of writing, this is how https://multisig.keplr.app/ instantiates multisig wallets. To change the key ordering, use the --nosort flag.

Now let's send some small funds to the multisig address in order to activate the address.

It is important to activate the address by sending a small amount of tokens to the address otherwise the network will not recognise the multisig address.

$ dydxprotocold tx bank send test dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh \
    100000000000000000adv4tnt \
    --fees 5000000000000000adv4tnt \
    --node https://dydx-testnet-rpc.polkachu.com:443 \
    --chain-id dydx-testnet-4

3. EXAMPLE scenario multisig

Goal: send 700000000000000000adydx (0.7 DYDX) from multisig dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh (added as ourMultisig ) to address dydx1h6x6pxkv2fv3jzsx5hc5efaarhue7gpsdmxafk

💡 Transferring tokens to a multisig address does not differ from transferring tokens to other address types

Let’s say that we have

  1. WalletA , dydx18ym0ts7fw40x2qqw73plgg7wgmkljvvd7x8jk0 - the one that we control

  2. WalletB , dydx1n72zp2myk6qzsec954nd6lap7tn5f6mwtr2qne - an offline wallet of one of our counterparties in the multisig

  3. WalletC , dydx1h03ny3j2k4yltt767r3tjw92qs0vj3p2g5w6x3 - an offline wallet of the other counterparty in the multisig

  4. ourMultisig , dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh - a 2/3 multisig, instantiated with WalletA , WalletB , and WalletC

dydxprotocold keys show ourMultisig
- address: dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh
  name: ourMultisig
  pubkey: '{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A/C+dj94...9SCD2Uu11"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A9QEyr+iQoCI...IP76IjzVG2vEMf"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"ArarpFkTpgJOnjvUen...KMU5c2zbQ0"}]}'
  type: multi

Querying the multisig balances:

dydxprotocold query bank balances dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh

balances:
- amount: "231357500000000000000"
  denom: adv4tnt
pagination:
  next_key: null
  total: "0"

4. Create send token Tx

This step can be executed from any workstation. It is not even necessary to have added walletA , walletB , walletC nor ourMultisig

$  dydxprotocold tx bank send ourMultisig \
     dydx1h6x6pxkv2fv3jzsx5hc5efaarhue7gpsdmxafk \
     100000000000000000adv4tnt \
     --fees 5000000000000000adv4tnt \
     --node https://dydx-testnet-rpc.polkachu.com:443 \
     --chain-id dydx-testnet-4 \
     --generate-only > unsignedTx.json
  • dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh is the address of the multisig from which we send tokens - in case you have instantiated the multisig locally, you can use it’s name, e.g. ourMultisig

  • dydx1h6x6pxkv2fv3jzsx5hc5efaarhue7gpsdmxafk is the recipient address

  • 700000000000000000adydx is the amount to send - 0.7 DYDX

  • --fees value 5000000000000000adydx may need adjustment; the transaction will fail only when broadcasted if this amount is not enough

  • --generate-only will prevent the transaction to be submitted

The result will be stored in the file named unsignedTx.json

unsignedTx.json
```json
{
	"body": {
		"messages": [
			{
				"@type": "/cosmos.bank.v1beta1.MsgSend",
				"from_address": "dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh",
				"to_address": "dydx1h6x6pxkv2fv3jzsx5hc5efaarhue7gpsdmxafk",
				"amount": [{ "denom": "adv4tnt", "amount": "100000000000000000" }]
			}
		],
		"memo": "",
		"timeout_height": "0",
		"extension_options": [],
		"non_critical_extension_options": []
	},
	"auth_info": {
		"signer_infos": [],
		"fee": {
			"amount": [{ "denom": "adv4tnt", "amount": "5000000000000000" }],
			"gas_limit": "200000",
			"payer": "",
			"granter": ""
		},
		"tip": null
	},
	"signatures": []
}

```

5. Sign Token Tx

💡 Each of the required signers must use their own instances of dydxprotocold CLI and their private keys. The unsignedTx.json file needs to be securely transferred to them.

💡 Each signer should verify the transaction details in unsignedTx.json before signing!

dydxprotocold tx sign unsignedTx.json \
    --from walletName \
    --multisig=ourMultisig \
    --multisig= --output-document=walletAsignedTx.json \
    --node https://dydx-testnet-rpc.polkachu.com:443 \
    --chain-id dydx-testnet-4
  • Replace walletName with the respective wallet name ( walletA , walletB , or walletC ).

  • Each signer must have instantiated the multisig locally - they must see dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh when listing their know wallets

  • The value of --multisig can also be the multisig name, e.g. ourMultisig

After this step, there should be two separate signed transaction files, e.g., walletASignedTx.json and walletBSignedTx.json.

// walletASignedTx.json
```json
{
	"signatures": [
		{
			"public_key": {
				"@type": "/cosmos.crypto.secp256k1.PubKey",
				"key": "ArarpFkTpgJOn...wkKMU5c2zbQ0"
			},
			"data": {
				"single": {
					"mode": "SIGN_MODE_LEGACY_AMINO_JSON",
					"signature": "Or9KGawXT...f3kT6DA=="
				}
			},
			"sequence": "0"
		}
	]
}

```

6. Combine signatures

This step can be executed from a workstation where the multisig has been instantiated. Not necessarily an owner of a wallet in the multisig though.

dydxprotocold tx multisign unsignedTx.json \
    ourMultisig \
    walletASignedTx.json \
    walletBSignedTx.json \ 
    --node https://dydx-testnet-rpc.polkachu.com:443 \
    --chain-id dydx-testnet-4 > multisignedTx.json

One needs all the files generated until now: unsignedTx.json , walletASignedTx.json and walletBSignedTx.json . ourMultisig cannot be the multisig address The result is a file called multisignedTx.json

//multisignedTx.json
```json
{
	"body": {
		"messages": [
			{
				"@type": "/cosmos.bank.v1beta1.MsgSend",
				"from_address": "dydx1z79c98w069eugzjme0dkd6quuhm8vfh2a00smh",
				"to_address": "dydx1h6x6pxkv2fv3jzsx5hc5efaarhue7gpsdmxafk",
				"amount": [{ "denom": "adv4tnt", "amount": "100000000000000000" }]
			}
		],
		"memo": "",
		"timeout_height": "0",
		"extension_options": [],
		"non_critical_extension_options": []
	},
	"auth_info": {
		"signer_infos": [
			{
				"public_key": {
					"@type": "/cosmos.crypto.multisig.LegacyAminoPubKey",
					"threshold": 2,
					"public_keys": [
						{
							"@type": "/cosmos.crypto.secp256k1.PubKey",
							"key": "A/C+dj94G1t...CD2Uu11"
						},
						{
							"@type": "/cosmos.crypto.secp256k1.PubKey",
							"key": "A9QEyr+i...P76IjzVG2vEMf"
						},
						{
							"@type": "/cosmos.crypto.secp256k1.PubKey",
							"key": "ArarpFkTpgJO....kKMU5c2zbQ0"
						}
					]
				},
				"mode_info": {
					"multi": {
						"bitarray": { "extra_bits_stored": 3, "elems": "oA==" },
						"mode_infos": [
							{ "single": { "mode": "SIGN_MODE_LEGACY_AMINO_JSON" } },
							{ "single": { "mode": "SIGN_MODE_LEGACY_AMINO_JSON" } }
						]
					}
				},
				"sequence": "0"
			}
		],
		"fee": {
			"amount": [{ "denom": "adv4tnt", "amount": "5000000000000000" }],
			"gas_limit": "200000",
			"payer": "",
			"granter": ""
		},
		"tip": null
	},
	"signatures": [
		"CkDEtBh262Ur...oZrBdEd/R/eRPoM"
	]
}

```

7. Broadcast Send Tokens Tx

$ dydxprotocold tx broadcast multisignedTx.json \
    --node https://dydx-testnet-rpc.polkachu.com:443 
    --chain-id dydx-testnet-4
    
    
Code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: '[]'
timestamp: ""
tx: null
txhash: F51017738F91D758CBF857D0B...739F7177EFC778BA2811585F84

You can use the txhash to check MintScan for the transaction status. If nothing is available on MintScan, then the broadcast or signing were unsuccessful.

Last updated