Fee Abstraction
The Fee Abstraction module allows the payment of transaction fees in other arbitrary tokens different from the native token of the chain. This allows users to pay for fees in a more flexible way, using stablecoins, for example.
The functionality of the module allows:
Pricing of transaction fees in arbitrary tokens.
Payment through native token (unwrapped) or through ERC20 tokens (wrapped).
Pricing of tokens is defined by the oracle module, which provides the exchange rates for the tokens.
This allows the use of stablecoins as fee tokens.
Applies safety mechanisms to prevent price manipulation:
Uses TWAP (Time-Weighted Average Price) instead of spot prices.
Applies a clamp factor to limit extreme price deviations.
Disables tokens with missing or zero prices.
Core functionality
In its code, the module changes the fee-paying ante handlers to allow payment of fees with tokens different from the native gas token.
This is done through both EVM and cosmos SDK ante handlers.
The pricing of tokens is done with the following logic:
The price of the token is defined by the oracle module in USD
The module then stores the price of the token per gas token
This allows for easy conversion when paying fees
And allows for easy querying
Price calculation
The price calculation happens at the beginning of each block:
We query the oracle module for the Twap (Time Weighted Average Price) of all available tokens in USD
Then we iterate over all the possible fee tokens and calculate the price of the token in gas tokens
The formula is:
price_in_gas_token = price_in_usd / gas_price_in_usd
During this calculation, we don't consider decimals, and all prices are in the same unit
The final pricing is stored in the module state
We have a few safety mechanisms to ensure the prices are valid:
If the oracle module can't provide a price, the fee token is disabled
If prices go to zero, the fee token is disabled
The Twap of the token is used to avoid sudden price changes
Price changes are clamped to avoid extreme values
Fee payment
The module by itself does not handle the fee payment:
It returns available assets to each ante handler to process the deduct fee
The following is the process to check for possible fee tokens:
Ante handler receives and calculates the fee for the native gas token
The fee goes to the fee abstraction module and is validated
The module ignores zero tokens, tokens different from the native fee token, and fees with multiple tokens
Due to the chain EVM nature, fees with multiple tokens are not supported
The user's balance is checked for the fee token
If the user has enough balance, the fee is returned as it is
If the user does not have enough balance, the module checks for available fee tokens
The available fee tokens are those that have a valid price and are not disabled
The module then calculates the fee in the available fee tokens
The fee is calculated using the price stored in the module state
If not available through the unwrapped native token, the module checks for wrapped ERC20 tokens
If the user has enough balance in the wrapped token, the token is unwrapped
This makes the token available for the fee payment
The fee is returned from the module to the ante handler
The ante handler then deducts the fee from the user's balance
State
The most important state types used by the Fee Abstraction module are:
Params
Params stores the module wide configuration. It is defined as:
// Params defines the parameters for the fee abstraction module
message Params {
// Native denom
string native_denom = 1;
// Oracle module identifier
string native_oracle_denom = 2;
// Enabled indicates if the fee abstraction module is enabled
bool enabled = 3;
// ClampFactor is the factor to clamp the price deviation
string clamp_factor = 4 [
(gogoproto.moretags) = "yaml:\"clamp_factor\"",
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false
];
// TwapLookbackWindow is the lookback window for calculating TWAPs
uint64 twap_lookback_window = 5;
// FallbackNativePrice is the fallback price for the native token if the
// oracle price is not available (in USD)
string fallback_native_price = 6 [
(gogoproto.moretags) = "yaml:\"fallback_native_price\"",
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false
];
}
FeeToken
FeeToken stores the information about a fee token. It is defined as:
// FeeTokenMetadata defines the metadata for a fee token
message FeeTokenMetadata {
// Denom is the token denom
string denom = 1;
// Identifier on the oracle module
string oracle_denom = 2;
// Decimals is the number of decimals for the token
uint32 decimals = 3;
// Price is the price of the token in the native denom
// This price is paired against the native denom
// So, this equals to the token/native denom
string price = 4 [
(gogoproto.moretags) = "yaml:\"price\"",
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false
];
// Enabled indicates if the token is enabled for fee abstraction
bool enabled = 6;
}
// Defines a collection of fee token metadata
message FeeTokenMetadataCollection {
// Items is a repeated field of FeeTokenMetadata
repeated FeeTokenMetadata items = 1 [ (gogoproto.nullable) = false ];
}
Messages
The module defines the following messages:
MsgUpdateParams
MsgUpdateParams is used to update the module parameters. Only the governance account can update the parameters, and it requires a valid signature. It is defined as:
// MsgUpdateParams is the Msg/UpdateParams request type.
message MsgUpdateParams {
option (cosmos.msg.v1.signer) = "authority";
option (amino.name) = "feeabstraction/update-params";
// authority is the address of the governance account.
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// params defines the x/feeabstraction parameters to update.
Params params = 2 [ (gogoproto.nullable) = false ];
}
MsgUpdateFeeTokens
The MsgUpdateFeeTokens
message is used to update the fee tokens metadata. Only the governance account can update the fee tokens, and it requires a valid signature. On each update, all fee tokens must be provided, even if they are not changed:
Ordering is important, since the balance will be calculated based on the order of the fee tokens.
It is defined as:
// MsgUpdateFeeTokens is the Msg/UpdateFeeTokens request type.
message MsgUpdateFeeTokens {
option (cosmos.msg.v1.signer) = "authority";
option (amino.name) = "feeabstraction/update-fee-tokens";
// authority is the address of the governance account.
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// fee_tokens defines the fee tokens to update.
FeeTokenMetadataCollection fee_tokens = 2 [ (gogoproto.nullable) = false ];
}
Queries
The module provides the following queries:
QueryParams
The QueryParams
query is used to retrieve the current module parameters. It does not require any input and returns the current parameters.
// QueryParamsResponse is the response type for the Query/Params RPC method.
message QueryParamsResponse {
// params defines the parameters of the module.
Params params = 1 [ (gogoproto.nullable) = false ];
}
QueryFeeTokens
The QueryFeeTokens
query is used to retrieve the current fee tokens metadata. It does not require any input and returns the current fee tokens.
// QueryFeeTokensResponse is the response type for the Query/FeeTokens RPC
// method
message QueryFeeTokensResponse {
// fee_tokens defines the fee tokens registered in the module.
FeeTokenMetadataCollection fee_tokens = 1;
}
Begin block
On each ABCI call, the Fee Abstraction module performs the following actions:
Check the current Twap for each fee token against the oracle module.
Update the price of each fee token based on the Twap.
Clamp the price of each fee token to avoid extreme values.
Disable fee tokens that have no valid price or have a price of zero.
Update the module state with the new prices and enabled status of the fee tokens.
If the module is disabled, it will not perform any of the above actions and will not allow fee abstraction.
Ante Handlers
The Fee Abstraction module provides custom ante handlers to handle fee payments in the specified fee tokens.
fee.go (Cosmos Ante Handler)
This is the Cosmos SDK ante handler:
Has the same implementation as the original fee ante handler.
The main difference is that the fees goes though the Fee Abstraction module before fee deduction.
mono_decorator.go (EVM Ante Handler)
This is the EVM ante handler:
Since the EVM module has a coupled implementation of the ante handler, the whole ante had to be migrated
The implementation is mostly the same as the original EVM ante handler
The following was required to be changed:
Balance checks were broken down into different steps:
Check if the user has enough balance to pay for fees
Check if the user has enough balance to pay for TX value
Account creation was moved up to allow accounts to exist before the fee deduction
At the end of the ante handler, the fee is registered on the context
This allows fee refunds to be processed correctly
Limitation
A limitation happens with fresh wallets (wallets that have never executed a transaction):
Scenario 1: Fresh wallet with ERC20 (wrapped) fee tokens
If the first transaction attempted is a Cosmos SDK transaction using only ERC20 fee tokens:
The transaction will fail with an “account doesn’t exist” error.
This is because the Cosmos SDK requires an on-chain account to exist prior to executing any Cosmos transaction, and ERC20 balances alone do not create the account record.
Scenario 2: Fresh wallet with native (unwrapped) fee tokens
If the first transaction attempted is a Cosmos SDK transaction using native fee tokens:
The transaction will succeed, as the presence of the native asset allows account creation during the transaction flow, and the fee abstraction module can process the fees normally.
Scenario 3: Fresh wallet with either ERC20 or native fee tokens, performing EVM transactions
If the first transaction attempted is an EVM transaction:
The transaction will succeed, regardless of whether the wallet holds ERC20 or native tokens, as the EVM module internally handles account creation differently.
Implication
Users must perform one of the following actions before attempting a Cosmos SDK transaction using ERC20 fee tokens:
Fund the wallet with any amount of the native (unwrapped) token and execute a small Cosmos transaction to create the account.
Or perform an EVM transaction first which also creates the account record.
Last updated