> ## Documentation Index
> Fetch the complete documentation index at: https://docs.celo.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Using Fee Abstraction in Transactions

> How to pay gas fees using alternate fee currencies on Celo with viem and celocli

This page is the how-to for app and agent developers: send transactions that pay gas fees in ERC20 tokens instead of CELO. For background and wallet support, see the [overview](/build/fee-abstraction/overview). For token and adapter addresses, see [Fee currency contracts](/build/tools/contracts/fee-currencies). For the protocol rules, see the [fee abstraction specification](/operate/specification/fee-abstraction).

***

## 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/fee-abstraction/add-fee-currency)).

To fetch the current allowlist, call `getCurrencies()` on the `FeeCurrencyDirectory` contract, or use `celocli`:

```bash theme={null}
# Celo Sepolia testnet
celocli network:whitelist --node celo-sepolia

# Celo mainnet
celocli network:whitelist --node celo
```

***

## Adapters for Non-18-Decimal Tokens

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. Newer adapters — including the USD₮ and USA₮ ones — expose this as `getAdaptedToken()` instead, so try both if the first call reverts.

For more on gas pricing, see [Transaction Fees](/operate/specification/transaction-fees).

### Adapter Addresses

The current adapter and token addresses for each network are listed on the [Fee currency contracts](/build/tools/contracts/fee-currencies) page, which is generated from the on-chain `FeeCurrencyDirectory` allowlist.

***

## Using Fee Abstraction with Celo CLI

Transfer 1 USDC using USDC as the fee currency via [`celocli`](/cli):

```bash theme={null}
celocli transfer:erc20 \
  --erc20Address 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B \
  --from 0x22ae7Cf4cD59773f058B685a7e6B7E0984C54966 \
  --to 0xDF7d8B197EB130cF68809730b0D41999A830c4d7 \
  --value 1000000 \
  --gasCurrency 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B \
  --privateKey [PRIVATE_KEY]
```

When using USDC, USD₮, or USA₮, use the **adapter address** (not the token address) as `--gasCurrency`. All three tokens use 6 decimals — pass `--value` in units of `10^6` (e.g. `1000000` = 1 USD₮/USDC/USA₮).

Transfer 1 USD₮ using USD₮ as the fee currency:

```bash theme={null}
celocli transfer:erc20 \
  --erc20Address 0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e \
  --from 0x22ae7Cf4cD59773f058B685a7e6B7E0984C54966 \
  --to 0xDF7d8B197EB130cF68809730b0D41999A830c4d7 \
  --value 1000000 \
  --gasCurrency 0x0e2a3e05bc9a16f5292a6170456a710cb89c6f72 \
  --privateKey [PRIVATE_KEY]
```

***

## Using Fee Abstraction with viem

