Skip to content

Commit

Permalink
feat!: autoCost for transaction estimation and funding (#3539)
Browse files Browse the repository at this point in the history
* feat: autocost for funding transactions

* docs: autocost docs

* chore: changeset
  • Loading branch information
danielbate authored Jan 4, 2025
1 parent 0fb6fa8 commit 8d8452e
Show file tree
Hide file tree
Showing 28 changed files with 179 additions and 180 deletions.
6 changes: 6 additions & 0 deletions .changeset/shaggy-zebras-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/account": minor
"@fuel-ts/program": minor
---

feat!: `autoCost` for transaction estimation and funding
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,8 @@ transactionRequest.addCoinOutput(
provider.getBaseAssetId()
);

const txCost = await wallet.getTransactionCost(transactionRequest);

transactionRequest.gasLimit = txCost.gasUsed;
transactionRequest.maxFee = txCost.maxFee;

await wallet.fund(transactionRequest, txCost);
// Estimate and fund the transaction
await transactionRequest.autoCost(wallet);

// Submit the transaction
const response = await wallet.sendTransaction(transactionRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,15 @@ const resources = await predicate.getResourcesToSpend([
amount: amountToReceiver,
},
]);

request.addResources(resources);
request.addWitness('0x');

// Add witnesses including the signer
// Estimate the predicate inputs
const txCost = await predicate.getTransactionCost(request, {
// Estimate and fund the request
request.addWitness('0x');
await request.autoCost(predicate, {
signatureCallback: (txRequest) => txRequest.addAccountWitnesses(signer),
});

request.updatePredicateGasUsed(txCost.estimatedPredicates);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await predicate.fund(request, txCost);

// Add the signer as a witness
await request.addAccountWitnesses(signer);

// Send the transaction
Expand Down
7 changes: 2 additions & 5 deletions apps/docs/src/guide/encoding/snippets/encode-and-decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,8 @@ const encodedArguments = abiInterface.encodeType(argument, [argumentToAdd]);
// The encoded value can now be set on the transaction via the script data property
request.scriptData = encodedArguments;

// Now we can build out the rest of the transaction and then fund it
const txCost = await wallet.getTransactionCost(request);
request.maxFee = txCost.maxFee;
request.gasLimit = txCost.gasUsed;
await wallet.fund(request, txCost);
// Now we can estimate and fund the transaction
await request.autoCost(wallet);

// Finally, submit the built transaction
const response = await wallet.sendTransaction(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ customRequest.addResources(predicateResources);
customRequest.addCoinOutput(receiver.address, amountToReceiver, assetId);

// Estimate the transaction cost and fund accordingly
const txCost = await predicate.getTransactionCost(customRequest);
customRequest.gasLimit = txCost.gasUsed;
customRequest.maxFee = txCost.maxFee;
await predicate.fund(customRequest, txCost);
await customRequest.autoCost(predicate);

// Submit the transaction and await it's result
const predicateTx = await predicate.sendTransaction(customRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,8 @@ const predicateCoins = await predicate.getResourcesToSpend([
// Add the predicate input and resources.
transactionRequest.addResources(predicateCoins);

const txCost = await predicate.getTransactionCost(transactionRequest);

transactionRequest.gasLimit = txCost.gasUsed;
transactionRequest.maxFee = txCost.maxFee;

await predicate.fund(transactionRequest, txCost);
// Estimate and fund the transaction
await transactionRequest.autoCost(predicate);

// Send the transaction using the predicate
const result = await predicate.sendTransaction(transactionRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,8 @@ transactionRequest.addCoinOutput(
provider.getBaseAssetId()
);

const txCost = await predicate.getTransactionCost(transactionRequest);

transactionRequest.gasLimit = txCost.gasUsed;
transactionRequest.maxFee = txCost.maxFee;

await predicate.fund(transactionRequest, txCost);
// Estimate and fund the transaction
await transactionRequest.autoCost(predicate);

const result = await predicate.simulateTransaction(transactionRequest);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,8 @@ const quantities = [
coinQuantityfy([500, ASSET_B]),
];

// 5. Calculate the transaction fee
const txCost = await wallet.getTransactionCost(request, { quantities });

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await wallet.fund(request, txCost);
// 5. Estimate and fund the transaction
await request.autoCost(wallet, { quantities });

// 6. Send the transaction
const tx = await wallet.sendTransaction(request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 auto-cost
const transactionRequest = new ScriptTransactionRequest({
script: ScriptSum.bytecode,
});

await transactionRequest.autoCost(wallet);

await wallet.sendTransaction(transactionRequest);
// #endregion auto-cost
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ const scriptMainFunctionArguments = [1];
transactionRequest.setData(ScriptSum.abi, scriptMainFunctionArguments);

// Fund the transaction
const txCost = await wallet.getTransactionCost(transactionRequest);

transactionRequest.maxFee = txCost.maxFee;
transactionRequest.gasLimit = txCost.gasUsed;

await wallet.fund(transactionRequest, txCost);
await transactionRequest.autoCost(wallet);

// Submit the transaction
const response = await wallet.sendTransaction(transactionRequest);
Expand Down
10 changes: 8 additions & 2 deletions apps/docs/src/guide/transactions/transaction-request.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,17 @@ Including `OutputCoin`s in the transaction request specifies the UTXOs that will

### Estimating and Funding the Transaction Request

Before submitting a transaction, it is essential to ensure it is properly funded to meet its requirements and cover the associated fee:
Before submitting a transaction, it is essential to ensure it is properly funded to meet its requirements and cover the associated fee. The SDK offers two approaches for this, one is to use the `autoCost` helper:

<<< @./snippets/transaction-request/auto-cost.ts#auto-cost{ts:line-numbers}

This approach provides a simple one-liner for estimating and funding the transaction request. Ensuring that the `gasLimit` and `maxFee` are accurately calculated and that the required amounts for `OutputCoin`s are fulfilled, as well as fetching and adding any missing resources from the calling account.

The other more manual approach is as so:

<<< @./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.
This approach provides the same behaviour as the `autoCost` helper, but gives more granular control over the transaction request. The `getTransactionCost` method also returns various information about the simulated request that you may want to use to further modify the transaction request, more on that can be found in the [API reference](https://fuels-ts-docs-api.vercel.app/types/_fuel_ts_account.TransactionCost.html).

### Manually Fetching Resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ const request = new ScriptTransactionRequest({

request.addCoinOutput(receiverAddress, 1000, provider.getBaseAssetId());

const txCost = await sender.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await sender.fund(request, txCost);
await request.autoCost(sender);

const tx = await sender.sendTransaction(request);
await tx.waitForResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ const request = new ScriptTransactionRequest({

request.addCoinOutput(receiverAddress, 1000, provider.getBaseAssetId());

const txCost = await sender.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await sender.fund(request, txCost);
await request.autoCost(sender);

const signedTransaction = await sender.signTransaction(request);
const transactionId = request.getTransactionId(provider.getChainId());
Expand Down
28 changes: 4 additions & 24 deletions packages/account/src/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,12 +610,7 @@ describe('Account', () => {
[amount, assetIdB],
]);

const txCost = await sender.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await sender.fund(request, txCost);
await request.autoCost(sender);

const response = await sender.sendTransaction(request);

Expand Down Expand Up @@ -722,12 +717,7 @@ describe('Account', () => {
request.addCoinOutput(sender.address, amount.div(3), provider.getBaseAssetId());
}

const txCost = await fundingWallet.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await fundingWallet.fund(request, txCost);
await request.autoCost(fundingWallet);

const tx1 = await fundingWallet.sendTransaction(request);
await tx1.waitForResult();
Expand Down Expand Up @@ -799,12 +789,7 @@ describe('Account', () => {
request.addCoinOutput(sender.address, fundingAmount.div(3), provider.getBaseAssetId());
}

const txCost = await fundingWallet.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await fundingWallet.fund(request, txCost);
await request.autoCost(fundingWallet);

const tx1 = await fundingWallet.sendTransaction(request);
await tx1.waitForResult();
Expand Down Expand Up @@ -967,12 +952,7 @@ describe('Account', () => {
const request = new ScriptTransactionRequest();
request.addCoinOutput(wallet.address, 30_000, provider.getBaseAssetId());

const txCost = await wallet.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await expectToThrowFuelError(() => wallet.fund(request, txCost), {
await expectToThrowFuelError(() => request.autoCost(wallet), {
code: ErrorCode.MAX_COINS_REACHED,
message:
'The account retrieving coins has exceeded the maximum number of coins per asset. Please consider combining your coins into a single UTXO.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { InputType, OutputType, TransactionType } from '@fuel-ts/transactions';
import { arrayify, hexlify } from '@fuel-ts/utils';
import { clone } from 'ramda';

import type { ChainInfo, GasCosts } from '../provider';
import type { Account } from '../../account';
import type { ChainInfo, GasCosts, TransactionCostParams } from '../provider';
import { calculateMetadataGasForTxScript, getMaxGas } from '../utils/gas';

import { hashTransaction } from './hash-transaction';
Expand Down Expand Up @@ -67,6 +68,27 @@ export class ScriptTransactionRequest extends BaseTransactionRequest {
this.abis = rest.abis;
}

/**
* Helper function to fund the transaction request with a specified account.
*
* @param account - The account to fund the transaction.
* @param params - The parameters for the transaction cost.
* @returns The current instance of the `ScriptTransactionRequest` funded.
*/
async autoCost(
account: Account,
{ signatureCallback, quantities = [] }: TransactionCostParams = {}
): Promise<ScriptTransactionRequest> {
const txCost = await account.getTransactionCost(this, { signatureCallback, quantities });

this.maxFee = txCost.maxFee;
this.gasLimit = txCost.gasUsed;

await account.fund(this, txCost);

return this;
}

/**
* Converts the transaction request to a `TransactionScript`.
*
Expand Down
14 changes: 2 additions & 12 deletions packages/fuel-gauge/src/advanced-logging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,7 @@ describe('Advanced Logging', () => {
])
.getTransactionRequest();

const txCost = await wallet.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await wallet.fund(request, txCost);
await request.autoCost(wallet);

const tx = await wallet.sendTransaction(request, { estimateTxDependencies: false });

Expand Down Expand Up @@ -312,12 +307,7 @@ describe('Advanced Logging', () => {
.addContracts([advancedLogContract, otherAdvancedLogContract])
.getTransactionRequest();

const txCost = await wallet.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await wallet.fund(request, txCost);
await request.autoCost(wallet);

const tx = await wallet.sendTransaction(request);

Expand Down
2 changes: 1 addition & 1 deletion packages/fuel-gauge/src/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ describe('Contract', () => {
const struct = { a: true, b: 1337 };
const invocationScopes = [contract.functions.foo(num), contract.functions.boo(struct)];
const multiCallScope = contract.multiCall(invocationScopes);
const transactionRequest = await multiCallScope.fundWithRequiredCoins();
const transactionRequest = await multiCallScope.autoCost();

const txRequest = JSON.stringify(transactionRequest);
const txRequestParsed = JSON.parse(txRequest);
Expand Down
7 changes: 1 addition & 6 deletions packages/fuel-gauge/src/coverage-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,12 +665,7 @@ describe('Coverage Contract', { timeout: 15_000 }, () => {

request.addCoinOutput(recipient.address, 10, provider.getBaseAssetId());

const txCost = await sender.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await sender.fund(request, txCost);
await request.autoCost(sender);

const response = await sender.sendTransaction(request);
const result = await response.waitForResult();
Expand Down
7 changes: 1 addition & 6 deletions packages/fuel-gauge/src/fee.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,7 @@ describe('Fee', () => {
request.addCoinOutput(destination2.address, amountToTransfer, ASSET_A);
request.addCoinOutput(destination3.address, amountToTransfer, ASSET_B);

const txCost = await wallet.getTransactionCost(request);

request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;

await wallet.fund(request, txCost);
await request.autoCost(wallet);

const tx = await wallet.sendTransaction(request);
const { fee } = await tx.wait();
Expand Down
Loading

0 comments on commit 8d8452e

Please sign in to comment.