diff --git a/.changeset/kind-glasses-brake.md b/.changeset/kind-glasses-brake.md new file mode 100644 index 00000000000..72349c14eda --- /dev/null +++ b/.changeset/kind-glasses-brake.md @@ -0,0 +1,6 @@ +--- +"@fuel-ts/predicate": patch +"@fuel-ts/wallet": patch +--- + +New helper method `Predicate.getTransferTxId`, which lets you calculate the transaction ID for a Predicate.transfer transaction, before actually sending it. diff --git a/apps/docs-snippets/src/guide/predicates/send-and-spend-funds-from-predicates.test.ts b/apps/docs-snippets/src/guide/predicates/send-and-spend-funds-from-predicates.test.ts index 0f7ce1ae338..6fcddf6adde 100644 --- a/apps/docs-snippets/src/guide/predicates/send-and-spend-funds-from-predicates.test.ts +++ b/apps/docs-snippets/src/guide/predicates/send-and-spend-funds-from-predicates.test.ts @@ -9,7 +9,10 @@ import { BaseAssetId, } from 'fuels'; -import { DocSnippetProjectsEnum, getDocsSnippetsForcProject } from '../../../test/fixtures/forc-projects'; +import { + DocSnippetProjectsEnum, + getDocsSnippetsForcProject, +} from '../../../test/fixtures/forc-projects'; import { getTestWallet } from '../../utils'; describe(__filename, () => { @@ -50,11 +53,22 @@ describe(__filename, () => { predicate.setData(inputAddress); // #endregion send-and-spend-funds-from-predicates-4 - // #region send-and-spend-funds-from-predicates-5 const receiverWallet = WalletUnlocked.generate({ provider, }); + // #region send-and-spend-funds-from-predicates-8 + const txId = await predicate.getTransferTxId( + receiverWallet.address, + amountToPredicate - 150_000, + BaseAssetId, + { + gasPrice, + } + ); + // #endregion send-and-spend-funds-from-predicates-8 + + // #region send-and-spend-funds-from-predicates-5 const tx2 = await predicate.transfer( receiverWallet.address, amountToPredicate - 150_000, @@ -66,6 +80,9 @@ describe(__filename, () => { await tx2.waitForResult(); // #endregion send-and-spend-funds-from-predicates-5 + const txIdFromExecutedTx = tx2.id; + + expect(txId).toEqual(txIdFromExecutedTx); }); it('should fail when trying to spend predicates entire amount', async () => { diff --git a/apps/docs/src/guide/predicates/send-and-spend-funds-from-predicates.md b/apps/docs/src/guide/predicates/send-and-spend-funds-from-predicates.md index 337b0d79d8c..68c8695edd3 100644 --- a/apps/docs/src/guide/predicates/send-and-spend-funds-from-predicates.md +++ b/apps/docs/src/guide/predicates/send-and-spend-funds-from-predicates.md @@ -38,6 +38,12 @@ Note the method transfer has two parameters: the recipient's address and the int Once the predicate resolves with a return value `true` based on its predefined condition, our predicate successfully spends its funds by means of a transfer to a desired wallet. +--- + +You can also use the `getTransferTxId` helper to obtain the transaction ID of the transfer beforehand, without actually executing it. + +<<< @/../../docs-snippets/src/guide/predicates/send-and-spend-funds-from-predicates.test.ts#send-and-spend-funds-from-predicates-8{ts:line-numbers} + ## Spending Entire Predicate Held Amount Trying to forward the entire amount held by the predicate results in an error because no funds are left to cover the transaction fees. Attempting this will result in an error message like: diff --git a/packages/predicate/package.json b/packages/predicate/package.json index 366e44c2f82..e42e159770b 100644 --- a/packages/predicate/package.json +++ b/packages/predicate/package.json @@ -28,6 +28,7 @@ "@fuel-ts/abi-coder": "workspace:*", "@fuel-ts/address": "workspace:*", "@fuel-ts/interfaces": "workspace:*", + "@fuel-ts/math": "workspace:*", "@fuel-ts/merkle": "workspace:*", "@fuel-ts/providers": "workspace:*", "@fuel-ts/transactions": "workspace:*", diff --git a/packages/predicate/src/predicate.ts b/packages/predicate/src/predicate.ts index 7449097e079..673337bc402 100644 --- a/packages/predicate/src/predicate.ts +++ b/packages/predicate/src/predicate.ts @@ -7,8 +7,11 @@ import { calculateVmTxMemory, } from '@fuel-ts/abi-coder'; import { Address } from '@fuel-ts/address'; +import { BaseAssetId } from '@fuel-ts/address/configs'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; -import type { AbstractPredicate } from '@fuel-ts/interfaces'; +import { hashTransaction } from '@fuel-ts/hasher'; +import type { AbstractAddress, AbstractPredicate } from '@fuel-ts/interfaces'; +import type { BigNumberish } from '@fuel-ts/math'; import type { CallResult, Provider, @@ -17,6 +20,7 @@ import type { } from '@fuel-ts/providers'; import { transactionRequestify } from '@fuel-ts/providers'; import { ByteArrayCoder, InputType } from '@fuel-ts/transactions'; +import type { TxParamsType } from '@fuel-ts/wallet'; import { Account } from '@fuel-ts/wallet'; import type { BytesLike } from 'ethers'; import { getBytesCopy, hexlify } from 'ethers'; @@ -92,6 +96,30 @@ export class Predicate extends Account implements Abs return super.sendTransaction(transactionRequest); } + /** + * Returns the transaction ID for a transfer transaction, without sending it. + * + * @param destination - The address of the destination. + * @param amount - The amount of coins to transfer. + * @param assetId - The asset ID of the coins to transfer. + * @param txParams - The transaction parameters (gasLimit, gasPrice, maturity). + * @returns A promise that resolves to the transaction ID. + */ + async getTransferTxId( + /** Address of the destination */ + destination: AbstractAddress, + /** Amount of coins */ + amount: BigNumberish, + /** Asset ID of coins */ + assetId: BytesLike = BaseAssetId, + /** Tx Params */ + txParams: TxParamsType = {} + ): Promise { + const request = await super.prepareTransferTxRequest(destination, amount, assetId, txParams); + const populatedRequest = this.populateTransactionPredicateData(request); + return hashTransaction(populatedRequest, this.provider.getChainId()); + } + /** * Simulates a transaction with the populated predicate data. * diff --git a/packages/wallet/src/account.ts b/packages/wallet/src/account.ts index 12695799a6b..18bbedbc512 100644 --- a/packages/wallet/src/account.ts +++ b/packages/wallet/src/account.ts @@ -32,7 +32,7 @@ import { formatScriptDataForTransferringToContract, } from './utils'; -type TxParamsType = Pick; +export type TxParamsType = Pick; /** * `Account` provides an abstraction for interacting with accounts or wallets on the network. @@ -239,16 +239,36 @@ export class Account extends AbstractAccount { /** Tx Params */ txParams: TxParamsType = {} ): Promise { + const request = await this.prepareTransferTxRequest(destination, amount, assetId, txParams); + return this.sendTransaction(request); + } + + /** + * A helper that prepares a transaction request for calculating the transaction ID. + * + * @param destination - The address of the destination. + * @param amount - The amount of coins to transfer. + * @param assetId - The asset ID of the coins to transfer. + * @param txParams - The transaction parameters (gasLimit, gasPrice, maturity). + * @returns A promise that resolves to the prepared transaction request. + */ + protected async prepareTransferTxRequest( + /** Address of the destination */ + destination: AbstractAddress, + /** Amount of coins */ + amount: BigNumberish, + /** Asset ID of coins */ + assetId: BytesLike = BaseAssetId, + /** Tx Params */ + txParams: TxParamsType = {} + ): Promise { const { maxGasPerTx } = this.provider.getGasConfig(); const params: TxParamsType = { gasLimit: maxGasPerTx, ...txParams }; const request = new ScriptTransactionRequest(params); request.addCoinOutput(destination, amount, assetId); - const { maxFee, requiredQuantities } = await this.provider.getTransactionCost(request); - await this.fund(request, requiredQuantities, maxFee); - - return this.sendTransaction(request); + return request; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3e238b9cd2..ebcbec159e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -867,6 +867,9 @@ importers: '@fuel-ts/interfaces': specifier: workspace:* version: link:../interfaces + '@fuel-ts/math': + specifier: workspace:* + version: link:../math '@fuel-ts/merkle': specifier: workspace:* version: link:../merkle @@ -888,10 +891,6 @@ importers: ethers: specifier: ^6.7.1 version: 6.7.1 - devDependencies: - '@fuel-ts/math': - specifier: workspace:* - version: link:../math packages/program: dependencies: