Skip to content

Commit e91eb6c

Browse files
authored
feat: support TX customization at BaseInvocationScope (#3875)
1 parent 4cbd72c commit e91eb6c

File tree

21 files changed

+643
-162
lines changed

21 files changed

+643
-162
lines changed

.changeset/huge-stars-attend.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@fuel-ts/account": patch
3+
"@fuel-ts/program": patch
4+
---
5+
6+
feat: support TX customization at BaseInvocationScope

apps/docs/.vitepress/config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,6 @@ export default defineConfig({
224224
text: 'Contract Balance',
225225
link: '/guide/contracts/contract-balance',
226226
},
227-
{
228-
text: 'Cost Estimation',
229-
link: '/guide/contracts/cost-estimation',
230-
},
231227
{
232228
text: 'Dependency Estimation',
233229
link: '/guide/contracts/dependency-estimation',
@@ -268,6 +264,10 @@ export default defineConfig({
268264
text: 'Configurable Constants',
269265
link: '/guide/contracts/configurable-constants',
270266
},
267+
{
268+
text: 'Custom Contract Calls',
269+
link: '/guide/contracts/custom-contract-calls',
270+
},
271271
{
272272
text: 'Minted Token Asset ID',
273273
link: '/guide/contracts/minted-token-asset-id',

apps/docs/src/guide/contracts/call-parameters.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ The contract in use in this section has the following implementation:
2020
<!-- This section should explain the `forward` param -->
2121
<!-- forward:example:start -->
2222

23-
The `forward` parameter allows the sending of a specific amount of coins to a contract when a function is called. This is useful when a contract function requires coins for its execution, such as paying fees or transferring funds. The forward parameter helps you control the resources allocated to the contract call and offers protection against potentially costly operations.
23+
The `forward` parameter allows the sending of a specific amount of coins to a contract when a function is called. This is useful when a contract function requires coins for its execution, such as transferring funds to another account or contract.
24+
25+
The forward parameter helps you control the resources allocated to the contract call and offers protection against potentially costly operations.
2426

2527
<!-- forward:example:end -->
2628

apps/docs/src/guide/contracts/cost-estimation.md

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Custom Contract Call
2+
3+
In certain scenarios, your use case may require more control over how contract calls are prepared and submitted.
4+
5+
For instance, imagine a liquidity pool contract where users can deposit a specific asset to receive some form of benefit. To enhance the experience and make it more attractive, one could use a predicate to cover the transaction fees. This way, users only need to provide the asset they wish to deposit without worrying about the fees.
6+
7+
There are two main ways to customize a contract call:
8+
9+
## Approach 1: Customize `assembleTx` Parameters
10+
11+
In most cases, this isn’t necessary. However, if you need precise control, you can specify the parameters passed to `assembleTx`, which is used internally to estimate and fund the transaction.
12+
13+
Here’s how it works:
14+
15+
<<< @./snippets/custom-contract-calls/assemble-tx-parameters.ts#assemble-tx-parameters-1{ts:line-numbers}
16+
17+
## Approach 2: Manually Call `assembleTx`
18+
19+
You can also retrieve the transaction request from the invocation scope and manually call `assembleTx` on it. Just like the approach 1 this gives you full control over how the transaction is assembled and funded.
20+
21+
<<< @./snippets/custom-contract-calls/call.ts#custom-contract-call-1{ts:line-numbers}
22+
23+
There are 2 details here that there are essential in this flow
24+
25+
1. `fromRequest`:
26+
This sets the transaction request extracted from the invocation scope. It ensures that any manual changes you’ve applied persist and are used when the transaction is executed.
27+
28+
1. `{ skipAssembleTx: true }` option:
29+
It tells the SDK to skip the automatic `assembleTx` step during the `.call()` execution, since we’ve already manually assembled and funded the transaction using `provider.assembleTx()`. Skipping this step prevents the SDK from re-estimating the transaction and ensures the custom logic remains intact, such as using a predicate as the fee payer.

apps/docs/src/guide/contracts/snippets/call-parameters/forward.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ const { contract } = await deploy.waitForResult();
1111

1212
// #region forward
1313
const amountToForward = 10;
14+
const baseAssetId = await provider.getBaseAssetId();
1415

1516
const { waitForResult } = await contract.functions
1617
.return_context_amount()
1718
.callParams({
18-
forward: [amountToForward, await provider.getBaseAssetId()],
19+
forward: [amountToForward, baseAssetId],
1920
})
2021
.call();
2122

apps/docs/src/guide/contracts/snippets/cost-estimation.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Provider, Wallet } from 'fuels';
2+
import { TestAssetId } from 'fuels/test-utils';
3+
4+
import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env';
5+
import {
6+
LiquidityPool,
7+
LiquidityPoolFactory,
8+
ReturnTruePredicate,
9+
} from '../../../../typegend';
10+
11+
const provider = new Provider(LOCAL_NETWORK_URL);
12+
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);
13+
const predicate = new ReturnTruePredicate({ provider });
14+
15+
const configurableConstants = {
16+
TOKEN: { bits: TestAssetId.A.value },
17+
};
18+
19+
const deploy = await LiquidityPoolFactory.deploy(wallet, {
20+
configurableConstants,
21+
});
22+
23+
const { contract } = await deploy.waitForResult();
24+
const contractId = contract.id.toB256();
25+
26+
const res = await wallet.transfer(predicate.address, 500_000);
27+
await res.waitForResult();
28+
29+
// #region assemble-tx-parameters-1
30+
// Create a new contract instance using the contract id
31+
const liquidityPoolContract = new LiquidityPool(contractId, wallet);
32+
33+
// Execute the contract call manually specifying the assembleTx parameters
34+
const { waitForResult } = await liquidityPoolContract.functions
35+
.deposit({ bits: wallet.address.toB256() })
36+
.callParams({
37+
forward: [1000, TestAssetId.A.value],
38+
})
39+
.assembleTxParams({
40+
feePayerAccount: predicate, // Using predicate as fee payer
41+
accountCoinQuantities: [
42+
{
43+
amount: 1000,
44+
assetId: TestAssetId.A.value,
45+
account: wallet,
46+
changeOutputAccount: wallet,
47+
},
48+
],
49+
})
50+
.call();
51+
52+
const {
53+
transactionResult: { isStatusSuccess },
54+
} = await waitForResult();
55+
56+
// #endregion assemble-tx-parameters-1
57+
console.log('isStatusSuccess', isStatusSuccess);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Provider, Wallet } from 'fuels';
2+
import { TestAssetId } from 'fuels/test-utils';
3+
4+
import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../../env';
5+
import {
6+
LiquidityPool,
7+
LiquidityPoolFactory,
8+
ReturnTruePredicate,
9+
} from '../../../../typegend';
10+
11+
const provider = new Provider(LOCAL_NETWORK_URL);
12+
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);
13+
const predicate = new ReturnTruePredicate({ provider });
14+
15+
const configurableConstants = {
16+
TOKEN: { bits: TestAssetId.A.value },
17+
};
18+
19+
const deploy = await LiquidityPoolFactory.deploy(wallet, {
20+
configurableConstants,
21+
});
22+
23+
const { contract } = await deploy.waitForResult();
24+
const contractId = contract.id.toB256();
25+
26+
const res = await wallet.transfer(predicate.address, 500_000);
27+
await res.waitForResult();
28+
29+
// #region custom-contract-call-1
30+
// Create a new contract instance using the contract id
31+
const liquidityPoolContract = new LiquidityPool(contractId, wallet);
32+
33+
// Create invocation scope to call the deposit function
34+
const scope = liquidityPoolContract.functions
35+
.deposit({ bits: wallet.address.toB256() })
36+
.callParams({
37+
forward: [1000, TestAssetId.A.value],
38+
});
39+
40+
// Get the transaction request
41+
const request = await scope.getTransactionRequest();
42+
43+
/**
44+
* Using "assembleTx" to estimate and fund the transaction.
45+
*/
46+
await provider.assembleTx({
47+
request,
48+
feePayerAccount: predicate, // Using predicate as fee payer
49+
accountCoinQuantities: [
50+
{
51+
amount: 1000,
52+
assetId: TestAssetId.A.value,
53+
account: wallet,
54+
changeOutputAccount: wallet,
55+
},
56+
],
57+
});
58+
59+
// Use the "call" method to submit the transaction, skipping the "assembleTx" step
60+
const response = await scope
61+
.fromRequest(request)
62+
.call({ skipAssembleTx: true });
63+
64+
const {
65+
transactionResult: { isStatusSuccess },
66+
} = await response.waitForResult();
67+
68+
// #endregion custom-contract-call-1
69+
console.log('isStatusSuccess', isStatusSuccess);

apps/docs/src/guide/cookbook/snippets/signature-script.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ const script = new ScriptSigning(signer);
1818
const witnessIndex = 0;
1919

2020
// Creating the scope invocation to be used later
21-
const request = await script.functions
22-
.main(signer.address.toB256(), witnessIndex)
23-
.getTransactionRequest();
21+
const scope = script.functions.main(signer.address.toB256(), witnessIndex);
22+
23+
// Creating the scope invocation to be used later
24+
const request = await scope.getTransactionRequest();
2425

2526
// Signing the transaction request before estimation
2627
let signature = await signer.signTransaction(request);
@@ -44,10 +45,12 @@ signature = await signer.signTransaction(assembledRequest);
4445
assembledRequest.updateWitness(witnessIndex, signature);
4546

4647
// Sending the transaction request
47-
const submit = await signer.sendTransaction(assembledRequest);
48+
const { waitForResult } = await scope.call({ skipAssembleTx: true });
4849

4950
// Getting the result of the transaction
50-
const { isStatusSuccess } = await submit.waitForResult();
51+
const {
52+
transactionResult: { isStatusSuccess },
53+
} = await waitForResult();
5154
// #endregion signature-script
5255

5356
console.log('isStatusSuccess', isStatusSuccess);

0 commit comments

Comments
 (0)