Skip to content

Commit 4947557

Browse files
authored
Merge pull request #639 from multiversx/TOOL-641-add-multisig-documentation
Tool 641 add multisig documentation
2 parents 9db382a + e0f29d0 commit 4947557

File tree

3 files changed

+369
-0
lines changed

3 files changed

+369
-0
lines changed

cookbook/cookbook.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2613,6 +2613,186 @@ Flow for Creating Guarded Relayed Transactions:
26132613
4. Then, the guardian signs.
26142614
5. Finally, the relayer signs before broadcasting.
26152615

2616+
### Multisig
2617+
2618+
The sdk contains components to interact with the [Multisig Contract](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.5).
2619+
We can deploy a multisig smart contract, add members, propose and execute actions and query the contract.
2620+
The same as the other components, to interact with a multisig smart contract we can use either the MultisigController or the MultisigTransactionsFactory.
2621+
2622+
These operations can be performed using both the **controller** and the **factory**. For a complete list of supported methods, please refer to the autogenerated documentation:
2623+
- [`MultisigController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigController.html)
2624+
- [`MultisigTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigTransactionsFactory.html)
2625+
2626+
#### Deploying a Multisig Smart Contract using the controller
2627+
```js
2628+
{
2629+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
2630+
const bytecode = await loadContractCode("src/testdata/multisig-full.wasm");
2631+
2632+
// create the entrypoint and the multisig controller
2633+
const entrypoint = new DevnetEntrypoint();
2634+
const controller = entrypoint.createMultisigController(abi);
2635+
2636+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
2637+
const alice = await Account.newFromPem(filePath);
2638+
2639+
// fetch the nonce of the network
2640+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
2641+
2642+
const transaction = await controller.createTransactionForDeploy(alice, alice.getNonceThenIncrement(), {
2643+
quorum: 2,
2644+
board: [
2645+
alice.address,
2646+
Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"),
2647+
],
2648+
bytecode: bytecode.valueOf(),
2649+
gasLimit: 100000000n,
2650+
});
2651+
2652+
// sending the transaction
2653+
const txHash = await entrypoint.sendTransaction(transaction);
2654+
2655+
// wait for transaction completion, extract multisig contract's address
2656+
const outcome = await controller.awaitCompletedDeploy(txHash);
2657+
2658+
const contractAddress = outcome[0].contractAddress;
2659+
}
2660+
```
2661+
2662+
#### Deploying a Multisig Smart Contract using the factory
2663+
```js
2664+
{
2665+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
2666+
const bytecode = await loadContractCode("src/testdata/multisig-full.wasm");
2667+
2668+
// create the entrypoint and the multisig factory
2669+
const entrypoint = new DevnetEntrypoint();
2670+
const factory = entrypoint.createMultisigTransactionsFactory(abi);
2671+
2672+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
2673+
const alice = await Account.newFromPem(filePath);
2674+
2675+
// fetch the nonce of the network
2676+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
2677+
2678+
const transaction = await factory.createTransactionForDeploy(alice.address, {
2679+
quorum: 2,
2680+
board: [
2681+
alice.address,
2682+
Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"),
2683+
],
2684+
bytecode: bytecode.valueOf(),
2685+
gasLimit: 100000000n,
2686+
});
2687+
2688+
transaction.nonce = alice.getNonceThenIncrement();
2689+
transaction.signature = await alice.signTransaction(transaction);
2690+
// sending the transaction
2691+
const txHash = await entrypoint.sendTransaction(transaction);
2692+
}
2693+
```
2694+
2695+
#### Propose an action using the controller
2696+
We'll propose an action to send some EGLD to Carol. After we sent the proposal, we'll also parse the outcome of the transaction to get the `proposal id`.
2697+
The id can be used later for signing and performing the proposal.
2698+
2699+
```js
2700+
{
2701+
// create the entrypoint and the multisig controller
2702+
const entrypoint = new DevnetEntrypoint();
2703+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
2704+
const controller = entrypoint.createMultisigController(abi);
2705+
2706+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
2707+
const alice = await Account.newFromPem(filePath);
2708+
2709+
// fetch the nonce of the network
2710+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
2711+
2712+
const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva");
2713+
2714+
const transaction = await controller.createTransactionForProposeTransferExecute(
2715+
alice,
2716+
alice.getNonceThenIncrement(),
2717+
{
2718+
multisigContract: contract,
2719+
to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"),
2720+
gasLimit: 10000000n,
2721+
nativeTokenAmount: 1000000000000000000n,
2722+
},
2723+
);
2724+
2725+
// sending the transaction
2726+
const txHash = await entrypoint.sendTransaction(transaction);
2727+
2728+
// parse the outcome and get the proposal id
2729+
const actionId = await controller.awaitCompletedPerformAction(txHash);
2730+
}
2731+
```
2732+
2733+
#### Propose an action using the factory
2734+
Proposing an action for a multisig contract using the MultisigFactory is very similar to using the controller, but in order to get the proposal id, we need to use MultisigTransactionsOutcomeParser.
2735+
2736+
```js
2737+
{
2738+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
2739+
2740+
// create the entrypoint and the multisig factory
2741+
const entrypoint = new DevnetEntrypoint();
2742+
const provider = entrypoint.createNetworkProvider();
2743+
const factory = entrypoint.createMultisigTransactionsFactory(abi);
2744+
2745+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
2746+
const alice = await Account.newFromPem(filePath);
2747+
2748+
const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva");
2749+
2750+
const transaction = await factory.createTransactionForProposeTransferExecute(alice.address, {
2751+
multisigContract: contract,
2752+
to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"),
2753+
gasLimit: 10000000n,
2754+
nativeTokenAmount: 1000000000000000000n,
2755+
});
2756+
// fetch the nonce of the network
2757+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
2758+
2759+
// set the nonce
2760+
transaction.nonce = alice.getNonceThenIncrement();
2761+
2762+
// sign the transaction
2763+
transaction.signature = await alice.signTransaction(transaction);
2764+
2765+
// sending the transaction
2766+
const txHash = await entrypoint.sendTransaction(transaction);
2767+
2768+
// wait for the transaction to execute
2769+
const transactionAwaiter = new TransactionWatcher(provider);
2770+
const transactionOnNetwork = await transactionAwaiter.awaitCompleted(txHash);
2771+
2772+
// parse the outcome of the transaction
2773+
const parser = new MultisigTransactionsOutcomeParser({ abi });
2774+
const actionId = parser.parseProposeAction(transactionOnNetwork);
2775+
}
2776+
```
2777+
2778+
#### Querying the Multisig Smart Contract
2779+
Unlike creating transactions, querying the multisig can be performed only using the controller.
2780+
Let's query the contract to get all board members.
2781+
2782+
```js
2783+
{
2784+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
2785+
2786+
// create the entrypoint and the multisig controller
2787+
const entrypoint = new DevnetEntrypoint();
2788+
const controller = entrypoint.createMultisigController(abi);
2789+
2790+
const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva");
2791+
2792+
const boardMembers = await controller.getAllBoardMembers({ multisigAddress: contract.toBech32() });
2793+
}
2794+
```
2795+
26162796
### Governance
26172797

