Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Futurepass multisig wallet example #44

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
);
});
Loading