Skip to content

Commit

Permalink
Futurepass multisig wallet example (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
KarishmaBothara authored Jun 13, 2024
1 parent 87ddbde commit 46e4965
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 0 deletions.
33 changes: 33 additions & 0 deletions examples/substrate/use-futurepass-multisig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Futurepass Pallet

[![Run in StackBlitz](https://img.shields.io/badge/Open_in_StackBlitz-1269D3?style=for-the-badge&logo=stackblitz&logoColor=white)](https://stackblitz.com/github/futureversecom/trn-examples?file=examples%2Fsubstrate%2Fuse-futurepass%2FREADME.md&title=Futurepass%20Pallet%20Examples) [![Pallet Documentation](https://img.shields.io/badge/Pallet_Documentation-black?style=for-the-badge&logo=googledocs&logoColor=white)](https://docs-beta.therootnetwork.com/buidl/substrate/pallet-futurepass)

> [!IMPORTANT]
> 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](../../GUIDES.md) on Porcini (testnet) if you don't have one yet.
## Examples

```bash
# change your working directory to this example first
cd examples/substrate/use-futurepass-multisig

# export all required environments
export THRESHOLD=2 WALLET_NAME=test SIGNATORIES=0xFFfFffFF000000000000000000000000000003CD,0x25451A4de12dcCc2D166922fA938E900fCc4ED24,0x6D1eFDE1BbF146EF88c360AF255D9d54A5D39408

# creates new multisig wallet and note the wallet address
pnpm call:createMultisigWallet

# export all required environments
export CALLER_PRIVATE_KEY=0xcb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854 // private key of fpass holder
export THRESHOLD=2 SIGNATORIES=0x25451A4de12dcCc2D166922fA938E900fCc4ED24,0x6D1eFDE1BbF146EF88c360AF255D9d54A5D39408
# call an extrinsic as FPass account with multisig call (system.remarkWithEvent)
pnpm call:proxyExtrinsic

export CALLER_PRIVATE_KEY=0x79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf // private key of other multisig wallet holder
export THRESHOLD=2 MULTISIG_WALLET=0xe944FAd69B79125706D2481f58b66fcDbED358d7 SIGNATORIES=0x25451A4de12dcCc2D166922fA938E900fCc4ED24,0x6D1eFDE1BbF146EF88c360AF255D9d54A5D39408
# sign by second account of multisig wallet
pnpm call:signByOtherWallet

```
8 changes: 8 additions & 0 deletions examples/substrate/use-futurepass-multisig/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"scripts": {
"call": "tsx --tsconfig ../../../tsconfig.json",
"call:createMultisigWallet": "pnpm call src/createMultisigWallet.ts",
"call:proxyExtrinsic": "pnpm call src/proxyExtrinsic.ts",
"call:signByOtherWallet": "pnpm call src/signByOtherWallet.ts"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createTestKeyring } from "@polkadot/keyring";
import { KeyringOptions } from "@polkadot/keyring/types";
import { bnToBn, objectSpread, u8aSorted } from "@polkadot/util";
import { createKeyMulti } from "@polkadot/util-crypto";
import { withChainContext } from "@trne/utils/withChainContext";
import { cleanEnv, str } from "envalid";

const { CHAIN_ENDPOINT, SIGNATORIES, WALLET_NAME, THRESHOLD } = cleanEnv(process.env, {
CHAIN_ENDPOINT: str({ default: "porcini" }),
SIGNATORIES: str(), // Comma separated signatories
WALLET_NAME: str(), // private key of extrinsic caller
THRESHOLD: str(),
});

/**
* Create multisig wallet with the given signatories and threshold
*/
withChainContext(CHAIN_ENDPOINT, async (api, logger) => {
const genesisHash = api.genesisHash;
const signatoryList = SIGNATORIES.split(",");
console.log("signatoryList::", signatoryList);
const ss58Format = 193;
const options = {
ss58Format,
type: "ethereum",
};
const keyring = createTestKeyring(options as KeyringOptions, true);
const multiSigOptions = {
genesisHash: genesisHash.toString(),
name: WALLET_NAME.trim(),
tags: [],
};
const multiSigAddress = addMultisig(signatoryList, THRESHOLD, multiSigOptions, keyring);

console.log("[1/3] Created multiSig address:", multiSigAddress);
logger.info(multiSigAddress, `Created multiSig address`);
});

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;
}
96 changes: 96 additions & 0 deletions examples/substrate/use-futurepass-multisig/src/proxyExtrinsic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { u8aToHex } from "@polkadot/util";
import { createDispatcher, nativeWalletSigner } from "@therootnetwork/extrinsic";
import { createKeyring } from "@trne/utils/createKeyring";
import { withChainContext } from "@trne/utils/withChainContext";
import { cleanEnv, str } from "envalid";
import assert from "node:assert";

const { CHAIN_ENDPOINT, SIGNATORIES, CALLER_PRIVATE_KEY } = cleanEnv(process.env, {
CHAIN_ENDPOINT: str({ default: "porcini" }),
SIGNATORIES: str(), // Comma separated signatories
CALLER_PRIVATE_KEY: str(), // private key of extrinsic caller to fund multi wallet address
});

/**
* Use `futurepass.proxyExtrinsic` to call `system.remarkWithEvent` and have FPasss account
* to pay for gas.
*
* Assume FPass account of the caller has XRP to pay for gas.
*/
withChainContext(CHAIN_ENDPOINT, async (api, logger) => {
const caller = createKeyring(CALLER_PRIVATE_KEY);
const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrapOr(undefined);
assert(fpAccount);
logger.info(
{
futurepass: {
holder: caller.address,
account: fpAccount.toString(),
},
},
"futurepass details"
);

const multiSigCall = api.tx.system.remarkWithEvent("Hello World");
const u8a = multiSigCall.method.toU8a();
const encodedCallData = u8aToHex(u8a);

const signatoryList = SIGNATORIES.split(",");
console.log("signatoryList::", signatoryList);
const threshold = signatoryList.length;
const maybeTimepoint = null;
const storeCall = false;
const maxWeight = 0;

const call = await api.tx.multisig.asMulti(
threshold,
signatoryList,
maybeTimepoint,
encodedCallData,
storeCall,
maxWeight
);

const proxyExtrinsic = await api.tx.futurepass.proxyExtrinsic(fpAccount, call);
logger.info(
{
parameters: {
fpAccount,
call,
},
},
`create a "futurepass.proxyExtrinsic" extrinsic`
);
const { estimate, signAndSend } = createDispatcher(
api,
caller.address,
[],
nativeWalletSigner(caller)
);

const feeResult = await estimate(proxyExtrinsic);
assert(feeResult.ok, (feeResult.value as Error).message);
logger.info(
{ parameters: { caller: caller.address, fee: feeResult.ok ? feeResult.value : undefined } },
`dispatch extrinsic`
);

const result = await signAndSend(proxyExtrinsic, (status) => {
logger.debug(status);
});
assert(result.ok, (result.value as Error).message);

const { id, events } = result.value;
console.log("events:", events);
const multiSigEvent = events.find((event) => event.name === "multisig.NewMultisig");
assert(multiSigEvent);
logger.info(
{
result: {
extrinsicId: id,
multiSigEvent,
},
},
"dispatch result"
);
});
113 changes: 113 additions & 0 deletions examples/substrate/use-futurepass-multisig/src/signByOtherWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { u8aToHex } from "@polkadot/util";
import { createDispatcher, nativeWalletSigner } from "@therootnetwork/extrinsic";
import { createKeyring } from "@trne/utils/createKeyring";
import { withChainContext } from "@trne/utils/withChainContext";
import { cleanEnv, str } from "envalid";
import assert from "node:assert";

const { CHAIN_ENDPOINT, SIGNATORIES, MULTISIG_WALLET, THRESHOLD, CALLER_PRIVATE_KEY } = cleanEnv(

Check warning on line 8 in examples/substrate/use-futurepass-multisig/src/signByOtherWallet.ts

View workflow job for this annotation

GitHub Actions / Build and Test

'THRESHOLD' is assigned a value but never used
process.env,
{
CHAIN_ENDPOINT: str({ default: "porcini" }),
SIGNATORIES: str(), // Comma separated signatories
MULTISIG_WALLET: str(), // private key of extrinsic caller
THRESHOLD: str(),
CALLER_PRIVATE_KEY: str(), // private key of extrinsic caller to fund multi wallet address
}
);

/**
* Sign multisig extrinisc with other signer
*
*/
withChainContext(CHAIN_ENDPOINT, async (api, logger) => {
const caller = createKeyring(CALLER_PRIVATE_KEY);

logger.info(
{
multisigWallet: {
holder: caller.address,
},
},
"futurepass details"
);

const multiSigCall = api.tx.system.remarkWithEvent("Hello World");
const u8a = multiSigCall.method.toU8a();
const encodedCallData = u8aToHex(u8a);

const signatoryList = SIGNATORIES.split(",");
console.log("signatoryList::", signatoryList);
const threshold = signatoryList.length;
const storeCall = false;

let timepoint = {};
const allEntries = await api.query.multisig.multisigs.entries(MULTISIG_WALLET);
allEntries.forEach(
([
{
args: [accountId],

Check warning on line 49 in examples/substrate/use-futurepass-multisig/src/signByOtherWallet.ts

View workflow job for this annotation

GitHub Actions / Build and Test

'accountId' is defined but never used
},
value,
]) => {
const time = JSON.parse(value);
timepoint = time.when;
}
);
const maybeTimepoint = api.registry.createType("Option<Timepoint>", timepoint);
const maxWeight = 882400098;
console.log("maybeTimepointData::", maybeTimepoint.toHuman());

const call = await api.tx.multisig.asMulti(
threshold,
signatoryList,
maybeTimepoint,
encodedCallData,
storeCall,
maxWeight
);

logger.info(
{
parameters: {
caller: caller.address,
call,
},
},
`create a "futurepass.proxyExtrinsic" extrinsic`
);
const { estimate, signAndSend } = createDispatcher(
api,
caller.address,
[],
nativeWalletSigner(caller)
);

const feeResult = await estimate(call);
assert(feeResult.ok, (feeResult.value as Error).message);
logger.info(
{ parameters: { caller: caller.address, fee: feeResult.ok ? feeResult.value : undefined } },
`dispatch extrinsic`
);

const result = await signAndSend(call, (status) => {
logger.debug(status);
});
assert(result.ok, (result.value as Error).message);

const { id, events } = result.value;
console.log("events::", events);
const multiSigEvent = events.find((event) => event.name === "multisig.MultisigExecuted");
assert(multiSigEvent);
const systemRemarkEvent = events.find((event) => event.name === "system.Remarked");
assert(systemRemarkEvent);
logger.info(
{
result: {
extrinsicId: id,
multiSigEvent,
},
},
"dispatch result"
);
});

0 comments on commit 46e4965

Please sign in to comment.