The assembleTx method is a crucial part of the Fuel TypeScript SDK that helps prepare and assemble transactions with the correct inputs, outputs, and policies. It is used by all higher-level APIs in the SDK, including account transfers, contract and blob deployments, and contract calls. This guide provides a comprehensive overview of how to use assembleTx effectively.
The assembleTx method takes a transaction request and assembles it with the necessary inputs, outputs, and policies based on the provided parameters. It handles:
The AssembleTxParams interface includes the following parameters:
export type AssembleTxParams<T extends TransactionRequest = TransactionRequest> = {
// The transaction request to assemble
request: T;
// Coin quantities required for the transaction, optional if transaction only needs funds for the fee
accountCoinQuantities?: AccountCoinQuantity[];
// Account that will pay for the transaction fees
feePayerAccount: Account;
// Block horizon for gas price estimation (default: 10)
blockHorizon?: number;
// Whether to estimate predicates (default: true)
estimatePredicates?: boolean;
// Resources to be ignored when funding the transaction (optional)
resourcesIdsToIgnore?: ResourcesIdsToIgnore;
// Amount of gas to reserve (optional)
reserveGas?: BigNumberish;
};
export type AssembleTxResponse<T extends TransactionRequest = TransactionRequest> = {
assembledRequest: T;
gasPrice: BN;
receipts: TransactionResultReceipt[];
rawReceipts: TransactionReceiptJson[];
};request: The transaction request to be assembled. blockHorizon: The number of blocks to look ahead for gas price estimation. Defaults to 10 blocks. feePayerAccount: The account that will pay for the transaction fees accountCoinQuantities: An array of coin quantities needed for the transaction. This is optional if the transaction only requires funds to cover the fee. The parameters are:
amount: The amount of coins needed (fee value does need to be included) assetId: The asset ID of the coins account: The account providing the coins (optional, defaults to feePayerAccount) changeOutputAccount: The account to receive change (optional, defaults to the account or feePayerAccount properties, respectively) resourcesIdsToIgnore: Resources to be ignored when funding the transaction (UTXOs or messages) estimatePredicates: Whether to estimate gas for predicates reserveGas: Additional Amount of gas to be set for the transaction The accountCoinQuantities entries have two optional properties with specific default behaviors:
account property:
feePayerAccount changeOutputAccount property:
account property assetId Example of default behaviors:
import { Provider, Wallet, type AccountCoinQuantity } from 'fuels';
import { TestAssetId } from 'fuels/test-utils';
import {
LOCAL_NETWORK_URL,
WALLET_PVT_KEY,
WALLET_PVT_KEY_2,
WALLET_PVT_KEY_3,
} from '../../../../env';
const provider = new Provider(LOCAL_NETWORK_URL);
const accountA = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);
const accountB = Wallet.fromPrivateKey(WALLET_PVT_KEY_2, provider);
const accountC = Wallet.fromPrivateKey(WALLET_PVT_KEY_3, provider);
const baseAssetId = await provider.getBaseAssetId();
const accountCoinQuantities: AccountCoinQuantity[] = [
{
amount: 100,
assetId: baseAssetId,
// account defaults to feePayerAccount
// changeOutputAccount defaults to feePayerAccount
},
{
amount: 200,
assetId: TestAssetId.A.value,
account: accountA,
// changeOutputAccount defaults to accountA
},
{
amount: 300,
assetId: TestAssetId.B.value,
account: accountB,
changeOutputAccount: accountC,
// Both account and changeOutputAccount are explicitly set
},
];The method returns an object of the type AssembleTxResponse :
assembledRequest: The fully assembled transaction request with all necessary inputs, outputs, and policies gasPrice: The estimated gas price for the transaction receipts: Parsed receipts returned from the transaction dry run. rawReceipts: Unparsed receipts returned from the transaction dry run. const request = new ScriptTransactionRequest();
request.addCoinOutput(accountB.address, transferAmount, baseAssetId);
const accountCoinQuantities: AccountCoinQuantity[] = [
{
amount: transferAmount,
assetId: baseAssetId, // Asset ID
account: accountA,
changeOutputAccount: accountA, // Optional
},
];
// Assemble the transaction
const { assembledRequest, gasPrice, receipts } = await provider.assembleTx({
request,
accountCoinQuantities,
feePayerAccount: accountA,
blockHorizon: 10,
estimatePredicates: true,
});
// The assembledRequest is now ready to be signed and sent
const submit = await accountA.sendTransaction(assembledRequest);
await submit.waitForResult();account and changeOutputAccount Fields As mentioned earlier, the account and changeOutputAccount fields are optional in the accountCoinQuantities entries. However, special attention must be paid to changeOutputAccount due to how change from spent resources works on Fuel.
In Fuel, only one OutputChange is allowed per assetId in a transaction. But what exactly is an OutputChange? In simple terms, it designates the recipient of the leftover funds ("change") after all UTXO resources for a specific assetId have been spent.
Because Fuel uses a UTXO-based model (unlike Ethereum’s account-based model), transactions will spend all included UTXOs, even if only a small portion is actually required. For example, suppose you have a single UTXO worth 10 ETH. If you create a transaction to send just 1 Gwei, the entire 10 ETH UTXO will be consumed. The transaction will then:
OutputChange. In this context, the OutputChange ensures that you receive the change from your transaction.
assetId A key rule in Fuel is that only one OutputChange per assetId is allowed per transaction. This becomes particularly important when a transaction includes resources from multiple accounts that hold the same assetId (e.g., ETH). In such a case, only one account can receive the change, whichever one is defined on the OutputChange.
If not managed correctly, this can lead to unintended behavior, such as one account spending its funds while another receives the leftover balance.
assembleTx The changeOutputAccount field in assembleTx explicitly specifies which account should receive the change for a particular assetId. This is critical when you're using resources from multiple accounts in the same transaction.
Here's an example:
let { assembledRequest } = await provider.assembleTx({
request,
feePayerAccount: accountA,
accountCoinQuantities: [
{
amount: transferAmount,
assetId: baseAssetId,
account: accountB,
/**
* accountB will receive the change. Although it is explicitly set here,
* if it were not set, it would default to the account property,
* which in this case is also accountB.
*/
changeOutputAccount: accountB,
},
],
});In the example above:
accountB are being explicitly requested in accountCoinQuantities. accountA is listed as the feePayerAccount, meaning it will also contribute resources to cover fees. changeOutputAccount is explicitly set to accountB. As a result, accountB will receive the change, even if UTXOs from accountA are spent in the transaction.
This means that if accountA contributes a 10 ETH UTXO, the transaction will spend the full 10 ETH. The leftover amount (change) will be sent to accountB, not back to accountA, because changeOutputAccountis set to accountB.
feePayerAccount that has sufficient funds for the transaction fees