From 475fbac85a8c82a477726fa27638df6bb5905bf5 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 29 Jul 2025 12:19:07 +0300 Subject: [PATCH] Add multisig and governance to v14 docs --- .../sdk-js/sdk-js-cookbook-v14.md | 346 ++++++++++++++++++ 1 file changed, 346 insertions(+) diff --git a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md index 443d82c5..dd70470f 100644 --- a/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md +++ b/docs/sdk-and-tools/sdk-js/sdk-js-cookbook-v14.md @@ -2626,6 +2626,352 @@ Flow for Creating Guarded Relayed Transactions: 4. Then, the guardian signs. 5. Finally, the relayer signs before broadcasting. +### Multisig + +The sdk contains components to interact with the [Multisig Contract](https://github.com/multiversx/mx-contracts-rs/releases/tag/v0.45.5). +We can deploy a multisig smart contract, add members, propose and execute actions and query the contract. +The same as the other components, to interact with a multisig smart contract we can use either the MultisigController or the MultisigTransactionsFactory. + +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: +- [`MultisigController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigController.html) +- [`MultisigTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/MultisigTransactionsFactory.html) + +#### Deploying a Multisig Smart Contract using the controller +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createMultisigController(abi); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForDeploy(alice, alice.getNonceThenIncrement(), { + quorum: 2, + board: [ + alice.address, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ], + bytecode: bytecode.valueOf(), + gasLimit: 100000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract multisig contract's address + const outcome = await controller.awaitCompletedDeploy(txHash); + + const contractAddress = outcome[0].contractAddress; +} +``` + +#### Deploying a Multisig Smart Contract using the factory +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const bytecode = await loadContractCode("src/testdata/multisig-full.wasm"); + + // create the entrypoint and the multisig factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createMultisigTransactionsFactory(abi); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = factory.createTransactionForDeploy(alice.address, { + quorum: 2, + board: [ + alice.address, + Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + ], + bytecode: bytecode.valueOf(), + gasLimit: 100000000n, + }); + + transaction.nonce = alice.getNonceThenIncrement(); + transaction.signature = await alice.signTransaction(transaction); + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); +} +``` + +#### Propose an action using the controller +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`. +The id can be used later for signing and performing the proposal. + +```js +{ + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + const controller = entrypoint.createMultisigController(abi); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = await controller.createTransactionForProposeTransferExecute( + alice, + alice.getNonceThenIncrement(), + { + multisigContract: contract, + to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gasLimit: 10000000n, + nativeTokenAmount: 1000000000000000000n, + }, + ); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // parse the outcome and get the proposal id + const actionId = await controller.awaitCompletedPerformAction(txHash); +} +``` + +#### Propose an action using the factory +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. + +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + + // create the entrypoint and the multisig factory + const entrypoint = new DevnetEntrypoint(); + const provider = entrypoint.createNetworkProvider(); + const factory = entrypoint.createMultisigTransactionsFactory(abi); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const transaction = factory.createTransactionForProposeTransferExecute(alice.address, { + multisigContract: contract, + to: Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + gasLimit: 10000000n, + nativeTokenAmount: 1000000000000000000n, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for the transaction to execute + const transactionAwaiter = new TransactionWatcher(provider); + const transactionOnNetwork = await transactionAwaiter.awaitCompleted(txHash); + + // parse the outcome of the transaction + const parser = new MultisigTransactionsOutcomeParser({ abi }); + const actionId = parser.parseProposeAction(transactionOnNetwork); +} +``` + +#### Querying the Multisig Smart Contract +Unlike creating transactions, querying the multisig can be performed only using the controller. +Let's query the contract to get all board members. + +```js +{ + const abi = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); + + // create the entrypoint and the multisig controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createMultisigController(abi); + + const contract = Address.newFromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf8llllswuedva"); + + const boardMembers = await controller.getAllBoardMembers({ multisigAddress: contract.toBech32() }); +} +``` + +### Governance + +We can create transactions for creating a new governance proposal, vote for a proposal or query the governance contract. + +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: +- [`GovernanceController`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceController.html) +- [`GovernanceTransactionsFactory`](https://multiversx.github.io/mx-sdk-js-core/v14/classes/GovernanceTransactionsFactory.html) + +#### Creating a new proposal using the controller +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + + const transaction = await controller.createTransactionForNewProposal(alice, alice.getNonceThenIncrement(), { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 500_000000000000000000n, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract proposal's details + const outcome = await controller.awaitCompletedProposeProposal(txHash); + + const proposalNonce = outcome[0].proposalNonce; + const proposalCommitHash = outcome[0].commitHash; + const proposalStartVoteEpoch = outcome[0].startVoteEpoch; + const proposalEndVoteEpoch = outcome[0].endVoteEpoch; +} +``` + +#### Creating a new proposal using the factory +```js +{ + // create the entrypoint and the governance factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createGovernanceTransactionsFactory(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const commitHash = "1db734c0315f9ec422b88f679ccfe3e0197b9d67"; + + const transaction = factory.createTransactionForNewProposal(alice.address, { + commitHash: commitHash, + startVoteEpoch: 10, + endVoteEpoch: 15, + nativeTokenAmount: 500_000000000000000000n, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // waits until the transaction is processed and fetches it from the network + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + + const parser = new GovernanceTransactionsOutcomeParser({}); + const outcome = parser.parseNewProposal(transactionOnNetwork); + const proposalNonce = outcome[0].proposalNonce; + const proposalCommitHash = outcome[0].commitHash; + const proposalStartVoteEpoch = outcome[0].startVoteEpoch; + const proposalEndVoteEpoch = outcome[0].endVoteEpoch; +} +``` + +#### Vote for a proposal using the controller + +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + const transaction = await controller.createTransactionForVoting(alice, alice.getNonceThenIncrement(), { + proposalNonce: 1, + vote: Vote.YES, + }); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract proposal's details + const outcome = await controller.awaitCompletedVote(txHash); + const proposalNonce = outcome[0].proposalNonce; + const vote = outcome[0].vote; + const voteTotalStake = outcome[0].totalStake; + const voteVotingPower = outcome[0].votingPower; +} +``` + +#### Vote for a proposal using the factory +```js +{ + // create the entrypoint and the governance factory + const entrypoint = new DevnetEntrypoint(); + const factory = entrypoint.createGovernanceTransactionsFactory(); + + const filePath = path.join("../src", "testdata", "testwallets", "alice.pem"); + const alice = await Account.newFromPem(filePath); + + const transaction = factory.createTransactionForVoting(alice.address, { + proposalNonce: 1, + vote: Vote.YES, + }); + // fetch the nonce of the network + alice.nonce = await entrypoint.recallAccountNonce(alice.address); + + // set the nonce + transaction.nonce = alice.getNonceThenIncrement(); + + // sign the transaction + transaction.signature = await alice.signTransaction(transaction); + + // sending the transaction + const txHash = await entrypoint.sendTransaction(transaction); + + // wait for transaction completion, extract proposal's details + const transactionOnNetwork = await entrypoint.awaitCompletedTransaction(txHash); + const parser = new GovernanceTransactionsOutcomeParser({}); + const outcome = parser.parseVote(transactionOnNetwork); + const proposalNonce = outcome[0].proposalNonce; + const vote = outcome[0].vote; + const voteTotalStake = outcome[0].totalStake; + const voteVotingPower = outcome[0].votingPower; +} +``` + +#### Querying the governance contract +Unlike creating transactions, querying the contract is only possible using the controller. Let's query the contract to get more details about a proposal. + +```js +{ + // create the entrypoint and the governance controller + const entrypoint = new DevnetEntrypoint(); + const controller = entrypoint.createGovernanceController(); + + const proposalInfo = await controller.getProposal(1); + console.log({ proposalInfo }); +} +``` + ## Addresses Create an `Address` object from a bech32-encoded string: