DEV Community

Cover image for Same Exchange, 3.5x the Fee: Modeling Regional Crypto Fee Schedules in TypeScript
starsearn
starsearn

Posted on

Same Exchange, 3.5x the Fee: Modeling Regional Crypto Fee Schedules in TypeScript

Last week I wrote about a crypto calculator that shows what five exchanges give you after fees. Its example table said a market order costs 0.10% on Bybit and 0.10% on OKX.

For anyone in Germany, France, Spain, the Netherlands or any other EEA country, that was wrong — by a factor of 2.5 to 3.5.

The fee depends on which company you sign up with

Big exchanges are no longer one company. Under MiCA, the EU's crypto rulebook (its last transition periods ran out on 1 July 2026), a European resident opens an account with a separate, locally licensed entity, and that entity publishes its own fee schedule:

Global platform EEA platform $1,000 market buy, global → EEA
Bybit 0.10% / 0.10% Bybit EU (Austria): 0.25% / 0.25%, as reported $1.00 → $2.50
OKX 0.08% / 0.10% OKX EEA: 0.20% / 0.35% on a spot-only account $1.00 → $3.50
Kraken Pro 0.40% / 0.80% the same schedule $8.00 → $8.00

Maker / taker, entry tier, spot.

Two details made it more interesting than "add a column":

  • OKX EEA drops to 0.08% / 0.10% once you also open a derivatives account. Same person, same coin, a 3.5x cheaper market order — decided by a step in onboarding.
  • Bybit EU's help centre blocks automated reading. Its 0.25% comes from reviewers and from Bybit EU's help pages as search engines show them, not from a page I could load myself. That has to be visible to the reader, not buried in a comment.

Before: one number per exchange

interface ExchangeFeeData {
  id: ExchangeId;
  proLabel: string; // "Bybit Spot"
  maker: number;    // 0.001 = 0.10%
  taker: number;
}
Enter fullscreen mode Exit fullscreen mode

Simple, and it quietly assumes that every visitor signs up with the same company.

After: regional overrides

Each exchange keeps its global schedule and gets an optional list of regional ones:

export interface RegionalFees {
  countries: ReadonlySet<string>; // ISO 3166-1 alpha-2
  proLabel: string;               // the platform the reader will actually sign up for
  maker: number;
  taker: number;
  feesOfficial: boolean;          // false = reported by third parties, not read on the exchange's own page
  note?: string;
  sources: { label: string; url: string }[];
}

// European Economic Area: the 27 EU states plus Iceland, Liechtenstein and Norway
export const EEA: ReadonlySet<string> = new Set([
  "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT",
  "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO",
]);
Enter fullscreen mode Exit fullscreen mode

Bybit's entry then looks like this (trimmed):

{
  id: "bybit",
  proLabel: "Bybit Spot",
  maker: 0.001,
  taker: 0.001,
  feesOfficial: true,
  regional: [
    {
      countries: EEA,
      proLabel: "Bybit EU Spot",
      maker: 0.0025,
      taker: 0.0025,
      feesOfficial: false,
      note: "EEA residents trade on Bybit EU (MiCA, Austria): one 0.25% rate for maker and taker since 5 Oct 2026, lower with volume. bybit.eu blocks automated checks, so the rate is as reported.",
      sources: [{ label: "Bybit EU spot fees", url: "https://www.bybit.eu/en-EU/help-center/article/Bybit-Spot-Fees-Explained" }],
    },
  ],
}
Enter fullscreen mode Exit fullscreen mode

One function decides which schedule a visitor gets:

export function feesFor(e: ExchangeFeeData, country: string | undefined) {
  const r = country ? e.regional?.find((x) => x.countries.has(country)) : undefined;
  return r
    ? { proLabel: r.proLabel, maker: r.maker, taker: r.taker, feesOfficial: r.feesOfficial, note: r.note ?? e.note }
    : { proLabel: e.proLabel, maker: e.maker, taker: e.taker, feesOfficial: e.feesOfficial, note: e.note };
}
Enter fullscreen mode Exit fullscreen mode

The fee calculator, the converter from last week's post, the side-by-side comparison tables and the exchange reviews all call feesFor(). When Bybit EU changes its rate, one object changes.

Three decisions mattered more than the code:

  1. The override carries its own name. "Bybit EU Spot: 0.25%" tells the reader which company they would be signing up with. A lot of confusion in exchange reviews comes from mixing up a global platform with its regional sibling.
  2. "Official" belongs to a number, not to an exchange. OKX's EEA rate is read on OKX's own page; Bybit EU's isn't. So one exchange can have an official global fee and a reported regional one, and the UI prints "(reported)" next to the second kind.
  3. The EEA is not the EU, and neither is Europe. The set has 30 countries. Switzerland and the UK aren't in it and need their own answers. Bybit, for example, returned to the UK in December 2025 with spot trading and P2P but without a UK fee schedule I could confirm, so the UK entry shows the global rate with a note that says exactly that.

Where the country comes from

Cloudflare adds a cf-ipcountry header to every request. The server reads it and passes it to the calculator, which shows it as "Germany (detected)". The reader can change it, which helps expats, VPN users and anyone checking what a friend abroad would pay.

What a reader in Germany sees now

The fee calculator for a reader in Germany: a $1,000 market buy of Bitcoin, with Bybit EU at 0.25% and OKX EEA at 0.35%

A $1,000 market buy of Bitcoin for a reader in Germany, new-user bonuses switched off. Bybit EU and OKX's EEA platform appear at their EU rates, each with its source note.

Below them come Kraken Pro at 0.80% ($8.00) and Coinbase Advanced at 1.20% as reported ($12.00). Exchanges that don't take the reader's country stay on the list, greyed out and marked "Not available", so nobody wonders whether the cheap one was forgotten.

What I'd tell anyone building a price comparison

  • Model the legal entity, not the brand. A fee belongs to a company, an account type and a volume tier. The brand is the logo on top.
  • Store where and when each number was read. Every rate in the fee file has a source URL next to it, and the page prints the date the fees were checked. When a reader asks "is this current?", the page has already answered.
  • Prefer "reported" or "—" to a confident wrong number. A stale fee on a money tool costs more trust than a missing one.
  • Re-read what you published. This post exists because a week-old post of mine was wrong for a big share of its readers.

You can run your own amount and country here: crypto exchange fee calculator. If you only care about two exchanges, the same numbers are in side-by-side tables, for example OKX vs Bybit.


Disclosure: I run StarsEarn. Kraken and Bybit buttons on the site are referral links. New Kraken users who deposit $100 by bank or crypto and trade $100 get $10 in Bitcoin, and the calculator counts that bonus unless you untick the box. OKX and Coinbase aren't partners of ours.

Top comments (0)