Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Predicate.getTransferTxId helper #1467

Merged
merged 5 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/kind-glasses-brake.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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, () => {
Expand Down Expand Up @@ -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,
Expand All @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions packages/predicate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
30 changes: 29 additions & 1 deletion packages/predicate/src/predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand Down Expand Up @@ -92,6 +96,30 @@ export class Predicate<ARGS extends InputValue[]> 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<string> {
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.
*
Expand Down
30 changes: 25 additions & 5 deletions packages/wallet/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
formatScriptDataForTransferringToContract,
} from './utils';

type TxParamsType = Pick<TransactionRequestLike, 'gasLimit' | 'gasPrice' | 'maturity'>;
export type TxParamsType = Pick<TransactionRequestLike, 'gasLimit' | 'gasPrice' | 'maturity'>;

/**
* `Account` provides an abstraction for interacting with accounts or wallets on the network.
Expand Down Expand Up @@ -239,16 +239,36 @@ export class Account extends AbstractAccount {
/** Tx Params */
txParams: TxParamsType = {}
): Promise<TransactionResponse> {
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<TransactionRequest> {
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;
}

Dhaiwat10 marked this conversation as resolved.
Show resolved Hide resolved
/**
Expand Down
7 changes: 3 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading