Skip to content
Draft
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
2 changes: 1 addition & 1 deletion build-on-celo/build-on-minipay/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Request CELO testnet tokens from the Celo [faucet](https://faucet.celo.org/celo-
## Helpful Tips to Make Your Mini App MiniPay Compatible

<Warning>
MiniPay uses Custom [Fee Abstraction](/tooling/overview/fee-abstraction) based
MiniPay uses Custom [Fee Abstraction](/build-on-celo/fee-abstraction/overview) based
transactions. We recommend using viem or wagmi as they provide native support
for fee currency.
</Warning>
Expand Down
2 changes: 1 addition & 1 deletion build-on-celo/build-with-ai/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,4 @@ await sendUSDCWithFeeAbstraction("0xRecipient...", parseUnits("1", 6));
| USDT | Mainnet | [`0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e`](https://celoscan.io/address/0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e) | [`0x0e2a3e05bc9a16f5292a6170456a710cb89c6f72`](https://celoscan.io/address/0x0e2a3e05bc9a16f5292a6170456a710cb89c6f72) |
| USDC | Sepolia | [`0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B`](https://sepolia.celoscan.io/address/0x2f25deb3848c207fc8e0c34035b3ba7fc157602b) | [`0x4822e58de6f5e485eF90df51C41CE01721331dC0`](https://sepolia.celoscan.io/address/0x4822e58de6f5e485eF90df51C41CE01721331dC0) |

For the full guide — including gas estimation, CIP-64 transaction types, and CLI usage — see [Implementing Fee Abstraction](/tooling/overview/fee-abstraction).
For the full guide — including gas estimation, CIP-64 transaction types, and CLI usage — see [Fee Abstraction](/build-on-celo/fee-abstraction/using-fee-abstraction).
2 changes: 1 addition & 1 deletion build-on-celo/build-with-ai/x402.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,4 @@ app.get("/articles/:id", async (req, res) => {

- [ERC-8004](/build-on-celo/build-with-ai/8004) - Trust layer for AI agents
- [Agent Skills](/build-on-celo/build-with-ai/agent-skills) - Modular agent capabilities
- [Fee Abstraction](/tooling/overview/fee-abstraction) - Pay gas with stablecoins on Celo
- [Fee Abstraction](/build-on-celo/fee-abstraction/overview) - Pay gas with stablecoins on Celo
151 changes: 151 additions & 0 deletions build-on-celo/fee-abstraction/add-fee-currency.mdx
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.
84 changes: 84 additions & 0 deletions build-on-celo/fee-abstraction/overview.mdx
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) |

---

## 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
Original file line number Diff line number Diff line change
@@ -1,77 +1,14 @@
---
title: Implementing Fee Abstraction in Wallets
og:description: How to allow your wallet users to pay for gas fees using alternate fee currencies
sidebarTitle: "Fee Abstraction"
title: Using Fee Abstraction in Transactions
sidebarTitle: "Using Fee Abstraction"
og:description: How to pay gas fees using alternate fee currencies on Celo with viem and celocli
---

Celo allows users to pay gas fees in currencies other than the native CELO token. The list of accepted tokens is governed on-chain and maintained in [**FeeCurrencyDirectory.sol**](/contracts/core-contracts).

To use an alternate fee currency, set its token or adapter address as the `feeCurrency` property on the transaction object. This field is exclusive to Celo. Leaving it empty defaults to CELO. Note that transactions specifying a non-CELO fee currency cost approximately 50,000 additional gas.
This guide shows how to send transactions that pay gas fees in ERC20 tokens instead of CELO. For background on how fee abstraction works, see the [Overview](/build-on-celo/fee-abstraction/overview).

---

## Allowlisted Fee Currencies

The protocol maintains a governable allowlist of smart contract addresses that implement an extension of the ERC20 interface, with additional functions for debiting and crediting transaction fees.

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) |

---

## Using Fee Abstraction with Celo CLI

Transfer 1 USDC using USDC as the fee currency via [`celocli`](/cli):
```bash
celocli transfer:erc20 \
--erc20Address 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B \
--from 0x22ae7Cf4cD59773f058B685a7e6B7E0984C54966 \
--to 0xDF7d8B197EB130cF68809730b0D41999A830c4d7 \
--value 1000000 \
--gasCurrency 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B \
--privateKey [PRIVATE_KEY]
```

When using USDC or USDT, use the adapter address (not the token address) to avoid inaccuracies caused by their 6-decimal precision.

---

## Using Fee Abstraction with viem
## Using viem

We recommend [viem](https://viem.sh/), which has native support for the `feeCurrency` field. Ethers.js and web3.js do not currently support this field.

Expand All @@ -85,7 +22,7 @@ The gas price returned from the RPC is always expressed in 18 decimals, regardle

Use the adapter address (for USDC/USDT) or token address (for USDm, EURm, BRLm) as the `feeCurrency` value when estimating.
```js
import { createPublicClient, hexToBigInt, http } from "viem";
import { createPublicClient, formatEther, hexToBigInt, http } from "viem";
import { celo } from "viem/chains";

const USDC_ADAPTER_MAINNET = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B";
Expand All @@ -111,11 +48,10 @@ async function getGasPriceInUSDC() {
}

async function estimateGasInUSDC(transaction) {
const estimatedGasInHex = await publicClient.estimateGas({
return await publicClient.estimateGas({
...transaction,
feeCurrency: USDC_ADAPTER_MAINNET,
});
return hexToBigInt(estimatedGasInHex);
}

async function main() {
Expand Down Expand Up @@ -143,7 +79,7 @@ let tx = {

The example below transfers 1 USDC, subtracting the estimated fee from the transfer amount so the sender's full balance is not over-spent.
```js
import { createWalletClient, http } from "viem";
import { createWalletClient, encodeFunctionData, http, parseEther } from "viem";
import { celo } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { stableTokenAbi } from "@celo/abis";
Expand Down Expand Up @@ -196,12 +132,20 @@ async function send(amountInWei) {

---

---
## Using Celo CLI

## Related
Transfer 1 USDC using USDC as the fee currency via [`celocli`](/cli):
```bash
celocli transfer:erc20 \
--erc20Address 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B \
--from 0x22ae7Cf4cD59773f058B685a7e6B7E0984C54966 \
--to 0xDF7d8B197EB130cF68809730b0D41999A830c4d7 \
--value 1000000 \
--gasCurrency 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B \
--privateKey [PRIVATE_KEY]
```

- [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
When using USDC or USDT, use the adapter address (not the token address) to avoid inaccuracies caused by their 6-decimal precision.

---

Expand Down
Loading
Loading