> ## 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 USDT 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}
# Celo mainnet (42220): token 0xcebA9300f2b948710d2653dD7B07f33A8B32118C (USDC, 6 decimals), fee currency 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B (USDC adapter, gas math in 18 decimals; this adapter has no decimals() function)
# --from and --to are example Celo mainnet addresses; use your own.
celocli transfer:erc20 \
  --node celo \
  --erc20Address 0xcebA9300f2b948710d2653dD7B07f33A8B32118C \
  --from 0x22ae7Cf4cD59773f058B685a7e6B7E0984C54966 \
  --to 0xDF7d8B197EB130cF68809730b0D41999A830c4d7 \
  --value 1000000 \
  --gasCurrency 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B \
  --privateKey [PRIVATE_KEY]
```

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

Transfer 1 USDT using USDT as the fee currency:

```bash theme={null}
# Celo mainnet (42220): token 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e (USDT, 6 decimals), fee currency 0x0E2A3e05bc9A16F5292A6170456A710cb89C6f72 (USDT adapter, 18 decimals)
# --from and --to are example Celo mainnet addresses; use your own.
celocli transfer:erc20 \
  --node celo \
  --erc20Address 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e \
  --from 0x22ae7Cf4cD59773f058B685a7e6B7E0984C54966 \
  --to 0xDF7d8B197EB130cF68809730b0D41999A830c4d7 \
  --value 1000000 \
  --gasCurrency 0x0E2A3e05bc9A16F5292A6170456A710cb89C6f72 \
  --privateKey [PRIVATE_KEY]
```

A key passed with `--privateKey` stays in your shell history, and other users on the machine can see it in the process list while the command runs. Use a dedicated account that holds only what you plan to send.

***

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

To run the scripts below, install viem with `npm install viem`, save each script as an `.mjs` file (they use `import` and top-level `await`), and run it with `node`. They target Celo mainnet and use USDC. For USDT or USA₮, replace the token and adapter addresses with that token's pair from [Fee currency contracts](/build/tools/contracts/fee-currencies); both tokens have 6 decimals, so the math stays the same.

### 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.

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

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

// Celo mainnet (42220), USDC, 6 decimals
const USDC = "0xcebA9300f2b948710d2653dD7B07f33A8B32118C";
// Celo mainnet (42220), USDC adapter, the fee currency for USDC
// (gas math in 18 decimals; this adapter has no decimals() function)
const USDC_ADAPTER = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B";

// Example Celo mainnet addresses; use your own. estimateGas reverts if the sender holds less than 1 USDC.
const sender = "0xccc9576F841de93Cd32bEe7B98fE8B9BD3070e3D";
const recipient = "0xDF7d8B197EB130cF68809730b0D41999A830c4d7";

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

// Transfer 1 USDC: token amounts use the token's 6 decimals
const data = encodeFunctionData({
  abi: erc20Abi,
  functionName: "transfer",
  args: [recipient, parseUnits("1", 6)],
});

// eth_gasPrice takes the fee currency as a parameter and returns a hex string
const gasPrice = hexToBigInt(
  await publicClient.request({ method: "eth_gasPrice", params: [USDC_ADAPTER] }),
);
const gas = await publicClient.estimateGas({
  account: sender,
  to: USDC,
  data,
  feeCurrency: USDC_ADAPTER,
});

// Total fee the user must reserve, in the fee token, scaled to 18 decimals
const fee = gasPrice * gas;
console.log(`Estimated fee: ${formatEther(fee)}`);
```

### Prepare the transaction

Set `feeCurrency` to the adapter address (USDC/USDT/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", // Celo mainnet (42220), USDC adapter (gas math in 18 decimals)
  type: "0x7b",
};
```

### Send the transaction

> [!WARNING]
>
> This script sends real USDC on Celo mainnet. Use a dedicated account that holds only what you plan to send, keep its key in an environment variable or a secret store, and never commit or log it. To try the script on Celo Sepolia first, replace `celo` with `celoSepolia` and use the Celo Sepolia USDC token and adapter from [Fee currency contracts](/build/tools/contracts/fee-currencies).

The script below sends 1 USDC in total, fee included: it estimates the fee as in [Estimate the gas fee](#estimate-the-gas-fee), converts it from 18 to 6 decimals, and transfers 1 USDC minus the fee. The network takes the fee from the sender's USDC balance before it runs the transfer, so the balance must cover the transferred amount plus the fee at the time of inclusion. That fee can rise above the estimate if the base fee goes up in between, so keep a small margin above 1 USDC. The network also checks that the balance covers the gas limit times the maximum fee per gas, which is higher than the estimate. Set `PRIVATE_KEY` to the sender's private key, a `0x`-prefixed hex string.

```js theme={null}
import {
  createPublicClient,
  createWalletClient,
  encodeFunctionData,
  erc20Abi,
  hexToBigInt,
  http,
  parseUnits,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { celo } from "viem/chains";

// Celo mainnet (42220), USDC, 6 decimals
const USDC = "0xcebA9300f2b948710d2653dD7B07f33A8B32118C";
// Celo mainnet (42220), USDC adapter, the fee currency for USDC
// (gas math in 18 decimals; this adapter has no decimals() function)
const USDC_ADAPTER = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B";

// Example Celo mainnet recipient; use your own.
const recipient = "0xDF7d8B197EB130cF68809730b0D41999A830c4d7";

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const publicClient = createPublicClient({ chain: celo, transport: http() });
const walletClient = createWalletClient({ account, chain: celo, transport: http() });

function transferData(amount) {
  return encodeFunctionData({
    abi: erc20Abi,
    functionName: "transfer",
    args: [recipient, amount],
  });
}

const total = parseUnits("1", 6); // 1 USDC

const gasPrice = hexToBigInt(
  await publicClient.request({ method: "eth_gasPrice", params: [USDC_ADAPTER] }),
);
const gas = await publicClient.estimateGas({
  account,
  to: USDC,
  data: transferData(total),
  feeCurrency: USDC_ADAPTER,
});

// The fee is in 18 decimals; convert it to USDC's 6, rounding up
const scale = 10n ** 12n;
const feeInUsdc = (gasPrice * gas + scale - 1n) / scale;

const hash = await walletClient.sendTransaction({
  to: USDC,
  data: transferData(total - feeInUsdc),
  feeCurrency: USDC_ADAPTER,
});
console.log(`Sent: ${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.