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 commit44c9e16of 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- Fund the multisig wallet address created above with some xrp, Can update the multiSigWallet address in script
bash
pnpm call:fundMultiSigWallet- Create futurepass proxy extrinsic Update the caller seed
CALLER_PRIVATE_KEYand signatories
bash
pnpm call:createProxyExtrinsic- Sign the multisig extrinsic with by other holder Update the caller seed
CALLER_PRIVATE_KEYto other holder and signatories
bash
pnpm call:signBySecondHolderIMPORTANT
Ensure the following ENV vars are available before running the examples
CALLER_PRIVATE_KEY- Private key of an account that submits the transaction. Follow this guide to create and fund an account with some test tokens on Porcini (testnet) if you don't have one yet.
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