How Crius measures token taxes (realized vs simulated)
How Crius measures buy, sell and transfer taxes on Robinhood Chain tokens from real trades and from a simulated buy and sell, how the two are combined, and how tax changes are tracked.
A token’s tax is the part of a trade that doesn’t reach the trader: the pool sends 1,000 tokens and the buyer receives 930. Crius measures taxes two ways for every Robinhood Chain token. Realized taxes come from real trades: what traders actually paid. Simulated taxes come from a test buy, transfer and sell run against the latest block. Real trades are the ground truth when there are enough of them; the simulation fills the gap for new tokens and catches tokens that block sells.
Realized tax: from real trades
For every trade, Crius compares the tokens the pool sent (or received) with what the trader’s wallet received (or sent). The difference is that trade’s tax, whoever takes it: the token contract, a fee-on-transfer mechanism or the pool’s Uniswap v4 hook. Each trade on the trade tape carries it as fees.token_tax.pct.
From those per-trade taxes, /security builds the current rate:
- The median of the latest 50 trades per side, within the last 30 days (
tax.realized.window: "30d"). A median ignores the odd trade that paid much more or less than the rest. - The developer is measured separately.
tax.realized.by_wallet_classhasothers_sell_pctfor ordinary sellers anddev_sell_pctfor the developer, so a developer who sells tax-free doesn’t hide the tax everyone else pays. The headline rate uses ordinary wallets. tax.confidenceishighwith 20+ measured trades,mediumwith 3+ andlowbelow that.tax.realized.samplesgives the counts.
A median that ignores outliers: CHARGE
Charge AI (0x7b8892cea949bc60d494681d1bc3ea96071ab0b8) shows a 0% sell tax, but one sell paid far more. Which is right?
curl "https://api.crius.sh/v1/robinhood/token/0x7b8892cea949bc60d494681d1bc3ea96071ab0b8/security"const res = await fetch("https://api.crius.sh/v1/robinhood/token/0x7b8892cea949bc60d494681d1bc3ea96071ab0b8/security");const { data, meta } = await res.json();import requests
r = requests.get( "https://api.crius.sh/v1/robinhood/token/0x7b8892cea949bc60d494681d1bc3ea96071ab0b8/security",)body = r.json()print(body["data"])Real response 200 Captured Oct 28, 2026 · 177 lines (arrays trimmed)
{ "data": { "contract": { "blacklist": false, "can_change_limits": false, "can_change_tax": false, "cooldown": false, "implementation": null, "is_proxy": false, "mintable": false, "owner": null, "ownership": "none", "pausable": false, "proxy_kind": null, "self_destruct": false, "trading_switch": false, "upgradeable": false, "verified": null }, "flags": [ "unverified" ], "honeypot": { "is_honeypot": false, "reason": null, "selective": false }, "hook": { "address": "0xe5e702641ea86f4ae6cc3cdaed2b886f976be044", "capabilities": { "can_block_swaps": false, "can_gate_liquidity": false, "can_modify_liquidity_amounts": false, "can_modify_swap_amounts": true }, "code": { "admin_functions": [], "checked_ts": 1793207140434, "code_hash": "0xc21b1e6c1b45403e81a581f22ed6d9c747997af1cfdac1b1dc9f4b1d346a10db", "family": "0xabda3266e50c", "kind": "contract", "logic": null, "owner": "0x263ed295dafae1d9aadd6e56c4b6f9f38ee019dd", "ownership": "active", "size": 15167 }, "family": { "honeypot_pct": 0, "hooks": 22, "id": "0xabda3266e50c", "liquidity_pulled_pct": 0.684086, "pools_traded": 10525, "taxed_pct": 51.9715, "verified_hooks": 1 }, "flags": [], "name": "PonsV2MemeHook", "observed": { "dynamic_tax_pools": 13, "first_trade_ts": 1785866942000, "honeypot_pools": 0, "last_trade_ts": 1793223683000, "liquidity_pulled_pct": 0.66813, "liquidity_pulled_pools": 70, "max_buy_tax_pct": 10.99, "max_sell_tax_pct": 0.7, "median_pool_life_min": 53423.5, "pools": 10478, "pools_traded": 10477, "tax_measured_pools": 10392, "taxed_10pct_pools": 35, "taxed_pools": 5463, "volume_usd": 5384607135 }, "pattern": { "description": "hooks that change swap amounts only", "honeypot_pct": 0.0696347, "id": "swap_delta_only", "liquidity_pulled_pct": 7.93603, "pools_traded": 43082, "taxed_pct": 15.7537 }, "permissions": [ "beforeInitialize", "afterSwap", "afterSwapReturnDelta" ], "risk": [ "can_modify_swap_amounts" ], "risk_level": "low" }, "known_asset": false, "launch": { "flags": [], "lp_top_wallet": { "address": "0x2d424d69b1ead72b30de4c7fe4c53e4853e1419d", "liquidity_usd": 3.0953, "pct": 0.0551662 }, "sniper_supply_pct": 6.09195, "sniper_top_wallet_pct": 6.09195, "sniper_wallets": 1, "sniper_window_blocks": 10, "uniform_holders": null, "wallet_farm": null }, "limits": {}, "liquidity": { "status": "partially_locked", "summary": { "burned_pct": 0, "locked_pct": 99.8878, "next_unlock_ts": null, "protocol_fee_pct": 0, "unlocked_in_wallets_pct": 0.1122, "weighting": "by pool liquidity_usd" } }, "primary_dex": "uniswap_v4", "risk": "low", "score": 95, "tax": { "buy_pct": 2.99, "confidence": "high", "realized": { "buy_pct": 2.99, "by_wallet_class": { "dev_sell_pct": null, "others_sell_pct": 0 }, "last_sample_ts": 1793196204000, "max_sell_pct_seen": 96.64, "samples": { "buys": 2539, "sells": 1873 }, "sell_pct": 0, "window": "30d" }, "sell_pct": 0, "simulated": null, "source": "realized", "transfer_pct": null }, "tax_history": [ { "from_block": 88385895, "pct": 0, "side": "buy" }, { "from_block": 88387118, "pct": 0, "side": "sell" }, { "from_block": 88461329, "pct": 3, "side": "buy" } ], "tax_history_total": 29, "tax_mode": "dynamic", "token": "0x7b8892cea949bc60d494681d1bc3ea96071ab0b8" }, "meta": { "block": 100406827, "block_age_ms": 1163, "block_ts": 1793225080000, "chain": "robinhood", "finalized_block": 100397649, "lag_ms": 64, "safe_block": 100401329, "simulation": "no V2 WETH pair: realized tax only" }}What this tells you
tax.sell_pct: 0whiletax.realized.max_sell_pct_seen: 96.64. Ordinary sells pay nothing; one sell lost almost everything. The median of recent sells is the rate a new seller can expect.tax.buy_pct: 2.99, from 2,539 buys and 1,873 sells measured (confidence: "high").tax_history_total: 29andtax_mode: "dynamic": the measured rate has changed many times. The first entries show 0% on both sides at launch, then 3% on buys from block 88461329, eight blocks after its v4 liquidity was locked in the Pons launch locker at block 88461321 (see the liquidity example): the launchpad’s fee on the graduated pool.tax.simulated: null,meta.simulation: "no V2 WETH pair: realized tax only": with no Uniswap v2 pair to simulate through, real trades are the whole answer.
Simulated tax: a test trade at the latest block
When a token has a Uniswap v2 pair with WETH, Crius simulates a full round trip through the Uniswap v2 router at the latest block, without sending anything on chain:
- Buy about
amount_usdworth (default $100) and compare the tokens received with what the router quoted: the buy tax. - Transfer 10% of the tokens to a second wallet: the transfer tax (
tax.transfer_pct), and whether transfers work at all (can_transfer). - Approve and sell half of the rest. The sell tax is the share of tokens that never reached the pair, which doesn’t depend on price impact.
The result is in tax.simulated: can_buy, can_sell, buy_pct, sell_pct, transfer_pct, gas used, and revert_reason when a step fails. A buy that works followed by a sell that fails is a honeypot (see How to detect a honeypot). Results are cached briefly; fresh=1 runs a new simulation (Starter plan, 20 credits).
How the two are combined
| Real trades available | tax.buy_pct / tax.sell_pct | tax.source |
|---|---|---|
| Recent buys and sells | Both realized | realized |
| Recent buys but no recent sells by ordinary wallets | Buy realized, sell simulated | realized+simulated (or realized without a simulation) |
| No recent buys | Simulated where available | simulated, or none without a v2 WETH pair |
When both a realized and a simulated sell tax exist and they differ by more than 2 percentage points, the token gets the tax_mismatch flag (−10 points). In the security score, a buy or sell tax above 5% costs 15 points (tax_over_5pct) and above 10% costs 30 (high_tax).
Tax history and dynamic taxes
tax_history turns the per-trade taxes of ordinary wallets into segments: a new entry (from_block, side, pct in whole percent) starts when a side’s rate changes and stays changed for at least two trades in a row, so one odd trade doesn’t create a change. tax_mode is dynamic when either side has more than one segment, else static. The response holds the newest 100 segments; tax_history_total counts all of them.
A dynamic tax isn’t always a trap. Launchpad tokens often go from 0% on the bonding curve to the launchpad’s fee after graduation, as CHARGE did. What matters is whether someone can still change it: contract.can_change_tax with an active owner, or a hook with a fee setter (fee_changeable in hook.flags).
Known assets
WETH, USDG and tokenized stocks (known_asset: true) have no tax to measure and no WETH pair to simulate against: their tax fields are empty and their trade noise isn’t treated as a tax history.
Frequently asked questions
Why does Crius use real trades instead of reading the tax from the contract?
Because the contract is often not where the tax is. On Robinhood Chain many taxes are taken by a Uniswap v4 hook, some contracts tax only certain wallets or sizes, and a tax variable can say one thing while the transfer logic does another. Comparing what the pool sent with what the wallet received catches all of these.
Why is the sell tax 0 when one sell paid 96%?
The headline rate is the median of the latest 50 sells by ordinary wallets, so one odd trade doesn't set it. tax.realized.max_sell_pct_seen keeps the worst single sell so you can still see it.
What does tax_mismatch mean?
The simulated sell tax and the realized sell tax differ by more than 2 percentage points. The token may tax differently by wallet or size, or the tax changed recently. It costs 10 points in the security score.
Related
Get your free Crius API key
Every example on this page works with a free key: higher rate limits than the anonymous plan, holders, wallets and the WebSocket stream. No card needed.
- Robinhood Chain (4663) only, from our own archive nodes
- REST + WebSocket
- Pay on-chain or by card