26182798
We can create transactions for creating a new governance proposal, vote for a proposal or query the governance contract.

cookbook/generate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
current_dir / "delegation.ts",
1616
current_dir / "relayed.ts",
1717
current_dir / "guarded.ts",
18+
current_dir / "multisig.ts",
1819
current_dir / "governance.ts",
1920
current_dir / "addresses.ts",
2021
current_dir / "wallets.ts",

cookbook/multisig.ts

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import path from "path"; // md-ignore
2+
import { Account, Address, DevnetEntrypoint, TransactionWatcher } from "../src"; // md-ignore
3+
import { MultisigTransactionsOutcomeParser } from "../src/multisig/multisigTransactionsOutcomeParser";
4+
import { loadAbiRegistry, loadContractCode } from "../src/testutils";
5+
// md-start
6+
(async () => {
7+
// ### Multisig
8+
9+
// The sdk contains components to interact with the [Multisig Contract](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.5).
10+
// We can deploy a multisig smart contract, add members, propose and execute actions and query the contract.
11+
// The same as the other components, to interact with a multisig smart contract we can use either the MultisigController or the MultisigTransactionsFactory.
12+
13+
// These operations can be performed using both the **controller** and the **factory**. For a complete list of supported methods, please refer to the autogenerated documentation:
14+
// - `class:MultisigController`
15+
// - `class:MultisigTransactionsFactory`
16+
17+
// #### Deploying a Multisig Smart Contract using the controller
18+
// ```js
19+
{
20+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
21+
const bytecode = await loadContractCode("src/testdata/multisig-full.wasm");
22+
23+
// create the entrypoint and the multisig controller // md-as-comment
24+
const entrypoint = new DevnetEntrypoint();
25+
const controller = entrypoint.createMultisigController(abi);
26+
27+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
28+
const alice = await Account.newFromPem(filePath);
29+
30+
// fetch the nonce of the network // md-as-comment
31+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
32+
33+
const transaction = await controller.createTransactionForDeploy(alice, alice.getNonceThenIncrement(), {
34+
quorum: 2,
35+
board: [
36+
alice.address,
37+
Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"),
38+
],
39+
bytecode: bytecode.valueOf(),
40+
gasLimit: 100000000n,
41+
});
42+
43+
// sending the transaction // md-as-comment
44+
const txHash = await entrypoint.sendTransaction(transaction);
45+
46+
// wait for transaction completion, extract multisig contract's address // md-as-comment
47+
const outcome = await controller.awaitCompletedDeploy(txHash);
48+
49+
const contractAddress = outcome[0].contractAddress;
50+
}
51+
// ```
52+
53+
// #### Deploying a Multisig Smart Contract using the factory
54+
// ```js
55+
{
56+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
57+
const bytecode = await loadContractCode("src/testdata/multisig-full.wasm");
58+
59+
// create the entrypoint and the multisig factory // md-as-comment
60+
const entrypoint = new DevnetEntrypoint();
61+
const factory = entrypoint.createMultisigTransactionsFactory(abi);
62+
63+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
64+
const alice = await Account.newFromPem(filePath);
65+
66+
// fetch the nonce of the network // md-as-comment
67+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
68+
69+
const transaction = await factory.createTransactionForDeploy(alice.address, {
70+
quorum: 2,
71+
board: [
72+
alice.address,
73+
Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"),
74+
],
75+
bytecode: bytecode.valueOf(),
76+
gasLimit: 100000000n,
77+
});
78+
79+
transaction.nonce = alice.getNonceThenIncrement();
80+
transaction.signature = await alice.signTransaction(transaction);
81+
// sending the transaction // md-as-comment
82+
const txHash = await entrypoint.sendTransaction(transaction);
83+
}
84+
// ```
85+
86+
// #### Propose an action using the controller
87+
// We'll propose an action to send some EGLD to Carol. After we sent the proposal, we'll also parse the outcome of the transaction to get the `proposal id`.
88+
// The id can be used later for signing and performing the proposal.
89+
90+
// ```js
91+
{
92+
// create the entrypoint and the multisig controller // md-as-comment
93+
const entrypoint = new DevnetEntrypoint();
94+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
95+
const controller = entrypoint.createMultisigController(abi);
96+
97+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
98+
const alice = await Account.newFromPem(filePath);
99+
100+
// fetch the nonce of the network // md-as-comment
101+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
102+
103+
const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva");
104+
105+
const transaction = await controller.createTransactionForProposeTransferExecute(
106+
alice,
107+
alice.getNonceThenIncrement(),
108+
{
109+
multisigContract: contract,
110+
to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"),
111+
gasLimit: 10000000n,
112+
nativeTokenAmount: 1000000000000000000n,
113+
},
114+
);
115+
116+
// sending the transaction // md-as-comment
117+
const txHash = await entrypoint.sendTransaction(transaction);
118+
119+
// parse the outcome and get the proposal id
120+
const actionId = await controller.awaitCompletedPerformAction(txHash);
121+
}
122+
// ```
123+
124+
// #### Propose an action using the factory
125+
// Proposing an action for a multisig contract using the MultisigFactory is very similar to using the controller, but in order to get the proposal id, we need to use MultisigTransactionsOutcomeParser.
126+
127+
// ```js
128+
{
129+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
130+
131+
// create the entrypoint and the multisig factory // md-as-comment
132+
const entrypoint = new DevnetEntrypoint();
133+
const provider = entrypoint.createNetworkProvider();
134+
const factory = entrypoint.createMultisigTransactionsFactory(abi);
135+
136+
const filePath = path.join("../src", "testdata", "testwallets", "alice.pem");
137+
const alice = await Account.newFromPem(filePath);
138+
139+
const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva");
140+
141+
const transaction = await factory.createTransactionForProposeTransferExecute(alice.address, {
142+
multisigContract: contract,
143+
to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"),
144+
gasLimit: 10000000n,
145+
nativeTokenAmount: 1000000000000000000n,
146+
});
147+
// fetch the nonce of the network // md-as-comment
148+
alice.nonce = await entrypoint.recallAccountNonce(alice.address);
149+
150+
// set the nonce // md-as-comment
151+
transaction.nonce = alice.getNonceThenIncrement();
152+
153+
// sign the transaction // md-as-comment
154+
transaction.signature = await alice.signTransaction(transaction);
155+
156+
// sending the transaction // md-as-comment
157+
const txHash = await entrypoint.sendTransaction(transaction);
158+
159+
// wait for the transaction to execute
160+
const transactionAwaiter = new TransactionWatcher(provider);
161+
const transactionOnNetwork = await transactionAwaiter.awaitCompleted(txHash);
162+
163+
// parse the outcome of the transaction
164+
const parser = new MultisigTransactionsOutcomeParser({ abi });
165+
const actionId = parser.parseProposeAction(transactionOnNetwork);
166+
}
167+
// ```
168+
169+
// #### Querying the Multisig Smart Contract
170+
// Unlike creating transactions, querying the multisig can be performed only using the controller.
171+
// Let's query the contract to get all board members.
172+
173+
// ```js
174+
{
175+
const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json");
176+
177+
// create the entrypoint and the multisig controller // md-as-comment
178+
const entrypoint = new DevnetEntrypoint();
179+
const controller = entrypoint.createMultisigController(abi);
180+
181+
const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva");
182+
183+
const boardMembers = await controller.getAllBoardMembers({ multisigAddress: contract.toBech32() });
184+
}
185+
// ```
186+
})().catch((e) => {
187+
console.log({ e });
188+
});

0 commit comments

Comments
 (0)