We recommend [viem](https://viem.sh/), which serializes CIP-64 natively when you pass a Celo chain from `viem/chains`. Ethers.js and web3.js do not support the field on their own; use the [Celo Ethers.js wrapper](/build/tools/libraries-sdks/ethers) or the [web3 transaction-types plugin](/build/tools/libraries-sdks/web3). Whichever library you use, the wallet that signs the transaction must also support CIP-64; see [wallet support](/build/fee-abstraction/overview#wallet-support-for-cip-64).

### 1. Estimate the Gas Fee

Before sending, estimate the transaction fee so the UI can reserve that amount and prevent users from trying to transfer more than their available balance.

<Note>
  The gas price returned from the RPC is always expressed in 18 decimals, regardless of the fee currency.
</Note>

Use the adapter address (for USDC/USD₮/USA₮) or token address (for USDm, EURm, BRLm) as the `feeCurrency` value when estimating.

```js theme={null}
import { createPublicClient, hexToBigInt, http } from "viem";
import { celo } from "viem/chains";

const USDC_ADAPTER_MAINNET = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B";

const publicClient = createPublicClient({
  chain: celo,
  transport: http(),
});

const transaction = {
  from: "0xccc9576F841de93Cd32bEe7B98fE8B9BD3070e3D",
  to: "0xcebA9300f2b948710d2653dD7B07f33A8B32118C",
  data: "0xa9059cbb000000000000000000000000ccc9576f841de93cd32bee7b98fe8b9bd3070e3d00000000000000000000000000000000000000000000000000000000000f4240",
  feeCurrency: USDC_ADAPTER_MAINNET,
};

async function getGasPriceInUSDC() {
  const priceHex = await publicClient.request({
    method: "eth_gasPrice",
    params: [USDC_ADAPTER_MAINNET],
  });
  return hexToBigInt(priceHex);
}

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

async function main() {
  const gasPriceInUSDC = await getGasPriceInUSDC();
  const estimatedGas = await estimateGasInUSDC(transaction);

  // Total fee the user must reserve before transferring
  const transactionFeeInUSDC = formatEther(gasPriceInUSDC * estimatedGas).toString();
  return transactionFeeInUSDC;
}
```

### 2. Prepare the Transaction

Set `feeCurrency` to the adapter address (USDC/USD₮/USA₮) or token address (USDm, EURm, BRLm). Use transaction type `123` (`0x7b`), which is [CIP-64](/learn/protocol/transactions/transaction-types) compliant.

```js theme={null}
let tx = {
  // ... other transaction fields
  feeCurrency: "0x2f25deb3848c207fc8e0c34035b3ba7fc157602b", // USDC Adapter address
  type: "0x7b",
};
```

### 3. Send the Transaction

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 theme={null}
import { createWalletClient, http } from "viem";
import { celo } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { stableTokenAbi } from "@celo/abis";

const account = privateKeyToAccount("0x432c...");

const client = createWalletClient({
  account,
  chain: celo,
  transport: http(),
});

const USDC_ADAPTER_MAINNET = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B";
const USDC_MAINNET = "0xcebA9300f2b948710d2653dD7B07f33A8B32118C";

async function calculateTransactionFeesInUSDC(transaction) {
  const gasPriceInUSDC = await getGasPriceInUSDC();
  const estimatedGas = await estimateGasInUSDC(transaction);
  return gasPriceInUSDC * estimatedGas;
}

async function send(amountInWei) {
  const to = USDC_MAINNET;

  const data = encodeFunctionData({
    abi: stableTokenAbi,
    functionName: "transfer",
    args: ["0xccc9576F841de93Cd32bEe7B98fE8B9BD3070e3D", amountInWei],
  });

  const transactionFee = await calculateTransactionFeesInUSDC({ to, data });

  // Subtract the fee from the amount so the sender isn't over-spending
  const tokenReceivedByReceiver = parseEther("1") - transactionFee;

  const dataAfterFeeCalculation = encodeFunctionData({
    abi: stableTokenAbi,
    functionName: "transfer",
    args: ["0xccc9576F841de93Cd32bEe7B98fE8B9BD3070e3D", tokenReceivedByReceiver],
  });

  const hash = await client.sendTransaction({
    ...{ to, data: dataAfterFeeCalculation },
    feeCurrency: USDC_ADAPTER_MAINNET,
  });

  return hash;
}
```

***

## Troubleshooting

| Symptom | Cause and fix |
| - | - |
| `fee currency not registered: 0x…` from `eth_estimateGas` | The `feeCurrency` value is not on the allowlist. For USDC, USDT and USA₮ it is usually the token address: use the adapter address from [Fee currency contracts](/build/tools/contracts/fee-currencies). The zero address is not an alias for CELO either; omit `feeCurrency` to pay in CELO. |
| `execution reverted: Currency not in the directory` from `eth_gasPrice` | Same cause: the address passed as the `feeCurrency` parameter is a token address, not an adapter or directory entry. |
| `could not convert from native to fee currency (fee-currency=0x…): unregistered fee-currency address` from `eth_maxPriorityFeePerGas` | Same cause: pass the adapter address, not the token address. |
| The transaction succeeds but gas was charged in CELO, or fails with an insufficient-funds error although the user holds the stablecoin | The wallet dropped `feeCurrency`. Check [wallet support for CIP-64](/build/fee-abstraction/overview#wallet-support-for-cip-64). |
| A transfer or fee is 10¹² times too large or too small | USDC, USDT and USA₮ have 6 decimals, but `eth_gasPrice` and `balanceOf` on the adapter report 18 decimals. Use 6 decimals for token transfers and 18 for gas math. |

Where to set the fee currency:

| Tool | Parameter |
| - | - |
| viem | `feeCurrency` on the transaction, with a Celo chain from `viem/chains` |
| Celo CLI | `--gasCurrency` |
| Ethers.js, web3.js | Not supported on their own; use the [Celo Ethers.js wrapper](/build/tools/libraries-sdks/ethers) or the [web3 transaction-types plugin](/build/tools/libraries-sdks/web3) |
| JSON-RPC | `feeCurrency` in the `eth_estimateGas` call, and the fee currency address as the parameter of `eth_gasPrice` and `eth_maxPriorityFeePerGas` |

## Resources

| Resource | Link |
| - | - |
| Questions | [developer-tooling discussions](https://github.com/celo-org/developer-tooling/discussions/categories/q-a) |
| CIP-64 | [cip-0064](https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0064.md) |

## Related

* [Fee abstraction guide](/build/fee-abstraction/overview) — Background, why it matters and which wallets support it
* [Fee currency contracts](/build/tools/contracts/fee-currencies) — Token and adapter addresses for each network
* [Fee abstraction specification](/operate/specification/fee-abstraction) — Protocol rules, JSON-RPC changes and node flags
* [Adding fee currencies](/build/fee-abstraction/add-fee-currency) — Register a new fee currency


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.