The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →To estimate a DEX pool’s recent sandwich-attack rate, request hourly bars for the pool from Codex’s GraphQL API, then calculate a transaction-weighted average of the returned sandwichRate values. Treat the result as an indexer-derived historical estimate—not a prediction that a particular swap will or will not be attacked.
What a pool’s sandwich rate tells you
A sandwich attack brackets a victim’s swap with an attacker’s front-run and back-run. The front-run changes pool reserves before the victim trades, potentially worsening the victim’s exchange rate; the back-run completes the attacker’s position afterward. A pool’s historical sandwich rate summarizes indexed activity over an observation period. It does not measure the risk of a specific future transaction.
As an Amazon Associate I earn from qualifying purchases.
Codex’s field definition, as reproduced in the how-to article, describes sandwichRate as sandwiched events divided by transactions and says it is null when there is no transaction data. A null observation therefore means the rate is unavailable, not zero. The underlying mechanism and the effect of slippage limits are discussed in the ETH Zurich study of sandwich attacks.
Query hourly pool bars with Python
You need a Codex API key, Python, and the requests package. The example below calls getBars at https://graph.codex.io/graphql, requests 60-minute bars, and uses a Unix-time start and end. Supply the pool’s address and network ID in the symbol value. Codex expects the API key in the Authorization header without a Bearer prefix.
#1 Best Overall
import os
import time
import requests
from datetime import datetime, timedelta, timezone
API_URL = "https://graph.codex.io/graphql"
API_KEY = os.environ["CODEX_API_KEY"]
POOL_ADDRESS = "0xYourPoolAddress"
NETWORK_ID = 1 # Replace with the pool's network ID
# Example window: the previous seven days, ending now.
end_time = int(datetime.now(timezone.utc).timestamp())
start_time = int((datetime.now(timezone.utc) - timedelta(days=7)).timestamp())
query = """
query GetPoolBars($symbol: String!, $from: Int!, $to: Int!) {
getBars(
symbol: $symbol
from: $from
to: $to
resolution: "60"
) {
t
transactions
sandwichRate
liquidityUsd
volumeUsd
pair {
address
networkId
token0 { symbol }
token1 { symbol }
exchange { name }
}
}
}
"""
variables = {
"symbol": f"{NETWORK_ID}:{POOL_ADDRESS}",
"from": start_time,
"to": end_time,
}
response = requests.post(
API_URL,
headers={"Authorization": API_KEY, "Content-Type": "application/json"},
json={"query": query, "variables": variables},
timeout=30,
)
response.raise_for_status()
payload = response.json()
if payload.get("errors"):
raise RuntimeError(payload["errors"])
bars = payload["data"]["getBars"]
if not bars:
raise RuntimeError("No bars returned for this request")
pair = bars[0].get("pair") or {}
if pair.get("address", "").lower() != POOL_ADDRESS.lower():
raise RuntimeError(f"Returned a different pool: {pair.get('address')}")
if pair.get("networkId") != NETWORK_ID:
raise RuntimeError(f"Returned a different network: {pair.get('networkId')}")
weighted_numerator = 0.0
weighted_denominator = 0
sandwiched_estimate = 0.0
usable_bars = 0
missing_rate_bars = 0
transaction_total = 0
for bar in bars:
transactions = int(bar.get("transactions") or 0)
transaction_total += transactions
rate_value = bar.get("sandwichRate")
if rate_value is None:
missing_rate_bars += 1
continue
rate = float(rate_value)
weighted_numerator += rate * transactions
weighted_denominator += transactions
sandwiched_estimate += rate * transactions
usable_bars += 1
weighted_rate = (
weighted_numerator / weighted_denominator
if weighted_denominator else None
)
print({
"pool": pair.get("address"),
"network_id": pair.get("networkId"),
"tokens": [
(pair.get("token0") or {}).get("symbol"),
(pair.get("token1") or {}).get("symbol"),
],
"protocol": (pair.get("exchange") or {}).get("name"),
"bars_returned": len(bars),
"bars_with_rate": usable_bars,
"bars_without_rate": missing_rate_bars,
"transactions_in_all_bars": transaction_total,
"transactions_in_rate_denominator": weighted_denominator,
"weighted_sandwich_rate": weighted_rate,
"estimated_sandwiched_transactions": sandwiched_estimate,
})
The selected metadata fields can help identify the returned pair and describe the sample. If you also request fee or MEV fields, convert decimal strings to numbers before arithmetic and keep nulls as missing values. Their interpretation and availability can vary by chain and indexing coverage.
Check the response before trusting the number
- Call
raise_for_status()before parsing the response, and check GraphQL’serrorsarray before readingdata.getBars. - Verify the returned
pair.addressand network ID against the pool you intended to query. A syntactically valid request can resolve a token identifier to a pool; the echoed pool address is an important check. - EVM addresses are case-insensitive, but Solana base58 addresses are case-sensitive. Do not lowercase a Solana address for validation.
- The how-to article reports a maximum of 1,500 datapoints per request and recommends paging longer, fine-grained windows. Because API limits can change, confirm the current limit in Codex documentation before relying on it.
Calculate the aggregate correctly
For each bar with a non-null rate, multiply the rate by that bar’s transaction count. Divide the sum of those products by the sum of transaction counts for those same bars:
Rank #2
weighted_rate = sum(rate_i * transactions_i) / sum(transactions_i)
This weights each hour according to its transaction volume. A simple average gives a quiet hour the same influence as a busy hour and answers a different question. The denominator must include only bars with a non-null rate; if it is zero, report the aggregate as unavailable rather than zero.
The corresponding estimated number of sandwiched transactions is the sum of rate_i × transactions_i across bars with rates. It is an estimate derived from indexed rates, not a separately verified count of individual attacks. Report the transaction denominator and how many bars lacked a rate alongside the aggregate so readers can judge its coverage.
Interpret and compare pool rates
There is no established “good” threshold
The consulted how-to does not provide an official benchmark for a good sandwich rate. Its author recommends comparing pools for the same token pair over several days; that is practical guidance, not an industry standard. For a useful comparison, use the same observation window, rate definition, chain, and similar data coverage. Include each pool’s transaction denominator and missing-rate-bar count.
Do not substitute MEV risk for sandwich incidence
mevRiskLevel and sandwichRate describe different things. The how-to article says the MEV risk field reflects builder-tip share, which can relate to arbitrage, back-runs, liquidations, and other MEV activity—not just sandwiches. It reports one author-tested Ethereum USDC/WETH example dated September 29, 2026, where the pool had a zero sandwich rate while most hourly bars showed medium MEV risk. That single example illustrates why the fields can diverge; it is not a general finding about pools.
Read fee fields and missing values cautiously
A null fee field should not be read as a zero fee or zero MEV. The how-to author reports null fee fields in sampled Solana pools and null builder-tip fields on Base and Arbitrum, while cautioning that field behavior may depend on indexing availability or chain fee structure. Confirm current field semantics and network coverage with Codex before drawing conclusions from those fields.
A historical pool rate cannot certify a future trade
A low rate over the period you measured does not establish that your next swap is safe. Pool-level history cannot resolve the details of an individual transaction, including its size, timing, slippage tolerance, or submission path. Slippage limits can make a swap fail if the exchange-rate change exceeds the permitted bound; they are not a guarantee against adverse execution.
Best Value
Forensic alternative: inspect EVM attack legs with Dune
For trade-level investigation on supported EVM networks, Dune documents dex.sandwiches as a table of the outer front-run and back-run trades in sandwich attacks. Its documentation states: “The dex.sandwiches table captures detailed data on the outer trades of sandwich attacks in decentralized exchanges (DEXs), recording front-running and back-running trades across various EVM networks.” See Dune’s sandwiches table documentation.
This is a source for examining attack legs, not a ready-made per-pool hourly rate. To derive a rate, define the pool filter, time window, what counts in the numerator, and the transaction denominator explicitly. The how-to article also mentions a companion victim table, but the cited official documentation here covers dex.sandwiches; validate any other table name and schema before building a query around it.
How large studies fit—and do not fit—this check
Published attack counts provide context for the phenomenon, but they are not pool-specific benchmarks. A 2022 CHI study by Ye Wang, Patrick Zuest, Yaxing Yao, Zhicong Lu, and Roger Wattenhofer analyzed Uniswap and Sushiswap Ethereum data from May 4, 2020 through April 30, 2021, reporting 480,276 sandwich attacks across 5,728 pools in that historical period. That total cannot be used as a present-day pool rate.
Free tools Windows power users keep installed
One-click scans. No signup required.
A 2026 arXiv preprint by Lioba Heimbach, Ozan Solmaz, Burak Öz, and Christof Ferreira Torres studied protected-order-flow attacks across Ethereum, Solana, Tron, Base, Arbitrum, and Monad over three years. It reports 28.0 million attacks on Solana, 38,567 on Tron, 30,607 on Ethereum, and 1,889 on Base against transactions intended to be protected from front-running. Those counts are scoped to the study and its transaction set; they are neither current total chain counts nor comparable rates for the API method.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




