# Pay gas fees with ERC20 tokens

Instead of sponsoring gas entirely, you can charge users in ERC20 tokens. This is the **User pays** sponsorship mode in the dashboard. Openfort supports two pricing strategies:

| Strategy | Schema | How the fee is calculated | `tokenContractAmount` |
|----------|--------|---------------------------|----------------------|
| **Dynamic price** | `charge_custom_tokens` | Gas cost converted to tokens at a live exchange rate (via CoinGecko) | Optional — omit for automatic pricing |
| **Fixed price** | `fixed_rate` | A constant token amount per transaction, regardless of gas cost | Required — the flat fee in smallest token units |

## Token support

You can use **any ERC20 token** for both strategies. If the token has a CoinGecko price feed, Openfort can calculate the exchange rate automatically when you omit `tokenContractAmount`. For all other tokens, provide `tokenContractAmount` yourself.

### Tokens with automatic exchange rates

The following tokens support automatic rate calculation. For any token not listed here, you can still use it by setting `tokenContractAmount` manually.

:::tip
In the dashboard, these supported tokens can be selected directly in the fee sponsorship form under **User pays**. You don't need to register them in the Contracts tab first — the token is added to your contract library automatically when you create the sponsorship.
:::

<details>
  <summary style={{ margin: '12px 0', fontSize: 15, fontWeight: 500 }}>See all mainnet tokens</summary>

  | Chain | Symbol | Decimals | Address |
  |-------|--------|----------|---------|
  | **Ethereum** | USDC | 6 | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` |
  | | USDT | 6 | `0xdAC17F958D2ee523a2206206994597C13D831ec7` |
  | | DAI | 18 | `0x6B175474E89094C44Da98b954EedeAC495271d0F` |
  | | WETH | 18 | `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2` |
  | **Polygon** | USDC | 6 | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |
  | | USDT | 6 | `0xc2132D05D31c914a87C6611C10748AEb04B58e8F` |
  | | DAI | 18 | `0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063` |
  | | WMATIC | 18 | `0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270` |
  | **Base** | USDC | 6 | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
  | | DAI | 18 | `0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb` |
  | | WETH | 18 | `0x4200000000000000000000000000000000000006` |
  | **Optimism** | USDC | 6 | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` |
  | | USDT | 6 | `0x94b008aA00579c1307B0EF2c499aD98a8ce58e58` |
  | | DAI | 18 | `0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1` |
  | **Arbitrum** | USDC | 6 | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` |
  | | USDT | 6 | `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9` |
  | | WETH | 18 | `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1` |
  | **Avalanche** | USDC | 6 | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` |
  | | USDT | 6 | `0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7` |
  | | DAI.e | 18 | `0xd586E7F844cEa2F87f50152665BCbc2C279D8d70` |
  | | WETH.e | 18 | `0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB` |
  | **BSC** | USDC | 18 | `0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d` |
  | | USDT | 18 | `0x55d398326f99059fF775485246999027B3197955` |
  | | DAI | 18 | `0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3` |
  | | WBNB | 18 | `0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c` |
</details>

### Testnet tokens (USDC)

Use these USDC test tokens to try ERC-20 gas sponsorship on testnets:

<details>
  <summary style={{ margin: '12px 0', fontSize: 15, fontWeight: 500 }}>See testnet USDC addresses</summary>

  | Chain | Chain ID | USDC Address |
  |-------|----------|--------------|
  | **Sepolia** | 11155111 | `0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238` |
  | **Arbitrum Sepolia** | 421614 | `0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d` |
  | **Avalanche Fuji** | 43113 | `0x5425890298aed601595a70AB815c96711a31Bc65` |
  | **Base Sepolia** | 84532 | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |
  | **Optimism Sepolia** | 11155420 | `0x5fd84259d66Cd46123540766Be93DFE6D43130D7` |
  | **Polygon Amoy** | 80002 | `0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582` |
  | **Monad Testnet** | 10143 | `0x534b2f3A21130d7a60830c2Df862319e593943A3` |
</details>

## Dynamic price

Users pay gas fees in ERC20 tokens at a live exchange rate. The fee is calculated at transaction time as:

```text
tokenFee = estimatedGasCost × (nativeTokenPrice / tokenPrice) × 10^tokenDecimals
```

::::steps

### Create a policy for the sponsorship rules

Create a [policy](/docs/configuration/policies) that defines which transactions should be sponsored. Use the `sponsorEvmTransaction` operation:

```bash
curl -X POST https://api.openfort.io/v2/policies \
  -H "Authorization: Bearer $YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "project",
    "description": "Sponsor all transactions",
    "rules": [
      {
        "action": "accept",
        "operation": "sponsorEvmTransaction",
        "criteria": []
      }
    ]
  }'
```

### Create the fee sponsorship with dynamic pricing

Create the fee sponsorship and link it to your policy. Omit `tokenContractAmount` for automatic exchange rate calculation:

```bash
curl -X POST https://api.openfort.io/v2/fee-sponsorship \
  -H "Authorization: Bearer $YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dynamic USDC gas payment",
    "strategy": {
      "sponsorSchema": "charge_custom_tokens",
      "tokenContract": "con_..."
    },
    "policyId": "ply_..."
  }'
```

The response includes `dynamicExchangeRate: true`, confirming auto-pricing is active:

```json
{
  "id": "pol_...",
  "object": "feeSponsorship",
  "enabled": true,
  "strategy": {
    "sponsorSchema": "charge_custom_tokens",
    "tokenContract": "con_...",
    "tokenContractAmount": null,
    "dynamicExchangeRate": true
  },
  "policyId": "ply_..."
}
```

:::tip
You can also provide a manual `tokenContractAmount` for the exchange rate if you prefer to control it yourself. This is required for tokens without CoinGecko price feeds.
:::

::::

## Fixed price

Users pay a flat fee per transaction in ERC20 tokens, regardless of actual gas cost. This gives predictable costs for your users.

::::steps

### Create a policy for the sponsorship rules

Same as above — create a [policy](/docs/configuration/policies) that defines which transactions are eligible:

```bash
curl -X POST https://api.openfort.io/v2/policies \
  -H "Authorization: Bearer $YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "project",
    "description": "Sponsor all transactions",
    "rules": [
      {
        "action": "accept",
        "operation": "sponsorEvmTransaction",
        "criteria": []
      }
    ]
  }'
```

### Create the fee sponsorship with fixed pricing

Specify `tokenContractAmount` as the flat fee in the token's smallest units:

```bash
curl -X POST https://api.openfort.io/v2/fee-sponsorship \
  -H "Authorization: Bearer $YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Fixed 1 USDC per transaction",
    "strategy": {
      "sponsorSchema": "fixed_rate",
      "tokenContract": "con_...",
      "tokenContractAmount": "1000000"
    },
    "policyId": "ply_..."
  }'
```

:::info
The `tokenContractAmount` is in the token's smallest units. For USDC (6 decimals), `"1000000"` = 1 USDC. For DAI (18 decimals), `"1000000000000000000"` = 1 DAI.
:::

::::

## API reference

### Create fee sponsorship

`POST /v2/fee-sponsorship`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | No | Display name |
| `strategy.sponsorSchema` | string | Yes | `charge_custom_tokens` or `fixed_rate` |
| `strategy.tokenContract` | string | Yes | Contract ID (`con_...`) of the ERC20 token |
| `strategy.tokenContractAmount` | string | No | Token amount in smallest units. Required for `fixed_rate`. Omit for dynamic auto-pricing with `charge_custom_tokens`. |
| `policyId` | string | Yes | Policy ID (`ply_...`) with sponsorship rules |
