How to Pay Fees
There a several ways to pay for transaction fees, thanks to fee abstraction implemented in the Aztec protocol.
This guide shows the various ways this can be done. For more of a background see Fees on Aztec.
Methods to pay a tx fee
Natively via an existing balance of fee juice
Either your own account or someone else's address has a balance of fee juice, and the transaction is paid for from this balance.
Natively via a balance of fee juice to be claimed
Processing this transaction first claims bridged fee juice then is paid for from the balance, all in the same public/private tx.
Privately or publicly via a Fee Paying Contract
A fee paying contract (FPC) is created and nominates a token that it accepts to then pay for txs in fee juice. So a user doesn't need to hold fee juice, they only need the token that the FPC accepts.
Fee Options
Functions pertaining to sending a transaction, such as deploy
and send
, each include a fee
variable defined with the following (optional) parameters:
/** Fee options as set by a user. */
export type UserFeeOptions = {
/** The fee payment method to use */
paymentMethod?: FeePaymentMethod;
/** The gas settings */
gasSettings?: Partial<FieldsOf<GasSettings>>;
/** Percentage to pad the base fee by, if empty, defaults to 0.5 */
baseFeePadding?: number;
/** Whether to run an initial simulation of the tx with high gas limit to figure out actual gas settings. */
estimateGas?: boolean;
/** Percentage to pad the estimated gas limits by, if empty, defaults to 0.1. Only relevant if estimateGas is set. */
estimatedGasPadding?: number;
};
Source code: yarn-project/aztec.js/src/entrypoint/payload.ts#L24-L38
Fee Payment Method
The paymentMethod
is an object for the type of payment. Each of the implementations can be found here. For example:
/**
* Pay fee directly in the Fee Juice.
*/
export class FeeJuicePaymentMethod implements FeePaymentMethod {
Source code: yarn-project/aztec.js/src/fee/fee_juice_payment_method.ts#L7-L12
Gas Settings
/** Gas usage and fees limits set by the transaction sender for different dimensions and phases. */
export class GasSettings {
constructor(
public readonly gasLimits: Gas,
public readonly teardownGasLimits: Gas,
public readonly maxFeesPerGas: GasFees,
public readonly maxPriorityFeesPerGas: GasFees,
) {}
Source code: yarn-project/stdlib/src/gas/gas_settings.ts#L11-L20
Gas Settings
used in transactions specify gas limits and maximum fee rates (fees-per-gas)
The Gas
and GasFees
types each specify Data availability and L2 cost components, so the settings are:
- gasLimits: DA and L2 gas limits
- teardownGasLimits: DA and L2 gas limits for a txs optional teardown operation
- maxFeesPerGas: maximum DA and L2 fees-per-gas
- maxPriorityFeesPerGas: maximum priority DA and L2 fees-per-gas
Other params
Fee and gas padding params can be left to their default values, and the estimateGas boolean can be used when simulating a tx to calculate gas.
With the fee options explained, lets do a paid transaction.
Examples
Pay with FeeJuice
An account can be deployed directly via fee-juice payment if the address has been pre-funded. This is done using the AccountManager as follows:
const paymentMethod = new FeeJuicePaymentMethod(bobsAddress);
const tx = await bobsAccountManager.deploy({ fee: { paymentMethod } }).wait();
Source code: yarn-project/end-to-end/src/e2e_fees/account_init.test.ts#L91-L94
Or to send a transaction from an account holding fee juice: (Note: this example is a public token transfer call, but can equally be a private function call)
const paymentMethod = new FeeJuicePaymentMethod(aliceAddress);
const { transactionFee } = await bananaCoin.methods
.transfer_in_public(aliceAddress, bobAddress, 1n, 0n)
.send({ fee: { gasSettings, paymentMethod } })
.wait();
Source code: yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts#L89-L95
The equivalent to specify fees via CLI...
The CLI tool aztec-wallet
takes the fee payment method via the param: --payment method=fee_juice
. See help for sending txs, eg aztec-wallet help deploy
See here to Bridge Fee Juice via the CLI wallet.
Claim and pay
After a user has sent fee juice from L1 to be bridged to L2, a transaction can be made that claims this to pay for itself, and make the transaction, in one.
The claim object:
/** L1 to L2 message info to claim it on L2. */
export type L2Claim = {
/** Secret for claiming. */
claimSecret: Fr;
/** Hash of the secret for claiming. */
claimSecretHash: Fr;
/** Hash of the message. */
messageHash: Hex;
/** Leaf index in the L1 to L2 message tree. */
messageLeafIndex: bigint;
};
/** L1 to L2 message info that corresponds to an amount to claim. */
export type L2AmountClaim = L2Claim & { /** Amount to claim */ claimAmount: bigint };
Source code: yarn-project/aztec.js/src/ethereum/portal_manager.ts#L19-L35
Calling a function on an object (in this case checking the balance of the fee juice contract)
const paymentMethod = new FeeJuicePaymentMethodWithClaim(bobWallet, claim);
const receipt = await feeJuiceContract
.withWallet(bobWallet)
.methods.check_balance(0n)
.send({ fee: { gasSettings, paymentMethod } })
.wait();
Source code: yarn-project/end-to-end/src/e2e_fees/fee_juice_payments.test.ts#L70-L77
Fee Paying Contract
Similarly with a fee paying contract, the fee payment method is created and used as follows:
const tx = await bananaCoin.methods
.transfer_in_public(aliceAddress, bobAddress, bananasToSendToBob, 0)
.send({
fee: {
gasSettings,
paymentMethod: new PublicFeePaymentMethod(bananaFPC.address, aliceWallet),
},
})
.wait();
Source code: yarn-project/end-to-end/src/e2e_fees/public_payments.test.ts#L62-L72
In this example, thanks to the FPC's accepted_asset
being banana tokens, Alice only needs to hold this token and not fee juice. The function being called happens to also be a transfer of banana tokens to Bob.
More on FPCs here