-
Notifications
You must be signed in to change notification settings - Fork 366
Move fee abstraction docs to "Build on Celo" section #2163
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| --- | ||
| title: Adding Fee Currencies | ||
| sidebarTitle: "Adding Fee Currencies" | ||
| og:description: How to implement and register a new ERC20 token as a fee currency on Celo | ||
| --- | ||
|
|
||
| Any ERC20 token can become a fee currency on Celo by implementing the `IFeeCurrency` interface and being added to the on-chain allowlist through governance. This guide explains the interface requirements and walks through an example implementation. | ||
|
|
||
| For background on how fee abstraction works, see the [Overview](/build-on-celo/fee-abstraction/overview). | ||
|
|
||
| --- | ||
|
|
||
| ## The IFeeCurrency Interface | ||
|
|
||
| Fee currencies must implement the [IFeeCurrency](https://github.com/celo-org/fee-currency-example/blob/main/src/IFeeCurrency.sol) interface, which extends ERC20 with two additional functions used by the Celo blockchain to debit and credit gas fees. | ||
|
|
||
| When a [CIP-64](https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0064.md) transaction is executed: | ||
|
|
||
| 1. **Before execution** — the blockchain calls `debitGasFees` to reserve the maximum gas the transaction can spend | ||
| 2. **After execution** — the blockchain calls `creditGasFees` to refund unused gas and distribute fees to the appropriate recipients | ||
|
|
||
| ### debitGasFees | ||
|
|
||
| ```solidity | ||
| function debitGasFees(address from, uint256 value) external; | ||
| ``` | ||
|
|
||
| Called before transaction execution to reserve the maximum gas amount. | ||
|
|
||
| - Must deduct `value` from `from`'s balance | ||
| - Must revert if `msg.sender` is not `address(0)` (only the VM may call this) | ||
|
|
||
| ### creditGasFees | ||
|
|
||
| There are two versions of `creditGasFees`. Both should be implemented for compatibility. | ||
|
|
||
| **New signature** (used once all fee currencies have migrated): | ||
| ```solidity | ||
| function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) external; | ||
| ``` | ||
|
|
||
| - Must credit each `recipient` the corresponding `amount` | ||
| - Must revert if `msg.sender` is not `address(0)` | ||
| - Must revert if `recipients` and `amounts` have different lengths | ||
|
|
||
| **Legacy signature** (for backwards compatibility): | ||
| ```solidity | ||
| function creditGasFees( | ||
| address refundRecipient, | ||
| address tipRecipient, | ||
| address _gatewayFeeRecipient, | ||
| address baseFeeRecipient, | ||
| uint256 refundAmount, | ||
| uint256 tipAmount, | ||
| uint256 _gatewayFeeAmount, | ||
| uint256 baseFeeAmount | ||
| ) external; | ||
| ``` | ||
|
|
||
| - `_gatewayFeeRecipient` and `_gatewayFeeAmount` are deprecated and will always be zero | ||
| - Must revert if `msg.sender` is not `address(0)` | ||
|
|
||
| --- | ||
|
|
||
| ## Example Implementation | ||
|
|
||
| The following example from [celo-org/fee-currency-example](https://github.com/celo-org/fee-currency-example) shows a minimal fee currency token using OpenZeppelin's ERC20 with burn/mint mechanics for gas fee handling: | ||
|
|
||
| ```solidity | ||
| pragma solidity ^0.8.13; | ||
|
|
||
| import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| import {IFeeCurrency} from "./IFeeCurrency.sol"; | ||
|
|
||
| contract FeeCurrency is ERC20, IFeeCurrency { | ||
| constructor(uint256 initialSupply) ERC20("ExampleFeeCurrency", "EFC") { | ||
| _mint(msg.sender, initialSupply); | ||
| } | ||
|
|
||
| modifier onlyVm() { | ||
| require(msg.sender == address(0), "Only VM can call"); | ||
| _; | ||
| } | ||
|
|
||
| function debitGasFees(address from, uint256 value) external onlyVm { | ||
| _burn(from, value); | ||
| } | ||
|
|
||
| // New function signature | ||
| function creditGasFees( | ||
| address[] calldata recipients, | ||
| uint256[] calldata amounts | ||
| ) public onlyVm { | ||
| require( | ||
| recipients.length == amounts.length, | ||
| "Recipients and amounts must be the same length." | ||
| ); | ||
|
|
||
| for (uint256 i = 0; i < recipients.length; i++) { | ||
| _mint(recipients[i], amounts[i]); | ||
| } | ||
| } | ||
|
|
||
| // Legacy function signature for backwards compatibility | ||
| function creditGasFees( | ||
| address from, | ||
| address feeRecipient, | ||
| address, // gatewayFeeRecipient, unused | ||
| address communityFund, | ||
| uint256 refund, | ||
| uint256 tipTxFee, | ||
| uint256, // gatewayFee, unused | ||
| uint256 baseTxFee | ||
| ) public onlyVm { | ||
| _mint(from, refund); | ||
| _mint(feeRecipient, tipTxFee); | ||
| _mint(communityFund, baseTxFee); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| This implementation uses `_burn` in `debitGasFees` and `_mint` in `creditGasFees` to handle the gas fee lifecycle. The `onlyVm` modifier ensures only the blockchain itself (via `address(0)`) can call these functions. | ||
|
|
||
| --- | ||
|
|
||
| ## Testing | ||
|
|
||
| Use [Foundry](https://book.getfoundry.sh/) to test your fee currency implementation. The [fee-currency-example](https://github.com/celo-org/fee-currency-example) repository includes a test suite you can use as a starting point: | ||
|
|
||
| ```bash | ||
| git clone https://github.com/celo-org/fee-currency-example.git | ||
| cd fee-currency-example | ||
| forge build | ||
| forge test | ||
| ``` | ||
|
|
||
| Key things to test: | ||
|
|
||
| - `debitGasFees` correctly reduces the sender's balance | ||
| - `debitGasFees` reverts when called by any address other than `address(0)` | ||
| - Both `creditGasFees` signatures correctly credit all recipients | ||
| - `creditGasFees` reverts when called by any address other than `address(0)` | ||
| - The total debited amount equals the total credited amount across a transaction lifecycle | ||
|
|
||
| --- | ||
|
|
||
| ## Registering a Fee Currency | ||
|
|
||
| Once your token implements `IFeeCurrency`, it must be added to the on-chain allowlist in [**FeeCurrencyDirectory.sol**](/tooling/contracts/core-contracts) through a governance proposal. The governance process ensures that fee currencies meet the necessary requirements for network stability. | ||
|
|
||
| If your token uses decimals other than 18, you will also need to deploy an adapter contract. See [Adapters for Non-18-Decimal Tokens](/build-on-celo/fee-abstraction/overview#adapters-for-non-18-decimal-tokens) for details. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| --- | ||
| title: Fee Abstraction | ||
| sidebarTitle: "Overview" | ||
| og:description: Pay gas fees using ERC20 tokens instead of the native CELO token | ||
| --- | ||
|
|
||
| Fee abstraction is one of Celo's core protocol features. It allows users to pay gas fees in ERC20 tokens — like USDC, USDT, or Mento stablecoins — instead of needing to hold the native CELO token. | ||
|
|
||
| ## Why Fee Abstraction Matters | ||
|
|
||
| On most EVM chains, users must hold the native token to pay for gas. This creates friction: a user who receives USDC on Celo can't send it anywhere without first acquiring CELO. Fee abstraction removes this barrier entirely. | ||
|
|
||
| With fee abstraction, a user holding only USDC can send transactions, interact with contracts, and pay gas — all in USDC. No bridging, no swaps, no extra steps. This is especially valuable for: | ||
|
|
||
| - **Onboarding new users** who receive stablecoins but don't know about gas tokens | ||
| - **Payment applications** where users transact in a single currency end-to-end | ||
| - **AI agents** that operate autonomously with a single token balance | ||
|
|
||
| ## How It Works | ||
|
|
||
| Fee abstraction is built into the Celo protocol at the node level — it is not a paymaster or relayer. When a transaction includes a `feeCurrency` field, the Celo blockchain: | ||
|
|
||
| 1. Calls `debitGasFees` on the fee currency contract to reserve the maximum gas cost | ||
| 2. Executes the transaction normally | ||
| 3. Calls `creditGasFees` to refund unused gas and distribute fees to block producers | ||
|
|
||
| This means fee abstraction works with any externally owned account (EOA). No smart contract wallets, no relayers, no extra infrastructure needed. | ||
|
|
||
| To use an alternate fee currency, set its token or adapter address as the `feeCurrency` property on the transaction object. | ||
|
|
||
| For implementation details, see [Using Fee Abstraction](/build-on-celo/fee-abstraction/using-fee-abstraction). To add a new fee currency to the protocol, see [Adding Fee Currencies](/build-on-celo/fee-abstraction/add-fee-currency). | ||
|
|
||
| --- | ||
|
|
||
| ## Allowlisted Fee Currencies | ||
|
|
||
| The protocol maintains a governable allowlist of smart contract addresses that can be used as fee currencies. These contracts implement an extension of the ERC20 interface with additional functions for debiting and crediting transaction fees (see [Adding Fee Currencies](/build-on-celo/fee-abstraction/add-fee-currency)). | ||
|
|
||
| To fetch the current allowlist, call `getCurrencies()` on the `FeeCurrencyDirectory` contract, or use `celocli`: | ||
| ```bash | ||
| # Celo Sepolia testnet | ||
| celocli network:whitelist --node celo-sepolia | ||
|
|
||
| # Celo mainnet | ||
| celocli network:whitelist --node https://forno.celo.org | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Adapters for Non-18-Decimal Tokens | ||
|
|
||
| After Contract Release 11, allowlisted addresses may be **adapters** rather than full ERC20 tokens. Adapters are used when a token has decimals other than 18 (e.g., USDC and USDT use 6 decimals). The Celo blockchain calculates gas pricing in 18 decimals, so adapters normalize the value. | ||
|
|
||
| - **For transfers**: use the token address as usual. | ||
| - **For `feeCurrency`**: use the adapter address. | ||
| - **For `balanceOf`**: querying via the adapter returns the balance as if the token had 18 decimals — useful for checking whether an account can cover gas without converting units. | ||
|
|
||
| To get the underlying token address for an adapter, call `adaptedToken()` on the adapter contract. | ||
|
|
||
| For more on gas pricing, see [Gas Pricing](/legacy/protocol/transaction/gas-pricing). | ||
|
|
||
| ### Adapter Addresses | ||
|
|
||
| #### Mainnet | ||
|
|
||
| | Name | Token Address | Adapter Address | | ||
| | ------ | ------------- | --------------- | | ||
| | `USDC` | [`0xcebA9300f2b948710d2653dD7B07f33A8B32118C`](https://celoscan.io/address/0xcebA9300f2b948710d2653dD7B07f33A8B32118C#code) | [`0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B`](https://celoscan.io/address/0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B#code) | | ||
| | `USDT` | [`0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e`](https://celoscan.io/address/0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e#code) | [`0x0e2a3e05bc9a16f5292a6170456a710cb89c6f72`](https://celoscan.io/address/0x0e2a3e05bc9a16f5292a6170456a710cb89c6f72#code) | | ||
|
|
||
| #### Celo Sepolia (Testnet) | ||
|
|
||
| | Name | Token Address | Adapter Address | | ||
| | ------ | ------------- | --------------- | | ||
| | `USDC` | [`0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B`](https://sepolia.celoscan.io/address/0x2f25deb3848c207fc8e0c34035b3ba7fc157602b#code) | [`0x4822e58de6f5e485eF90df51C41CE01721331dC0`](https://sepolia.celoscan.io/address/0x4822e58de6f5e485eF90df51C41CE01721331dC0#code) | | ||
|
|
||
| --- | ||
|
|
||
|
Comment on lines
+35
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these 2 sections (Allowlisted fee currencies & Adapters) would make more sense in the 'Using fee abstraction' page, since that is where you need to know these things. An average user has no use for this information, since fee currencies need to be explicitly supported in any app that they may be using. |
||
| ## Related | ||
|
|
||
| - [Fee Abstraction for AI Agents](/build-on-celo/build-with-ai/overview#fee-abstraction-for-agents) — Using fee abstraction in autonomous agent backends | ||
| - [x402: Agent Payments](/build-on-celo/build-with-ai/x402) — HTTP-native stablecoin payments for agents | ||
| - [Using Fee Abstraction](/build-on-celo/fee-abstraction/using-fee-abstraction) — How to pay gas with alternate fee currencies in your transactions | ||
| - [Adding Fee Currencies](/build-on-celo/fee-abstraction/add-fee-currency) — How to implement and register a new fee currency | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be updated to maintain consistency with the above