Skip to main content

VIP Tier Fee Schedule

Overview

Perp trading fees follow a six-tier VIP ladder driven by a 14-day rolling notional volume across all USDⓈ-M perpetual pairs. Both maker and taker sides count. All fees are charged from the user's collateral margin — no external fee settlement.

The effective fee rate a user actually pays is:

effective_rate = base_rate × (1 − referral_discount) × (1 − token_staking_discount)
effective_fee = notional × effective_rate (rounded up to 6 decimals)
PieceCurrent value
referral_discount0.10 (10%)
token_staking_discount0.00 (interface reserved, always zero today)
discount_multiplier(1 − 0.10) × (1 − 0.00) = 0.90
notionalamount × mark_price
Precision6 decimals (USDC)

Tier Table

Level14d Volume (USD)TakerMaker
VIP 0< 5 M0.040%0.010%
VIP 15 M – 25 M0.036%0.008%
VIP 225 M – 100 M0.032%0.004%
VIP 3100 M – 500 M0.028%0.000%
VIP 4500 M – 2 B0.026%0.000%
VIP 5 2 B0.024%0.000%

At VIP 3 and above maker is free.

Upgrade / Downgrade Semantics

  • Upgrade is applied immediately the first time a fee-info read, a preview, or a fill observes the user crossing the next threshold.
  • Downgrade is scheduled, not immediate: the new (lower) level is recorded as pending_tier and takes effect at the next UTC 00:00. This gives users a 24-hour buffer against momentary volume dips.
  • An audit row is written to vip_tier_events for every upgrade_immediate, downgrade_scheduled, and downgrade_applied transition.

GET /account/fee-info

Returns the caller's current VIP tier, the full tier schedule, the 14/30-day rolling volumes, progress to the next tier, and the active discount stack.

Request

Auth: JWT (wallet) or API Key. No query parameters.

GET /api/v1/account/fee-info
Authorization: Bearer <jwt>

Response

{
"current_tier": 3,
"current_label": "VIP 3",
"current_maker": "0.00000",
"current_taker": "0.00028",
"effective_maker": "0.000000",
"effective_taker": "0.000252",
"volume_14d": "138206820.47",
"volume_30d": "215440192.11",
"fee_tiers": [
{ "level": 0, "label": "VIP 0", "maker": "0.00010", "taker": "0.00040", "volume_min": "0", "volume_max": "5000000" },
{ "level": 1, "label": "VIP 1", "maker": "0.00008", "taker": "0.00036", "volume_min": "5000000", "volume_max": "25000000" },
{ "level": 2, "label": "VIP 2", "maker": "0.00004", "taker": "0.00032", "volume_min": "25000000", "volume_max": "100000000" },
{ "level": 3, "label": "VIP 3", "maker": "0.00000", "taker": "0.00028", "volume_min": "100000000", "volume_max": "500000000" },
{ "level": 4, "label": "VIP 4", "maker": "0.00000", "taker": "0.00026", "volume_min": "500000000", "volume_max": "2000000000"},
{ "level": 5, "label": "VIP 5", "maker": "0.00000", "taker": "0.00024", "volume_min": "2000000000" }
],
"progress_to_next": {
"next_level": 4,
"next_label": "VIP 4",
"required_volume": "500000000",
"remaining_volume": "361793179.53",
"percent": "0.276413640"
},
"pending_tier": null,
"pending_effective_at": null,
"discounts": {
"referral": "0.10",
"token_staking": "0",
"multiplier": "0.90"
}
}

Notes:

  • current_maker / current_taker are the base rates before discount.
  • effective_maker / effective_taker already include discount_multiplier; use these for display and for reconciling with POST /orders/preview.
  • progress_to_next is omitted when the user is already at VIP 5.
  • pending_tier and pending_effective_at are non-null only when a downgrade is scheduled.

POST /orders/preview — fee fields

The preview endpoint returns fees that already include the discount multiplier, so frontends do not need to apply it again:

{
"order_value": "500.000",
"taker_fee_rate": "0.000252",
"maker_fee_rate": "0.000000",
"est_fee": "0.126000",
...
}

est_fee = order_value × (taker or maker rate) — which rate is used depends on order_type:

order_typerate used
markettaker_fee_rate
limitmaker_fee_rate

WebSocket Push — vip_tier

Authenticated clients may subscribe to the private vip_tier channel to receive push notifications whenever their tier changes.

Subscribe

{ "op": "subscribe", "args": ["vip_tier"] }

Event payload

{
"channel": "vip_tier",
"type": "vip_tier_changed",
"data": {
"user_address": "0xbc7c75bf109cda7104c2467532c578c0e8b0efe0",
"old_tier": 2,
"new_tier": 3,
"volume_14d": "138206820.47",
"reason": "upgrade_immediate",
"timestamp": 1776311435000
}
}

reason is one of:

ValueMeaning
upgrade_immediateVolume crossed a threshold upward; new tier is already in effect.
downgrade_scheduledVolume fell; lower tier will apply at next UTC 00:00. data.new_tier is the scheduled target.
downgrade_appliedThe previously scheduled downgrade just took effect.

Worker Cadence

  • Lazy path: Any call into GET /account/fee-info or POST /orders/preview triggers vip_tier::resolve(), which recomputes and persists the user's tier. Upgrades are applied on the spot, downgrades are scheduled.
  • Daily sweep: A background worker ticks every 10 minutes and, during the 00:00–00:10 UTC window, does two things: applies all due pending_effective_at <= NOW() downgrades, then re-evaluates every user who traded in the past 14 days (so tiers are always up-to-date even for users who never hit the API).

Data Model

The VIP tier service persists, per user:

  • current_tier — the tier currently in effect
  • effective_since — when that tier took effect
  • pending_tier + pending_effective_at — scheduled downgrade target and effective time (null when no change is pending)
  • last_volume_14d — rolling 14-day volume used to evaluate the tier
  • updated_at — last recompute timestamp

An append-only event log also records every tier transition (old tier, new tier, the 14-day volume at the time, reason) so downgrades and upgrades are fully auditable.

Code Examples

curl

curl -s -H "Authorization: Bearer ${JWT}" \
https://api-sepolia.ztdx.io/api/v1/account/fee-info | jq

Python

import requests

r = requests.get(
"https://api.ztdx.io/api/v1/account/fee-info",
headers={"Authorization": f"Bearer {jwt}"},
timeout=10,
)
info = r.json()
print(f"VIP {info['current_tier']} "
f"taker={info['effective_taker']} maker={info['effective_maker']} "
f"volume_14d={info['volume_14d']}")

FAQ

Q: Does the daily sweep ever lower a user's tier mid-day? No. Once current_tier is set, only a pending_effective_at <= NOW() check in the 00:00 window can lower it. Lazy reads inside the day can only upgrade.

Q: What if a user is inactive for 14+ days? Their volume_14d eventually becomes 0 and resolve() schedules a downgrade to VIP 0. The downgrade applies at the next UTC 00:00.

Q: Are maker rebates paid at VIP 3+? No — maker is zero at VIP 3+ but not negative. Negative-fee market-maker contracts are governed separately via the MM admin API.

Q: Can discount_multiplier exceed 1? No. Both referral_discount and token_staking_discount are capped in [0, 1), so the multiplier is always (0, 1].