From 2c600787dc28990d32ac4de070c7bc32348ef568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:44:58 -0300 Subject: [PATCH] docs: improve `cookbook/transaction-request` (#3440) --- .changeset/spicy-mails-sort.md | 4 ++ apps/docs/scripts/launcher-snippet.ts | 4 +- apps/docs/scripts/wrap-snippets.ts | 2 +- .../transaction-request/add-output-coin.ts | 26 ++++++++ .../transaction-request/add-predicate.ts | 4 +- .../transaction-request/add-witness.ts | 32 +++++----- .../transaction-request/estimate-and-fund.ts | 22 +++++++ .../transaction-request/fetch-coins.ts | 22 +++++++ .../transaction-request/fetch-resources.ts | 37 ++++++++++++ .../transaction-request/fund-request.ts | 59 ------------------- .../transaction-request/input-contract.ts | 20 +++++++ .../guide/transactions/transaction-request.md | 42 ++++++++++--- apps/docs/sway/Forc.toml | 1 + apps/docs/sway/script-call-contract/Forc.toml | 7 +++ .../sway/script-call-contract/src/counter.sw | 12 ++++ .../sway/script-call-contract/src/main.sw | 11 ++++ 16 files changed, 214 insertions(+), 91 deletions(-) create mode 100644 .changeset/spicy-mails-sort.md create mode 100644 apps/docs/src/guide/transactions/snippets/transaction-request/add-output-coin.ts create mode 100644 apps/docs/src/guide/transactions/snippets/transaction-request/estimate-and-fund.ts create mode 100644 apps/docs/src/guide/transactions/snippets/transaction-request/fetch-coins.ts create mode 100644 apps/docs/src/guide/transactions/snippets/transaction-request/fetch-resources.ts delete mode 100644 apps/docs/src/guide/transactions/snippets/transaction-request/fund-request.ts create mode 100644 apps/docs/src/guide/transactions/snippets/transaction-request/input-contract.ts create mode 100644 apps/docs/sway/script-call-contract/Forc.toml create mode 100644 apps/docs/sway/script-call-contract/src/counter.sw create mode 100644 apps/docs/sway/script-call-contract/src/main.sw diff --git a/.changeset/spicy-mails-sort.md b/.changeset/spicy-mails-sort.md new file mode 100644 index 00000000000..14b887f7d93 --- /dev/null +++ b/.changeset/spicy-mails-sort.md @@ -0,0 +1,4 @@ +--- +--- + +docs: improve `cookbook/transaction-request` diff --git a/apps/docs/scripts/launcher-snippet.ts b/apps/docs/scripts/launcher-snippet.ts index 2d4dd03089b..72e573112ea 100644 --- a/apps/docs/scripts/launcher-snippet.ts +++ b/apps/docs/scripts/launcher-snippet.ts @@ -1,6 +1,6 @@ -import { launchTestNode } from 'fuels/test-utils'; +import { launchTestNode, TestMessage } from 'fuels/test-utils'; -using node = await launchTestNode({ walletsConfig: { count: 5 } }); +using node = await launchTestNode({ walletsConfig: { count: 5, messages: [new TestMessage()] } }); const LOCAL_NETWORK_URL = node.provider.url; diff --git a/apps/docs/scripts/wrap-snippets.ts b/apps/docs/scripts/wrap-snippets.ts index 11ff2fc0a37..98de3b93e65 100644 --- a/apps/docs/scripts/wrap-snippets.ts +++ b/apps/docs/scripts/wrap-snippets.ts @@ -67,7 +67,7 @@ export const wrapSnippet = (filepath: string) => { Adds `launchTestNode` import, always right below the last `fuels` import and before the next relative one. */ - const launchImport = `import { launchTestNode } from 'fuels/test-utils';`; + const launchImport = `import { launchTestNode, TestMessage } from 'fuels/test-utils';`; const searchStr = `from 'fuels';`; const lastIndexStart = imports.lastIndexOf(searchStr); diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/add-output-coin.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/add-output-coin.ts new file mode 100644 index 00000000000..568c2b300b8 --- /dev/null +++ b/apps/docs/src/guide/transactions/snippets/transaction-request/add-output-coin.ts @@ -0,0 +1,26 @@ +import { Provider, ScriptTransactionRequest, Wallet } from 'fuels'; +import { TestAssetId } from 'fuels/test-utils'; + +import { + LOCAL_NETWORK_URL, + WALLET_PVT_KEY, + WALLET_PVT_KEY_2, +} from '../../../../env'; +import { ScriptSum } from '../../../../typegend'; + +// #region transaction-request-3 +const provider = await Provider.create(LOCAL_NETWORK_URL); + +const recipient1 = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); +const recipient2 = Wallet.fromPrivateKey(WALLET_PVT_KEY_2, provider); + +const baseAssetId = provider.getBaseAssetId(); +const assetA = TestAssetId.A.value; + +const transactionRequest = new ScriptTransactionRequest({ + script: ScriptSum.bytecode, +}); + +transactionRequest.addCoinOutput(recipient1.address, 1000, baseAssetId); +transactionRequest.addCoinOutput(recipient2.address, 500, assetA); +// #endregion transaction-request-3 diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/add-predicate.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/add-predicate.ts index 1b867ddbcba..557fe8df8b8 100644 --- a/apps/docs/src/guide/transactions/snippets/transaction-request/add-predicate.ts +++ b/apps/docs/src/guide/transactions/snippets/transaction-request/add-predicate.ts @@ -13,7 +13,7 @@ import { ScriptSum, SimplePredicate } from '../../../../typegend'; const provider = await Provider.create(LOCAL_NETWORK_URL); const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); -// #region transaction-request-5 +// #region transaction-request-9 // Instantiate the transaction request const transactionRequest = new ScriptTransactionRequest({ script: ScriptSum.bytecode, @@ -42,4 +42,4 @@ const predicateCoins = await predicate.getResourcesToSpend([ // Add the predicate input and resources transactionRequest.addResources(predicateCoins); -// #endregion transaction-request-5 +// #endregion transaction-request-9 diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/add-witness.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/add-witness.ts index ac601192485..3903cd1eec2 100644 --- a/apps/docs/src/guide/transactions/snippets/transaction-request/add-witness.ts +++ b/apps/docs/src/guide/transactions/snippets/transaction-request/add-witness.ts @@ -1,38 +1,34 @@ -import type { Account } from 'fuels'; -import { - Provider, - ScriptTransactionRequest, - WalletUnlocked, - ZeroBytes32, -} from 'fuels'; - -import { LOCAL_NETWORK_URL } from '../../../../env'; +import { Provider, ScriptTransactionRequest, Wallet } from 'fuels'; + +import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env'; import { ScriptSum } from '../../../../typegend'; +// #region transaction-request-10 const provider = await Provider.create(LOCAL_NETWORK_URL); -const witness = ZeroBytes32; +const accountA = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); +const accountB = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); const transactionRequest = new ScriptTransactionRequest({ script: ScriptSum.bytecode, }); -// #region transaction-request-6 // Add a witness directly -transactionRequest.addWitness(witness); +// Add a witness signature directly +const signature = await accountA.signTransaction(transactionRequest); +transactionRequest.addWitness(signature); -// Add a witness using an account -const account: Account = WalletUnlocked.generate({ provider }); -await transactionRequest.addAccountWitnesses(account); -// #endregion transaction-request-6 +// Or add multiple via `addAccountWitnesses` +await transactionRequest.addAccountWitnesses([accountB]); +// #endregion transaction-request-10 -// #region transaction-request-7 +// #region transaction-request-11 // Get the chain ID const chainId = provider.getChainId(); // Get the transaction ID using the Chain ID const transactionId = transactionRequest.getTransactionId(chainId); // TX ID: 0x420f6... -// #endregion transaction-request-7 +// #endregion transaction-request-11 console.log('transactionId', transactionId); console.log('witnesses', transactionRequest.witnesses.length === 2); diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/estimate-and-fund.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/estimate-and-fund.ts new file mode 100644 index 00000000000..4b593a11678 --- /dev/null +++ b/apps/docs/src/guide/transactions/snippets/transaction-request/estimate-and-fund.ts @@ -0,0 +1,22 @@ +import { Provider, ScriptTransactionRequest, Wallet } from 'fuels'; + +import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env'; +import { ScriptSum } from '../../../../typegend'; + +const provider = await Provider.create(LOCAL_NETWORK_URL); +const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); + +// #region transaction-request-4 +const transactionRequest = new ScriptTransactionRequest({ + script: ScriptSum.bytecode, +}); + +const cost = await wallet.getTransactionCost(transactionRequest); + +transactionRequest.gasLimit = cost.gasUsed; +transactionRequest.maxFee = cost.maxFee; + +await wallet.fund(transactionRequest, cost); + +await wallet.sendTransaction(transactionRequest); +// #endregion transaction-request-4 diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/fetch-coins.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/fetch-coins.ts new file mode 100644 index 00000000000..aedcb18b45f --- /dev/null +++ b/apps/docs/src/guide/transactions/snippets/transaction-request/fetch-coins.ts @@ -0,0 +1,22 @@ +import { Provider, ScriptTransactionRequest, Wallet } from 'fuels'; + +import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env'; +import { ScriptSum } from '../../../../typegend'; + +const provider = await Provider.create(LOCAL_NETWORK_URL); +const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); +const baseAssetId = provider.getBaseAssetId(); + +const transactionRequest = new ScriptTransactionRequest({ + script: ScriptSum.bytecode, +}); + +// #region transaction-request-6 +// Fetching coins +const { coins } = await wallet.getCoins(baseAssetId); +const { messages } = await wallet.getMessages(); + +// Adding a specific coin or message +transactionRequest.addCoinInput(coins[0]); +transactionRequest.addMessageInput(messages[0]); +// #endregion transaction-request-6 diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/fetch-resources.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/fetch-resources.ts new file mode 100644 index 00000000000..dc7bec20eec --- /dev/null +++ b/apps/docs/src/guide/transactions/snippets/transaction-request/fetch-resources.ts @@ -0,0 +1,37 @@ +import type { CoinQuantity } from 'fuels'; +import { bn, Provider, ScriptTransactionRequest, Wallet } from 'fuels'; +import { TestAssetId } from 'fuels/test-utils'; + +import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env'; +import { ScriptSum } from '../../../../typegend'; + +const provider = await Provider.create(LOCAL_NETWORK_URL); +const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); + +// #region transaction-request-5 +// Instantiate the transaction request +const transactionRequest = new ScriptTransactionRequest({ + script: ScriptSum.bytecode, +}); + +const baseAssetId = provider.getBaseAssetId(); +const assetA = TestAssetId.A.value; + +// Define the quantities to fetch +const quantities: CoinQuantity[] = [ + { + amount: bn(10000), + assetId: baseAssetId, + }, + { + amount: bn(100), + assetId: assetA, + }, +]; + +// Fetching resources +const resources = await wallet.getResourcesToSpend(quantities); + +// Adding resources (coins or messages) +transactionRequest.addResources(resources); +// #endregion transaction-request-5 diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/fund-request.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/fund-request.ts deleted file mode 100644 index ab8f0294a51..00000000000 --- a/apps/docs/src/guide/transactions/snippets/transaction-request/fund-request.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Coin, MessageCoin, Resource } from 'fuels'; -import { Address, bn, Provider, ScriptTransactionRequest, Wallet } from 'fuels'; - -import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env'; -import { CounterFactory, ScriptSum } from '../../../../typegend'; - -const provider = await Provider.create(LOCAL_NETWORK_URL); -const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); -const address = Address.fromRandom(); - -const message: MessageCoin = { - assetId: provider.getBaseAssetId(), - sender: address, - recipient: address, - nonce: '0x', - amount: bn(0), - daHeight: bn(0), -}; -const coin: Coin = { - id: '0x', - assetId: provider.getBaseAssetId(), - amount: bn(0), - owner: address, - blockCreated: bn(0), - txCreatedIdx: bn(0), -}; -const recipientAddress = address; -const resource = coin; -const resources: Resource[] = [resource]; - -// #region transaction-request-3 -// Instantiate the transaction request -const transactionRequest = new ScriptTransactionRequest({ - script: ScriptSum.bytecode, -}); - -// Adding resources (coins or messages) -transactionRequest.addResources(resources); -transactionRequest.addResource(resource); - -// Adding coin inputs and outputs (including transfer to recipient) -transactionRequest.addCoinInput(coin); -transactionRequest.addCoinOutput( - recipientAddress, - 1000, - provider.getBaseAssetId() -); - -// Adding message inputs -transactionRequest.addMessageInput(message); -// #endregion transaction-request-3 - -// #region transaction-request-4 -const deploy = await CounterFactory.deploy(wallet); -const { contract } = await deploy.waitForResult(); - -// Add the contract input and output using the contract ID -transactionRequest.addContractInputAndOutput(contract.id); -// #endregion transaction-request-4 diff --git a/apps/docs/src/guide/transactions/snippets/transaction-request/input-contract.ts b/apps/docs/src/guide/transactions/snippets/transaction-request/input-contract.ts new file mode 100644 index 00000000000..ccb38131e4c --- /dev/null +++ b/apps/docs/src/guide/transactions/snippets/transaction-request/input-contract.ts @@ -0,0 +1,20 @@ +import { Provider, ScriptTransactionRequest, Wallet } from 'fuels'; + +import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env'; +import { CounterFactory, ScriptSum } from '../../../../typegend'; + +const provider = await Provider.create(LOCAL_NETWORK_URL); +const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); + +// #region transaction-request-8 +const deploy = await CounterFactory.deploy(wallet); +const { contract } = await deploy.waitForResult(); + +const transactionRequest = new ScriptTransactionRequest({ + script: ScriptSum.bytecode, + scriptData: contract.id.toB256(), +}); + +// Add the contract input and output using the contract ID +transactionRequest.addContractInputAndOutput(contract.id); +// #endregion transaction-request-8 diff --git a/apps/docs/src/guide/transactions/transaction-request.md b/apps/docs/src/guide/transactions/transaction-request.md index ede75601993..95ceb4847bb 100644 --- a/apps/docs/src/guide/transactions/transaction-request.md +++ b/apps/docs/src/guide/transactions/transaction-request.md @@ -30,23 +30,47 @@ A `CreateTransactionRequest` is used for create transactions, which are transact Once you have instantiated a transaction request, you can modify it by setting the transaction parameters and policies. This can either be done manually by directly altering the transaction request object, or through helper methods that are available on the above classes. -### Adding Resources to a Transaction Request +### Adding `OutputCoin` -Resources populate the inputs and outputs of a transaction request. This can take the form of coins, messages or contracts. The SDK provides a range of methods for dealing with resources. Below will detail how coins and messages can be added to a transaction request. +Including `OutputCoin`s in the transaction request specifies the UTXOs that will be created once the transaction is processed. These UTXOs represent the amounts being transferred to specified account addresses during the transaction: -<<< @./snippets/transaction-request/fund-request.ts#transaction-request-3{ts:line-numbers} +<<< @./snippets/transaction-request/add-output-coin.ts#transaction-request-3{ts:line-numbers} -### Adding a Contract to a Transaction Request +### Estimating and Funding the Transaction Request -Scripts can perform multiple actions on chain, therefore you may want to chain contract calls. For this you will need to add a contract to the transaction request. This can be done like so: +Before submitting a transaction, it is essential to ensure it is properly funded to meet its requirements and cover the associated fee: -<<< @./snippets/transaction-request/fund-request.ts#transaction-request-4{ts:line-numbers} +<<< @./snippets/transaction-request/estimate-and-fund.ts#transaction-request-4{ts:line-numbers} + +This is the recommended approach for manually estimating and funding a transaction before submission. It ensures that the `gasLimit` and `maxFee` are accurately calculated and that the required amounts for `OutputCoin`s are fulfilled. The `fund` method automatically fetches any missing resource amounts from the calling account and adds them to the transaction request. + +### Manually Fetching Resources + +In certain scenarios, you may need to manually fetch resources. This can be achieved using the `getResourcesToSpend` method, which accepts an array of `CoinQuantities` and returns the necessary resources to meet the specified amounts: + +<<< @./snippets/transaction-request/fetch-resources.ts#transaction-request-5{ts:line-numbers} + +#### Manually Fetching Coins or Messages + +If needed, you can manually include specific coins or messages in the transaction. However, this approach is generally discouraged and should only be used in scenarios where explicitly adding particular coins or messages to the transaction request is required: + +<<< @./snippets/transaction-request/fetch-coins.ts#transaction-request-6{ts:line-numbers} + +### Adding a Contract Input and Output to a Transaction Request + +Imagine that you have a Sway script that manually calls a contract: + +<<< @../../../../sway/script-call-contract/src/main.sw#transaction-request-7{rs:line-numbers} + +In those cases, you will need to add both an `InputContract` and `OutputContract` to the transaction request: + +<<< @./snippets/transaction-request/input-contract.ts#transaction-request-8{ts:line-numbers} ### Adding a Predicate to a Transaction Request Predicates are used to define the conditions under which a transaction can be executed. Therefore you may want to add a predicate to a transaction request to unlock funds that are utilized by a script. This can be added like so: -<<< @./snippets/transaction-request/add-predicate.ts#transaction-request-5{ts:line-numbers} +<<< @./snippets/transaction-request/add-predicate.ts#transaction-request-9{ts:line-numbers} > **Note**: For more information on predicates, including information on configuring them, funding them and using them to unlock funds, please refer to the [predicate guide](../predicates/index.md). @@ -54,7 +78,7 @@ Predicates are used to define the conditions under which a transaction can be ex The SDK provides a way of either modifying the witnesses for a transaction request directly, or by passing accounts. This will then sign the transaction request with the account's private key. Below will detail how to add a witness to a transaction request: -<<< @./snippets/transaction-request/add-witness.ts#transaction-request-6{ts:line-numbers} +<<< @./snippets/transaction-request/add-witness.ts#transaction-request-10{ts:line-numbers} A more complex example of adding multiple witnesses to a transaction request can be seen in the multiple signers guide [here](../cookbook/transactions-with-multiple-signers.md), which validates the signatures inside the script itself. @@ -64,6 +88,6 @@ A more complex example of adding multiple witnesses to a transaction request can The transaction ID is a SHA-256 hash of the entire transaction request. This can be useful for tracking the transaction on chain. To get the transaction ID, you can use the following method: -<<< @./snippets/transaction-request/add-witness.ts#transaction-request-7{ts:line-numbers} +<<< @./snippets/transaction-request/add-witness.ts#transaction-request-11{ts:line-numbers} > **Note**: Any changes made to a transaction request will alter the transaction ID. Therefore, you should only get the transaction ID after all modifications have been made. diff --git a/apps/docs/sway/Forc.toml b/apps/docs/sway/Forc.toml index b737384200f..f5103be9737 100644 --- a/apps/docs/sway/Forc.toml +++ b/apps/docs/sway/Forc.toml @@ -32,6 +32,7 @@ members = [ "simple-predicate", "simple-token", "simple-token-abi", + "script-call-contract", "storage-test-contract", "sum-option-u8", "token", diff --git a/apps/docs/sway/script-call-contract/Forc.toml b/apps/docs/sway/script-call-contract/Forc.toml new file mode 100644 index 00000000000..4fa1432ff68 --- /dev/null +++ b/apps/docs/sway/script-call-contract/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "script-call-contract" + +[dependencies] diff --git a/apps/docs/sway/script-call-contract/src/counter.sw b/apps/docs/sway/script-call-contract/src/counter.sw new file mode 100644 index 00000000000..32217e37d56 --- /dev/null +++ b/apps/docs/sway/script-call-contract/src/counter.sw @@ -0,0 +1,12 @@ +library; + +abi CounterAbi { + #[storage(read)] + fn get_count() -> u64; + + #[storage(write, read)] + fn increment_count(amount: u64) -> u64; + + #[storage(write, read)] + fn decrement_count(amount: u64) -> u64; +} diff --git a/apps/docs/sway/script-call-contract/src/main.sw b/apps/docs/sway/script-call-contract/src/main.sw new file mode 100644 index 00000000000..0e29bf7c735 --- /dev/null +++ b/apps/docs/sway/script-call-contract/src/main.sw @@ -0,0 +1,11 @@ +script; + +mod counter; + +// #region transaction-request-7 +use counter::CounterAbi; +fn main(contract_id: ContractId) -> u64 { + let counter_contract = abi(CounterAbi, contract_id.into()); + counter_contract.get_count() +} +// #endregion transaction-request-7