Skip to content

FuturePass multisig (Native API example) ​

Make a multisig wallet with a FuturePass among its signers, and approve a transfer from it with a second signer. Reference: multisig pallet.

Copied from Futureverse’s futureversecom/trn-examples, deleted with its GitHub organisation, at commit 44c9e16 of 2024-10-31. Apache-2.0: license. Before you run it, read Running the examples.

You don’t need a FuturePass

This page treats FuturePass as something to use. You don’t need one: where the page uses a FuturePass, use an account in your own wallet instead — any EIP-6963 wallet app, MetaMask preferred. If you have a FuturePass, you can move what’s in it to your wallet at any time: https://futurepass.trn.codeology.co.nz/wallet/futurepass. FuturePass explains how it works.

Scripts to use FuturePass proxy extrinsic to multi sig an extrinsic

Examples ​

bash
# change your working directory to this example first
cd examples/substrate/use-fp-multisig-wallet
# export all required environments
export CALLER_PRIVATE_KEY=

Steps:

1. `createMultiSigWallet` - parameter of `signatores` (`-s`), `name` (`-n`) and `threshold` (`-t`)

 Can replace signatores, threshold and wallet name in createMultiSigWallet script

```bash
pnpm call:createMultiSigWallet
  1. Fund the multisig wallet address created above with some xrp, Can update the multiSigWallet address in script
bash
pnpm call:fundMultiSigWallet
  1. Create futurepass proxy extrinsic Update the caller seed CALLER_PRIVATE_KEY and signatories
bash
pnpm call:createProxyExtrinsic
  1. Sign the multisig extrinsic with by other holder Update the caller seed CALLER_PRIVATE_KEY to other holder and signatories
bash
pnpm call:signBySecondHolder

IMPORTANT

Ensure the following ENV vars are available before running the examples

Source files ​

src/createMultiSigWallet.ts ​

ts
import { createTestKeyring } from "@polkadot/keyring";
import { KeyringInstance, KeyringOptions } from "@polkadot/keyring/types";
import { bnToBn, objectSpread, u8aSorted } from "@polkadot/util";
import { createKeyMulti } from "@polkadot/util-crypto";
import { withChainApi } from "@trne/utils/withChainApi";

export const command = "createMultiSigWallet";
export const desc = "Create a multisig wallet from given addresses";

// Create a multisig wallet from given addresses"
withChainApi("porcini", async (api) => {
	const genesisHash = api.genesisHash;
	const signatoryList = [
		"0xFFfFffFF000000000000000000000000000003CD",
		"0x25451A4de12dcCc2D166922fA938E900fCc4ED24",
		"0x6D1eFDE1BbF146EF88c360AF255D9d54A5D39408",
	];
	//	signatories.split(",");
	console.log("signatoryList::", signatoryList);
	const multiSigWalletName = "multiSigWallet";
	const threshold = 2;
	const ss58Format = 193;
	const options = {
		ss58Format,
		type: "ethereum",
	};
	const keyring = createTestKeyring(options as KeyringOptions, true);
	const multiSigOptions = {
		genesisHash: genesisHash.toString(),
		name: multiSigWalletName.trim(),
		tags: [],
	};
	const multiSigAddress = addMultisig(signatoryList, threshold, multiSigOptions, keyring);

	console.log("[1/3] Created multiSig address:", multiSigAddress);
	process.exit(0);
});

function addMultisig(addresses, threshold, meta = {}, keyring: KeyringInstance) {
	let address = createKeyMulti(addresses, threshold);
	address = address.slice(0, 20); // for ethereum addresses
	// we could use `sortAddresses`, but rather use internal encode/decode so we are 100%
	const who = u8aSorted(addresses.map((who) => keyring.decodeAddress(who))).map((who) =>
		keyring.encodeAddress(who)
	);
	const meta1 = objectSpread({}, meta, {
		isMultisig: true,
		threshold: bnToBn(threshold).toNumber(),
		who,
	});
	const pair = keyring.addFromAddress(address, objectSpread({}, meta1, { isExternal: true }), null);
	console.log("MultiSig address created::", pair.address);
	return pair.address;
}

src/createProxyExtrinsic.ts ​

ts
import { u8aToHex } from "@polkadot/util";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

export const command = "createProxyExtrinsic";
export const desc = "Create futurepass multisig proxy extrinsic";

withChainApi("porcini", async (api, caller, logger) => {
	console.log("signer.address:", caller.address);
	const fpassAccount = await api.query.futurepass.holders(caller.address);
	console.log("futurepass account:", fpassAccount.toString());
	const xrp = 2;
	const amount = 100;
	const randomAccount = "0xd094275a3eb8D3300faD74370a71633CfedDCA99";
	// Extrinsic to be used for multi signature call
	const extrinsic = api.tx.assets.transfer(xrp, randomAccount, amount);
	const u8a = extrinsic.method.toU8a();
	const encodedCallData = u8aToHex(u8a);

	const signatoryList = [
		"0x25451A4de12dcCc2D166922fA938E900fCc4ED24",
		"0x6D1eFDE1BbF146EF88c360AF255D9d54A5D39408",
	];
	console.log("signatoryList::", signatoryList);
	const threshold = signatoryList.length;
	const maybeTimepoint = null;
	const maxWeight = 0;

	const multiSigCall = await api.tx.multisig.asMulti(
		threshold,
		signatoryList,
		maybeTimepoint,
		encodedCallData,
		maxWeight
	);
	const proxyExtrinsic = await api.tx.futurepass.proxyExtrinsic(fpassAccount, multiSigCall);

	const { result, extrinsicId } = await sendExtrinsic(proxyExtrinsic, caller, { log: logger });
	console.log("Result:", result);
	console.log("Extrinsic id::", extrinsicId);
	// const [proxyEvent, futurepassEvent] = filterExtrinsicEvents(result.events, [
	// 	"FeeProxy.CallWithFeePreferences",
	// 	"Futurepass.ProxyExecuted",
	// ]);
});

src/fundMultiSigWallet.ts ​

ts
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

export const command = "fundMultiSigWallet";
export const desc = "Fund a multisig wallet with xrp";

// Multi sig wallet used is 0xe944FAd69B79125706D2481f58b66fcDbED358d7
// Update the env CALLER_PRIVATE_KEY with funder account
withChainApi("porcini", async (api, caller, logger) => {
	const multiSigWallet = "0xe944FAd69B79125706D2481f58b66fcDbED358d7";

	const xrp = 2;
	const amount = 100000;
	const extrinsic = await api.tx.assets.transfer(xrp, multiSigWallet, amount);
	const { result, extrinsicId } = await sendExtrinsic(extrinsic, caller, { log: logger });
	// const response = await submitExtrinsic(extrinsic, caller);
	console.log("Result:", result);
	console.log("Extrinsic id::", extrinsicId);
});

src/signBySecondHolder.ts ​

ts
import { u8aToHex } from "@polkadot/util";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
import { sendExtrinsic } from "@trne/utils/sendExtrinsic";
import { withChainApi } from "@trne/utils/withChainApi";

// Sign multisig proxy extrinsic by second holder, update the env CALLER_PRIVATE_KEY to second holders
// MultiSig wallet used is 0xe944FAd69B79125706D2481f58b66fcDbED358d7
withChainApi("porcini", async (api, caller, logger) => {
	console.log("signer.address:", caller.address);

	const xrp = 2;
	const amount = 100;
	const randomAccount = "0xd094275a3eb8D3300faD74370a71633CfedDCA99";
	// Extrinsic to be used for multi signature call
	const extrinsic = api.tx.assets.transfer(xrp, randomAccount, amount);
	const u8a = extrinsic.method.toU8a();
	const encodedCallData = u8aToHex(u8a);

	// Update the signatoryList with the other two multisig wallet addresses
	const signatoryList = [
		"0x6D1eFDE1BbF146EF88c360AF255D9d54A5D39408",
		"0xFFfFffFF000000000000000000000000000003CD",
	];

	const multiSigWallet = "0xe944FAd69B79125706D2481f58b66fcDbED358d7";
	console.log("signatoryList::", signatoryList);
	const threshold = signatoryList.length;
	let timepoint = {};
	const allEntries = await api.query.multisig.multisigs.entries(multiSigWallet);
	allEntries.forEach(
		([
			{
				args: [accountId],
			},
			value,
		]) => {
			const time = JSON.parse(value);
			timepoint = time.when;
		}
	);
	const maybeTimepoint = api.registry.createType("Option<Timepoint>", timepoint);
	const proofSize = 882400098;
	console.log("maybeTimepointData::", maybeTimepoint.toHuman());
	const maxWeight = api.registry.createType("SpWeightsWeightV2Weight", {
		refTime: 646755879000,
		proofSize,
	});
	const multiSigCall = await api.tx.multisig.asMulti(
		threshold,
		signatoryList,
		maybeTimepoint,
		encodedCallData,
		maxWeight
	);

	const { result, extrinsicId } = await sendExtrinsic(multiSigCall, caller);
	console.log("Result:", result);
	console.log("Extrinsic id::", extrinsicId);
	const [assetTransferredEvent] = filterExtrinsicEvents(result.events, ["Assets.Transferred"]);
	if (assetTransferredEvent) {
		const data = assetTransferredEvent.event.data;
		console.log("Transfer xrp successfully executed:::", data.toHuman());
	} else {
		console.log("Something wrong with multi sig signing...");
	}
});

package.json ​

json
{
  "scripts": {
    "call": "tsx --tsconfig ../../../tsconfig.json",
    "call:createMultiSigWallet": "pnpm call src/createMultiSigWallet.ts",
    "call:createProxyExtrinsic": "pnpm call src/createProxyExtrinsic.ts",
    "call:fundMultiSigWallet": "pnpm call src/fundMultiSigWallet.ts",
    "call:signBySecondHolder": "pnpm call src/signBySecondHolder.ts"
  }
}
Source: futureversecom/trn-examples, examples/substrate/use-fp-multisig-wallet (deleted) · commit 44c9e16, 2024-10-31 · Apache-2.0, license · part of Code examples

Curated independently. Source-attributed reference for The Root Network. Not affiliated with Futureverse / TRN Labs.