1

How can a multisig transaction be initiated, validated and completed, all using polkadotjs from javascript code?

The info I have currently is :

but I didn't find example code.

1 Answer 1

1

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

4
  • In the meantime I was pointed to github.com/allenzhong/polkadot-multisig-example which is similar to your code. But this does not work for me: const timepoint = timeinfo.unwrap().when; I get Property 'unwrap' does not exist on type 'Codec'.
    – ytresza
    Commented May 16 at 7:30
  • You are getting this error with my sample or the sample you linked to?
    – Shawn Tabrizi
    Commented May 16 at 15:11
  • I called this in my code which otherwise worked fine. I wanted to try your code as is, but I can't seem to get a local node running with the multisig pallet. I tried on Westend but it seems it's not setting up the api as expected (TypeError: api.tx.balances.transfer is not a function). Anyway, priorities have shifted and I'm taking another approach now.
    – ytresza
    Commented May 22 at 11:57
  • @ytresza I just ran the production polkadot node using the --dev flag. The error you are getting in your own code means that your timeinfo type is not correct, and you should check how you are getting it. My code queries the chain. Perhaps your type is not an option, so you do not need to unwrap, but printing the value or other debugging will tell you more about the solution.
    – Shawn Tabrizi
    Commented May 23 at 23:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.