# Execution Fee

Because the protocol pays gas for 1CT trades, each intent carries a small onchain fee in an ERC-20
token. It is priced in USD, configured per action, and charged when the intent executes.

The fee is part of the signed message (`feeToken` and `antiDdosFee`), so you must resolve it
*before* signing — and the values you sign have to match the server's current configuration.

## How it works

- Each action has a USD fee amount and a set of accepted fee tokens, each with a priority.
- The server converts USD to token amounts and republishes the configuration **every 60 seconds**.
- The last two generations of configuration remain valid, so a signature stays acceptable for about
  **120 seconds** after you read the config.
- On execution the contract transfers `antiDdosFee` of `feeToken` from the trader. A failed transfer
  skips the intent with `skipReason = "FEE"` — it is not executed.
- When `antiDdosFee` is `0`, no transfer is attempted onchain — but the relayer still checks
  `feeToken` at submission. A disabled action must be signed with `feeToken = 0x0000…0000`; pairing
  a zero fee with a real token address is rejected with `400 anti-ddos not enabled`.

## Fetching the configuration

```http
GET /v2/trading/anti-ddos-config
```

```json
[
  { "action": 0, "actionName": "MARKET_OPEN",  "feeToken": "0x7547…", "antiDdosFee": "500000", "enabled": true, "priority": 0 },
  { "action": 0, "actionName": "MARKET_OPEN",  "feeToken": "0x3bd3…", "antiDdosFee": "12345678901234567", "enabled": true, "priority": 1 },
  { "action": 1, "actionName": "MARKET_CLOSE", "feeToken": "0x0000000000000000000000000000000000000000", "antiDdosFee": "0", "enabled": false, "priority": 2147483647 }
]
```

| Field | Meaning |
| :--- | :--- |
| `action` | Action number |
| `feeToken` | ERC-20 address the fee is charged in |
| `antiDdosFee` | Amount in that token's decimals, as a string |
| `enabled` | `false` means this action is free |
| `priority` | Lower is preferred |

**One action appears on several rows** — one per accepted fee token. When an action is disabled, sign
with `feeToken = 0x0000…0000` and `antiDdosFee = 0`.

Refresh every 60 seconds and cache the result. Do not fetch it inline while signing.

```ts
let feeConfig: AntiDdosConfig[] = []

export async function refreshFeeConfig(baseUrl: string) {
  const res = await fetch(`${baseUrl}/v2/trading/anti-ddos-config`)
  if (!res.ok) throw new Error(`anti-ddos config: HTTP ${res.status}`)
  feeConfig = await res.json()
}

export function startFeeConfigRefresh(baseUrl: string) {
  void refreshFeeConfig(baseUrl).catch(console.warn)
  return setInterval(() => void refreshFeeConfig(baseUrl).catch(console.warn), 60_000)
}
```

## Selecting a fee token

Walk the enabled options for the action in priority order and pick the first one the trader can
actually pay.

```
for config of options.sortBy(priority):
    required = config.antiDdosFee
             + (amount this trade spends of the same token, if any)
    if balance(feeToken) >= required and allowance(feeToken, diamond) >= required:
        use it
```

```ts
export function selectFeeToken(
  action: number,
  getAccountState: (token: Address) => { balance: bigint; allowance: bigint } | undefined,
  additionalSpends: readonly { token: Address; amount: bigint }[] = [],
): { feeToken: Address; antiDdosFee: bigint } {
  const options = feeConfig
    .filter((c) => c.action === action && c.enabled)
    .sort((a, b) => a.priority - b.priority)

  if (options.length === 0) return { feeToken: ZERO_ADDRESS, antiDdosFee: 0n }

  for (const opt of options) {
    const token = opt.feeToken as Address
    const state = getAccountState(token)
    if (!state) continue

    const extra = additionalSpends
      .filter((s) => s.token.toLowerCase() === token.toLowerCase())
      .reduce((sum, s) => sum + s.amount, 0n)
    const required = BigInt(opt.antiDdosFee) + extra

    if (state.balance >= required && state.allowance >= required) {
      return { feeToken: token, antiDdosFee: BigInt(opt.antiDdosFee) }
    }
  }

  throw new Error('No fee token with sufficient balance and allowance')
}
```

::: danger Add the trade's own spend
If the trade itself spends the same token as the fee, that amount must be added to `required`.
Skipping this produces intents that pass selection and then fail onchain with
`skipReason = "FEE"`, because the collateral transfer consumed the balance the fee needed.

What counts as the trade's own spend:

| Action | Spends |
| :--- | :--- |
| `MARKET_OPEN`, `LIMIT_OPEN` | `amountIn` **plus [`extraFee`](/introduction/brokers#extrafee)** if you set one |
| `ADD_MARGIN` | `amount` |

```ts
const { feeToken, antiDdosFee } = selectFeeToken(
  OneClickAction.MARKET_OPEN,
  getCachedErc20State,
  [{ token: tokenIn, amount: amountIn + extraFee }],   // ← this line
)
```

`extraFee` is the one most integrations miss, because it is optional and usually zero during
development. A broker integration that turns it on later will start seeing `FEE` skips unless this
is accounted for.
:::

## Cache balances and allowances

`selectFeeToken` should read from a cache, never issue RPC calls inline — a round trip in the signing
path defeats the purpose of 1CT.

Prefetch `balanceOf(trader)` and `allowance(trader, diamond)` for every enabled fee token, and
refresh:

- on a timer,
- when the window regains focus,
- whenever the fee configuration changes.

Distinguish the failure modes so you can show something useful:

| State | Meaning | UI |
| :--- | :--- | :--- |
| `config-not-loaded` | Fee config not fetched yet | Wait |
| `account-state-not-ready` | Balance/allowance cache not warm | Wait |
| `needs-approval` | Balance is sufficient, allowance is not | Prompt for approval |
| `insufficient-balance` | Neither is sufficient | Prompt to fund |

## Approvals

The trader must approve the **Diamond** for each fee token they might pay in. Without it, intents are
accepted and then skipped onchain.

Approving the collateral token is separate — see
[Authorizing an Agent → Token approvals](/gasless/authorization#token-approvals). When the fee token
and the collateral token are the same, the allowance has to cover
`amountIn + extraFee + antiDdosFee` in total.

## Failure modes

| Symptom | Cause |
| :--- | :--- |
| `400 antiDdosFee mismatch` | Signed with a config older than ~120 seconds |
| `400 feeToken not supported` | Fee token not in the configuration for that action |
| `400 anti-ddos not enabled` | Disabled action signed with a non-zero fee, **or** with a `feeToken` other than `0x0000…0000` |
| `skipReason = FEE` | No approval to the Diamond, or the balance was consumed by the trade itself |

Next: [Submit & Track](/gasless/submit).
