// This example demonstrates how to use InitiaJS to:
// 1. Create a multisig account
// 2. Create a proposal
// 3. Vote on the proposal
// 4. Execute the proposal
//
// Steps are annotated with comments for clarity.
import {
AccAddress,
bcs,
Coins,
MnemonicKey,
MsgExecute,
MsgSend,
RESTClient,
Tx,
WaitTxBroadcastResult,
Wallet
} from '@initia/initia.js'
import { sha3_256 } from '@noble/hashes/sha3'
// A helper function to deterministically derive a multisig address
export function getMultisigAddress(creator: string, name: string) {
// The address scheme used when generating from seed
const OBJECT_FROM_SEED_ADDRESS_SCHEME = 0xfe
// Serialize the creator address into bytes via BCS
const addrBytes = Buffer.from(bcs.address().serialize(creator).toBytes()).toJSON().data
// Build a seed from the 'multisig_v2' definition and the given name
const seed = Buffer.from(`0x1::multisig_v2::MultisigWallet${name}`, 'ascii').toJSON().data
// Concatenate the address bytes, the seed, and append the scheme byte
const bytes = addrBytes.concat(seed)
bytes.push(OBJECT_FROM_SEED_ADDRESS_SCHEME)
// Hash the combined bytes using sha3_256, then convert to hex string
const sum = sha3_256.create().update(Buffer.from(bytes)).digest()
return Buffer.from(sum).toString('hex')
}
// Configure the REST client for Initia, including gas price/adjustment
const restClient = new RESTClient('https://rest.testnet.initia.xyz', {
gasPrices: '0.015uinit',
gasAdjustment: '1.5'
})
// Example mnemonic keys: 3 participants (multisigCreator, multisigMember1, multisigMember2)
const keys = [
'lawn gentle alpha display brave luxury aunt spot resource problem attend finish clown tilt outer security strike blush inspire gallery mesh law discover mad', // multisig creator
'leisure minimum grow fringe hamster divide leaf evidence bread lift maple rather matrix budget loop envelope warrior hill exotic raven access prevent pottery this', // multisig member 1
'game gown scorpion discover erase various crash nut ill leisure candy resemble tissue roast close dizzy dune speak rug exhaust body boss trip cherry' // multisig member 2
]
// Convert each mnemonic key to a Wallet instance
const accounts = keys.map((mnemonic) => new Wallet(restClient, new MnemonicKey({ mnemonic })))
async function main() {
let signedTx: Tx
let res: WaitTxBroadcastResult
// Destructure the accounts array for convenience
const [multisigCreator, multisigMember1, multisigMember2] = accounts
//
// ===========================
// Step 1: CREATE MULTISIG ACCOUNT
// ===========================
//
const multisigName = 'multisig_name'
// Create a MsgExecute to call 'create_non_weighted_multisig_account'
const msgCreateNonWeightedMultisigAccount = new MsgExecute(
multisigCreator.key.accAddress,
'0x1',
'multisig_v2',
'create_non_weighted_multisig_account',
[],
[
// 1. Multisig name (used in deterministic address generation)
bcs.string().serialize(multisigName),
// 2. Vector of members (3 participants)
bcs
.vector(bcs.address())
.serialize([
multisigCreator.key.accAddress,
multisigMember1.key.accAddress,
multisigMember2.key.accAddress
]),
// 3. Threshold (e.g., require 2 out of 3 approvals)
bcs.u64().serialize(2)
].map((v) => v.toBase64())
)
// Sign and broadcast the TX
signedTx = await multisigCreator.createAndSignTx({
msgs: [msgCreateNonWeightedMultisigAccount]
})
res = await restClient.tx.broadcast(signedTx)
console.log('Multisig account created. Tx hash:', res.txhash)
// The actual multisig address can be obtained from 'CreateMultisigAccountEvent'
// or from the helper function getMultisigAddress:
const multisigAddress = getMultisigAddress(
AccAddress.toHex(multisigCreator.key.accAddress),
multisigName
)
//
// ===========================
// Step 2: CREATE PROPOSAL
// ===========================
//
// 1) First, fund the multisig so it has enough balance to execute future transactions
const msgFundtoMultisig = new MsgSend(
multisigCreator.key.accAddress,
AccAddress.fromHex(multisigAddress),
new Coins({ uinit: 5_000_000 })
)
signedTx = await multisigCreator.createAndSignTx({
msgs: [msgFundtoMultisig]
})
res = await restClient.tx.broadcast(signedTx)
console.log('Funded the multisig address. Tx hash:', res.txhash)
// 2) Create proposals
// Proposal 1: send tokens using `0x1::cosmos::stargate` function
const recipient = 'init1nu7ujl76zac4pkdck8r2zve5zkjaus2xuz8thx'
const msgMiultiSigProposal1 = new MsgSend(
AccAddress.fromHex(multisigAddress),
recipient,
new Coins({ uinit: 1_000_000 })
)
// Proposal 2: send tokens using `0x1::coin::transfer` function
// We need to serialize the arguments in BCS
const msgMiultiSigProposal2Args = [
bcs.address().serialize(recipient), // recipient
bcs.object().serialize('0x8e4733bdabcf7d4afc3d14f0dd46c9bf52fb0fce9e4b996c939e195b8bc891d9'), // coin metadata
bcs.u64().serialize(1_000_000) // amount
]
// Use create_proposal to bundle both proposals
const msgCreateProposal = new MsgExecute(
multisigCreator.key.accAddress,
'0x1',
'multisig_v2',
'create_proposal',
[],
[
bcs.address().serialize(multisigAddress), // multisig address
bcs.vector(bcs.address()).serialize(['0x1', '0x1']), // module addresses
bcs.vector(bcs.string()).serialize(['cosmos', 'coin']), // module names
bcs.vector(bcs.string()).serialize(['stargate', 'transfer']), // function names
bcs.vector(bcs.vector(bcs.string())).serialize([[], []]), // no type args
bcs.vector(bcs.vector(bcs.vector(bcs.u8()))).serialize([
[
[
// Arguments for the first proposal (stargate)
...bcs
.vector(bcs.u8())
.serialize(Buffer.from(JSON.stringify(msgMiultiSigProposal1.toData())))
.toBytes()
]
],
// Arguments for the second proposal (coin::transfer)
msgMiultiSigProposal2Args.map((v) => v.toBytes())
]),
bcs.option(bcs.u64()).serialize(null) // optional expiry duration (null)
].map((v) => v.toBase64())
)
// Broadcast the proposal creation
signedTx = await multisigCreator.createAndSignTx({
msgs: [msgCreateProposal]
})
res = await restClient.tx.broadcast(signedTx)
console.log('Proposal created. Tx hash:', res.txhash)
//
// ===========================
// Step 3: VOTE ON PROPOSAL
// ===========================
//
// Assume the proposal ID is 1
const proposalId = 1
const msgVoteProposal1 = new MsgExecute(
multisigMember1.key.accAddress,
'0x1',
'multisig_v2',
'vote_proposal',
[],
[
bcs.address().serialize(multisigAddress),
bcs.u64().serialize(proposalId),
bcs.bool().serialize(true) // yes vote
].map((v) => v.toBase64())
)
signedTx = await multisigMember1.createAndSignTx({
msgs: [msgVoteProposal1]
})
res = await restClient.tx.broadcast(signedTx)
console.log('Member 1 voted YES. Tx hash:', res.txhash)
// Member 2 also votes YES
const msgVoteProposal2 = new MsgExecute(
multisigMember2.key.accAddress,
'0x1',
'multisig_v2',
'vote_proposal',
[],
[
bcs.address().serialize(multisigAddress),
bcs.u64().serialize(proposalId),
bcs.bool().serialize(true)
].map((v) => v.toBase64())
)
signedTx = await multisigMember2.createAndSignTx({
msgs: [msgVoteProposal2]
})
res = await restClient.tx.broadcast(signedTx)
console.log('Member 2 voted YES. Tx hash:', res.txhash)
//
// ===========================
// Step 4: EXECUTE PROPOSAL
// ===========================
//
// Since we have 2 out of 3 votes, the threshold is met, so we can execute.
const msgExecuteProposal = new MsgExecute(
multisigCreator.key.accAddress,
'0x1',
'multisig_v2',
'execute_proposal',
[],
[
bcs.address().serialize(multisigAddress),
bcs.u64().serialize(proposalId)
].map((v) => v.toBase64())
)
signedTx = await multisigCreator.createAndSignTx({
msgs: [msgExecuteProposal]
})
res = await restClient.tx.broadcast(signedTx)
console.log('Proposal executed. Tx hash:', res.txhash)
}
main()