Skip to main content
2 of 2
deleted 74 characters in body
Shawn Tabrizi
  • 16.3k
  • 2
  • 24
  • 64

Here is an end to end example of creating and executing a multisig with Polkadot JS:

const { ApiPromise, WsProvider } = require('@polkadot/api');
const { Keyring } = require('@polkadot/keyring');
const {
    cryptoWaitReady,
    createKeyMulti,
    encodeAddress,
    sortAddresses,
} = require('@polkadot/util-crypto');

const SS58Prefix = 2;

async function multisigTransfer() {
    await cryptoWaitReady();

    // create a keyring with some non-default values specified
    const keyring = new Keyring({ type: 'sr25519', ss58Format: SS58Prefix });

    // Connect to the local Polkadot node
    const wsProvider = new WsProvider('ws://localhost:9944');
    const api = await ApiPromise.create({ provider: wsProvider });

    // Retrieve the chain & node information via rpc calls
    const [chain, nodeName, nodeVersion] = await Promise.all([
        api.rpc.system.chain(),
        api.rpc.system.name(),
        api.rpc.system.version()
    ]);

    console.log(`You are connected to chain ${chain} using ${nodeName} v${nodeVersion}`);

    // create accounts based on the development seed
    const alice = keyring.addFromUri('//Alice');
    const bob = keyring.addFromUri('//Bob');
    const charlie = keyring.addFromUri('//Charlie');

    // Define the multisig transfer details
    const multisigCall = api.tx.balances.transfer(alice.address, 1_234_567);
    const addresses = [alice.address, bob.address, charlie.address]
    const threshold = 2;
    const { weight: maxWeight } = await multisigCall.paymentInfo(alice);

    // Address as a byte array.
    const multisigAddress = encodeAddress(createKeyMulti(addresses, threshold), SS58Prefix);
    console.log(multisigAddress);

    await fundAddress(api, alice, multisigAddress, 1_000_000_000_000)

    await approveMultisigWithHash(api, alice, threshold, addresses, null, multisigCall.method.hash, maxWeight);

    const info = await api.query.multisig.multisigs(multisigAddress, multisigCall.method.hash);
    const timepoint = info.unwrap().when;

    await approveMultisigWithHash(api, bob, threshold, addresses, timepoint, multisigCall.method.hash, maxWeight);
    await finalizeMultisig(api, charlie, threshold, addresses, timepoint, multisigCall, maxWeight);
}

async function fundAddress(api, sender, to, amount) {
    return new Promise((resolve, reject) => {
        api.tx.balances
            .transfer(to, amount)
            .signAndSend(sender, async (result) => {
                console.log(`Current status is ${result.status}`);

                if (result.status.isInBlock) {
                    console.log(`Transfer completed at blockHash ${result.status.asInBlock}`);
                    resolve();
                }
            }).catch((error) => {
                reject(error);
            });
    });
}

async function approveMultisigWithHash(api, sender, threshold, addresses, timepoint, callHash, maxWeight) {
    return new Promise((resolve, reject) => {
        // Take addresses and remove the sender.
        const otherSignatories = addresses.filter((who) => who !== sender.address);

        // Sort them by public key.
        const otherSignatoriesSorted = sortAddresses(otherSignatories, SS58Prefix);

        api.tx.multisig
            .approveAsMulti(threshold, otherSignatoriesSorted, timepoint, callHash, maxWeight)
            .signAndSend(sender, async (result) => {
                console.log(`Current status is ${result.status}`);

                if (result.status.isInBlock) {
                    console.log(`Transaction included at blockHash ${result.status.asInBlock}`);
                    resolve();
                }
            }).catch((error) => {
                reject(error);
            });
    });
}

async function finalizeMultisig(api, sender, threshold, addresses, timepoint, multisigCall, maxWeight) {
    return new Promise((resolve, reject) => {
        // Take addresses and remove the sender.
        const otherSignatories = addresses.filter((who) => who !== sender.address);

        // Sort them by public key.
        const otherSignatoriesSorted = sortAddresses(otherSignatories, SS58Prefix);

        api.tx.multisig
            .asMulti(threshold, otherSignatoriesSorted, timepoint, multisigCall, maxWeight)
            .signAndSend(sender, async (result) => {
                console.log(`Current status is ${result.status}`);

                if (result.status.isInBlock) {
                    console.log(`Transaction included at blockHash ${result.status.asInBlock}`);
                    resolve();
                }
            }).catch((error) => {
                reject(error);
            });
    });
}

multisigTransfer().catch(console.error);

Here you can see all the events emitted, showing the end to end scenario works on a local polkadot dev envrionment:

enter image description here

Here was a resource I found which helped me complete the scenario:

https://github.com/polkadot-js/api/issues/2878

Shawn Tabrizi
  • 16.3k
  • 2
  • 24
  • 64