The same perpetual future often pays a different funding rate on Hyperliquid and Lighter, two onchain crypto exchanges. Funding rate arbitrage holds the long on the venue that pays less and the short on the one that pays more, and collects the gap. Equal size on both legs cancels the shared price move, so the position is close to delta neutral. Each leg still has its own margin and can be liquidated on its own venue. This is the cross-venue form, perp against perp; the single-venue form pairs a short perp with a spot long. The hard part is telling a spread you can keep from one that vanishes by tomorrow. The scanner below ranks every core perpetual on both venues, puts the rates in one unit, finds the widest spreads, checks you can trade them, and tests whether each spread lasted. Every number is from a live run on 8 November 2026.
Short answer
- To find it, read Hyperliquid's
metaAndAssetCtxsand Lighter'sfunding-rates, match the same perpetuals, put both rates in one unit and rank by spread. Then check that both books can take your size and that the spread lasted. Both venues settle funding every hour. One scan of every core perpetual needs 3 free requests (0.51 seconds). On 8 November 2026 at 16:29 UTC, 97 markets were listed on both, and 33 of them differed by more than 5% annualized. - A wide spread today is rarely a spread you can keep. LIT showed 59.8% annualized and collected 3.3% annualized in funding over the last 15 days of a 30-day window. XMR showed 47.0% and collected 19.6%.
- Only 21 of the 97 shared markets have at least $1M of open interest on both venues. Lighter is the thin side for all 21.
- The free native APIs are enough for the live scan and for settled funding history. 0xArchive adds past order books by timestamp, which neither venue's API serves, and hourly open interest for both venues in one request each.
Why build this on Hyperliquid and Lighter
Both venues publish funding, open interest and order books openly, and both pay funding every hour. A perpetual future has no expiry, so the funding payment is the price of holding the position. When two venues price that payment differently, you can hold the long on the cheaper venue and the short on the dearer one and collect the difference. The data to find, size and check that trade is public on both sides.
Step 1: put funding in one unit
Funding looks different on every endpoint. We checked each against its documentation and against the other sources on 8 November 2026.
| Source | BTC value | Unit |
|---|---|---|
Hyperliquid metaAndAssetCtxs funding | 0.0000125 | decimal per hour |
Hyperliquid fundingHistory fundingRate | hourly rows | decimal per hour, settled |
Lighter /api/v1/funding-rates | 9.6e-05 | decimal per 8 hours (eight times the hourly rate) |
Lighter /api/v1/fundings and WebSocket current_funding_rate | 0.0012 | percent per hour |
0xArchive /v1/lighter/funding/BTC, /v1/hyperliquid/funding/BTC | 0.000012 | decimal per hour |
Hyperliquid's docs say funding is computed as an 8-hour rate and paid every hour at one eighth of it. Lighter's docs say the same: the hour's average premium is divided by 8. So both venues pay hourly, and the cleanest common unit is a decimal per hour. We multiply by 8,760 for an annualized figure. Two checks back this up. Hyperliquid's hourly 0.0000125 times 8 gives 0.0001, the value Lighter's funding-rates reports for Hyperliquid BTC. Lighter's BTC 0.0012 percent per hour is 0.000012, which is 9.6e-05 over 8 hours.
The baseline matters when you read the ranking. Hyperliquid's interest component is 0.01% per 8 hours, which is 10.95% a year as simple interest (times 8,760). Hyperliquid's docs quote it as 11.6% APR, the compounded figure. Most markets sit on it: 70 of the 97 shared markets showed a Hyperliquid rate at that baseline, and 71 showed Lighter at about 10.5%.
| Funding rule | Hyperliquid | Lighter |
|---|---|---|
| Paid | Every hour | Every hour |
| Rate | Premium plus interest, an 8-hour rate paid in eighths | Same structure |
| Interest baseline | 0.01% per 8 hours | 0.01% per 8 hours on most markets |
| Cap | 0.5% an hour | 4% per 8 hours (0.5% an hour) on most markets |
| Open interest field | Longs and shorts added | One side |
| Taker fee, entry tier | 0.045% | 0% on a Standard account |
Sources: the Hyperliquid and Lighter funding docs and fee schedules. Lighter publishes each market's own interest rate and caps in orderBookDetails.
Step 2: rank every market and find the spreads
Hyperliquid's metaAndAssetCtxs returns every perpetual in one call, with funding, open interest, mark price and 24-hour volume. Lighter's orderBookDetails and funding-rates do the same in two. Match markets by name; Hyperliquid's kPEPE is Lighter's 1000PEPE. Spread is Lighter's rate minus Hyperliquid's. A positive spread means short Lighter and long Hyperliquid.
"""scanner.py: rank funding on both venues, normalize to an hourly decimal, find spreads, apply a liquidity floor.
Three free native calls cover every core perpetual on both venues. Hyperliquid's builder-deployed (HIP-3) markets are listed separately and are not in this scan. No API key needed."""
import json, requests
HL = "https://api.hyperliquid.xyz/info"
LT = "https://mainnet.zklighter.elliot.ai/api/v1"
HOURS_PER_YEAR = 24 * 365
# Hyperliquid: funding is already an hourly decimal. openInterest is in coins and counts both sides, so halve it.
meta, ctxs = requests.post(HL, json={"type": "metaAndAssetCtxs"}, timeout=30).json()
hl = {}
for m, c in zip(meta["universe"], ctxs):
if m.get("isDelisted"):
continue
mark = float(c["markPx"])
hl[m["name"]] = {"hourly": float(c["funding"]), "oi_usd": float(c["openInterest"]) * mark / 2,
"vol24_usd": float(c["dayNtlVlm"])}
# Lighter: /funding-rates reports a decimal per 8 hours, so divide by 8 for hourly.
lt_rates = {r["symbol"]: r["rate"] / 8 for r in
requests.get(f"{LT}/funding-rates", timeout=30).json()["funding_rates"] if r["exchange"] == "lighter"}
lt = {}
for b in requests.get(f"{LT}/orderBookDetails", params={"filter": "perp"}, timeout=30).json()["order_book_details"]:
if b["status"] == "active" and b["symbol"] in lt_rates:
# open_interest is in coins on one side. open_interest_usd counts both sides, so skip it.
lt[b["symbol"]] = {"hourly": lt_rates[b["symbol"]], "oi_usd": float(b["open_interest"]) * float(b["mark_price"]),
"vol24_usd": float(b["daily_quote_token_volume"])}
# Names differ for 1000x contracts: Hyperliquid kPEPE is Lighter 1000PEPE.
def lighter_name(n):
return n if n in lt else ("1000" + n[1:] if n.startswith("k") and ("1000" + n[1:]) in lt else None)
def rank_venue(name, book):
ranked = sorted(book.items(), key=lambda kv: -kv[1]["hourly"])
fmt = lambda kv: f"{kv[0]} {kv[1]['hourly'] * HOURS_PER_YEAR * 100:.1f}%"
print(f"{name}: {len(ranked)} markets. Highest APR: {', '.join(fmt(x) for x in ranked[:3])}. Lowest: {', '.join(fmt(x) for x in ranked[-3:])}")
return ranked
rank_venue("Hyperliquid", hl)
rank_venue("Lighter", lt)
rows = []
for n, h in hl.items():
ln = lighter_name(n)
if ln:
l = lt[ln]
spread = l["hourly"] - h["hourly"] # positive: short Lighter, long Hyperliquid
rows.append({"coin": n, "hl_apr": h["hourly"] * HOURS_PER_YEAR * 100, "lt_apr": l["hourly"] * HOURS_PER_YEAR * 100,
"spread_apr": spread * HOURS_PER_YEAR * 100, "hl_oi": h["oi_usd"], "lt_oi": l["oi_usd"],
"hl_vol": h["vol24_usd"], "lt_vol": l["vol24_usd"]})
print(f"Hyperliquid live perps: {len(hl)} Lighter active perps: {len(lt)} matched on both: {len(rows)}")
def show(title, items, n=10):
print(f"\n{title}")
print(f"{'coin':10s}{'HL APR%':>10s}{'LT APR%':>10s}{'spread APR%':>13s}{'HL OI $M':>10s}{'LT OI $M':>10s} side")
for r in items[:n]:
side = "short LT / long HL" if r["spread_apr"] > 0 else "short HL / long LT"
print(f"{r['coin']:10s}{r['hl_apr']:10.1f}{r['lt_apr']:10.1f}{r['spread_apr']:13.1f}{r['hl_oi']/1e6:10.2f}{r['lt_oi']/1e6:10.2f} {side}")
for venue, key in (("Hyperliquid", "hl_apr"), ("Lighter", "lt_apr")):
show(f"Highest funding on {venue} (matched markets, APR %)", sorted(rows, key=lambda r: -r[key]), 5)
show("Widest spreads, no liquidity filter", sorted(rows, key=lambda r: -abs(r["spread_apr"])))
MIN_OI = 1_000_000
liquid = [r for r in rows if min(r["hl_oi"], r["lt_oi"]) >= MIN_OI]
print(f"\nMarkets with open interest >= ${MIN_OI/1e6:.0f}M on both venues: {len(liquid)} of {len(rows)}")
show("Widest spreads with the liquidity floor", sorted(liquid, key=lambda r: -abs(r["spread_apr"])))
json.dump({"rows": rows, "liquid": sorted(liquid, key=lambda r: -abs(r["spread_apr"]))}, open("scan.json", "w")) # read by verdict.pyThis is a prototype with no retry logic. It needs no API key.
Output from our run:
Hyperliquid: 179 markets. Highest APR: XMR 57.5%, USUAL 35.7%, GOAT 29.6%. Lowest: GAS -91.4%, S -99.7%, RUNE -125.3%
Lighter: 215 markets. Highest APR: CRCL 53.4%, ANSEM 50.8%, CBRS 34.2%. Lowest: AZTEC -174.3%, PRL -418.7%, H100 -716.6%
Hyperliquid live perps: 179 Lighter active perps: 215 matched on both: 97
Markets with open interest >= $1M on both venues: 21 of 97
Widest spreads with the liquidity floor
coin HL APR% LT APR% spread APR% HL OI $M LT OI $M side
LIT -66.8 -7.0 59.8 57.86 33.90 short LT / long HL
XMR 57.5 10.5 -47.0 75.76 3.58 short HL / long LT
ZEC 10.9 -24.5 -35.5 314.26 22.84 short HL / long LT
WLD -44.5 -18.4 26.2 19.98 2.64 short LT / long HL
NEAR -7.2 1.8 9.0 76.97 6.25 short LT / long HL
BNB 1.5 10.5 9.0 21.25 3.49 short LT / long HL
SOL 1.9 10.5 8.6 248.29 13.91 short LT / long HL
LINK 10.9 3.5 -7.4 26.02 1.43 short HL / long LT
ENA 5.2 10.5 5.3 30.89 2.06 short LT / long HL
WLFI 2.7 -1.8 -4.5 8.82 1.14 short HL / long LTHyperliquid listed 235 perpetuals, 56 of them delisted, which leaves 179. Lighter had 215 active perpetuals. The widest spreads without any size filter were tiny markets: AZTEC at -167.0% annualized had $0.02M of open interest on Lighter. That is why the next step is a liquidity floor.
Step 3: read the unit trap on open interest
Open interest is where the two venues disagree on units, and it changes which markets pass your filter. Hyperliquid's openInterest is in coins and counts both sides: on 9 November its BNB figure was 57,172 coins while accounts held 28,666 BNB long and 28,666 short. Halve it, then multiply by mark price. Lighter's orderBookDetails has open_interest in coins on one side and a separate open_interest_usd that counts both. For BTC the two differed by exactly 2.0 times (332.8M against 166.4M). The scanner puts both venues on one side, in dollars, so the floor means the same thing on each.
Step 4: check you can trade it
Open interest says people hold positions. It does not say you can enter. Pull the order book on each venue and walk it for your size. This step uses the 0xArchive order book route, which returns up to 20 levels a side on both venues in one shape. Hyperliquid's own l2Book returned 20 levels a side for XMR as well, and Lighter's orderBookOrders returns the live resting orders.
"""liquidity.py: can you trade it? Top-of-book spread and the cost to cross $10k of notional on each venue,
walking the 20 visible levels a side of the 0xArchive order book route, plus 30 days of hourly open interest."""
import json, os, requests
H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
API = "https://api.0xarchive.io/v1"
SIZE = 10_000 # notional in USD you want to trade on each venue
def book(venue, coin):
return requests.get(f"{API}/{venue}/orderbook/{coin}", headers=H, params={"depth": 20}, timeout=30).json()["data"]
def cross_cost_bps(levels, mid, side_sign):
"""Average price paid to fill SIZE walking the levels, in bps worse than mid. None if 20 levels are not enough."""
need, spent, got = SIZE, 0.0, 0.0
for lv in levels:
px, sz = float(lv["px"]), float(lv["sz"])
take = min(sz * px, need)
spent += take; got += take / px; need -= take
if need <= 1e-9:
avg = spent / got
return side_sign * (avg - mid) / mid * 1e4
return None
def summarize(venue, coin):
b = book(venue, coin)
bid, ask = float(b["bids"][0]["px"]), float(b["asks"][0]["px"])
mid = (bid + ask) / 2
return {"spread_bps": (ask - bid) / mid * 1e4, "buy_bps": cross_cost_bps(b["asks"], mid, +1),
"sell_bps": cross_cost_bps(b["bids"], mid, -1)}
if __name__ == "__main__":
print(f"cost to cross ${SIZE:,} in bps vs mid (None = the 20 visible levels hold less than that)")
print(f"{'coin':6s}{'HL spread':>10s}{'HL buy':>8s}{'HL sell':>8s} {'LT spread':>10s}{'LT buy':>8s}{'LT sell':>8s}")
f = lambda v: "none" if v is None else f"{v:.1f}"
for c in [r["coin"] for r in json.load(open("scan.json"))["liquid"][:10]] + ["BTC"]:
h, l = summarize("hyperliquid", c), summarize("lighter", c)
print(f"{c:6s}{h['spread_bps']:10.2f}{f(h['buy_bps']):>8s}{f(h['sell_bps']):>8s} {l['spread_bps']:10.2f}{f(l['buy_bps']):>8s}{f(l['sell_bps']):>8s}")Output from our run:
cost to cross $10,000 in bps vs mid (None = the 20 visible levels hold less than that)
coin HL spread HL buy HL sell LT spread LT buy LT sell
LIT 0.31 0.9 4.6 1.25 3.8 3.5
XMR 0.16 3.0 none 4.29 3.7 4.8
ZEC 0.79 0.4 0.9 3.62 2.3 10.6
WLD 1.85 3.2 2.5 4.93 5.4 3.6
NEAR 2.98 1.5 2.5 0.03 0.3 3.8
BNB 0.14 0.1 4.5 2.22 1.1 2.1
SOL 0.56 0.3 1.4 0.11 0.5 0.9
LINK 0.93 0.5 5.6 9.05 5.3 4.8
ENA 3.26 4.2 3.1 2.61 4.5 1.3
WLFI 2.78 4.3 6.8 2.14 11.8 2.6
BTC 0.13 0.1 0.3 0.01 0.0 0.5Cost here is the average price you would pay to fill $10,000 against the visible levels, in basis points worse than the midpoint. Most liquid markets cost 1 to 5 bps a leg. Some do not fill: none means the 20 visible levels hold less than $10,000. Hyperliquid's XMR bid side is the example. It holds about $8,000 in 20 levels, even with $76M of open interest, and the number moves within minutes. The same check a minute earlier resolved to a cost.
Visible depth also changes by the day. On 20 October at 12:00 UTC, XMR's Hyperliquid book held $59k on the bid and $43k on the ask. On 8 November at 12:00 UTC the same book held $12k and $4k. Past books come from the 0xArchive point-in-time route; neither venue's own API keeps them.
Open interest history shows whether the money behind a spread is stable. Over the 30 days to 8 November, XMR's Hyperliquid open interest ranged from $31M to $80M on one side, and its Lighter open interest from $1.9M to $4.1M. LIT's Lighter open interest held between $27.7M and $41.2M. One request per venue returns the 720 hourly rows. Note the units: 0xArchive returns Hyperliquid open interest as Hyperliquid reports it, in coins counting both sides, and Lighter open interest in dollars on one side.
Step 5: test whether the spread lasts
Today's spread is a snapshot. To see if it persists, pull settled hourly funding for both venues over 30 days, choose the trade direction using only the first 15 days, and measure what that direction earned in the last 15. Using the first half to pick the side keeps the test honest: the second half is data the choice never saw.
"""persist.py: does a spread persist? Pull 30 days of settled hourly funding for both venues from their own free APIs,
choose the trade direction from the first 15 days, and measure the carry that direction earned in the last 15 days."""
import json, statistics, time, datetime as dt, requests
HL = "https://api.hyperliquid.xyz/info"
LT = "https://mainnet.zklighter.elliot.ai/api/v1"
START = int(dt.datetime(2026, 10, 9, tzinfo=dt.timezone.utc).timestamp())
END = int(dt.datetime(2026, 11, 8, tzinfo=dt.timezone.utc).timestamp()) # 720 hourly settlements
MARKET_ID = {b["symbol"]: b["market_id"] for b in requests.get(f"{LT}/orderBooks", timeout=30).json()["order_books"]}
def hl_hourly(coin):
"""Settled hourly rates, decimal. fundingHistory returns at most 500 rows, so page on the last timestamp."""
out, cur = {}, START * 1000
while True:
rows = requests.post(HL, json={"type": "fundingHistory", "coin": coin, "startTime": cur,
"endTime": END * 1000}, timeout=30).json()
if not rows:
return out
for r in rows:
out[dt.datetime.fromtimestamp(r["time"] / 1000, dt.timezone.utc).strftime("%m-%d %H")] = float(r["fundingRate"])
cur = rows[-1]["time"] + 1
def lt_hourly(coin):
"""Settled hourly rates. Lighter reports percent per hour plus a direction; convert to a signed decimal."""
rows = requests.get(f"{LT}/fundings", timeout=30, params={
"market_id": MARKET_ID[coin], "resolution": "1h", "start_timestamp": START,
"end_timestamp": END, "count_back": 0}).json()["fundings"]
return {dt.datetime.fromtimestamp(r["timestamp"], dt.timezone.utc).strftime("%m-%d %H"):
float(r["rate"]) / 100 * (1 if r["direction"] == "long" else -1) for r in rows}
def stats(coin, lighter_coin=None):
hl, lt = hl_hourly(coin), lt_hourly(lighter_coin or coin)
hours = sorted(set(hl) & set(lt), key=lambda h: (h[:5], h[6:]))
spread = [lt[h] - hl[h] for h in hours] # positive: short Lighter, long Hyperliquid
half = len(spread) // 2
sign = 1 if statistics.mean(spread[:half]) >= 0 else -1 # direction chosen from the first half only
hold = [sign * s for s in spread[half:]]
run, longest = 0, 0
for s in hold:
run = run + 1 if s > 0 else 0
longest = max(longest, run)
return {"coin": coin, "hours": len(hours), "dir": "short LT / long HL" if sign > 0 else "short HL / long LT",
"form_apr": sign * statistics.mean(spread[:half]) * 8760 * 100,
"hold_apr": statistics.mean(hold) * 8760 * 100,
"hold_pos_pct": 100 * sum(s > 0 for s in hold) / len(hold),
"hold_carry_bps": sum(hold) * 1e4, "longest_run_h": longest}
if __name__ == "__main__":
print(f"{'coin':6s}{'hours':>6s} {'direction':20s}{'first-15d APR%':>15s}{'last-15d APR%':>14s}{'hours positive':>15s}{'carry bps':>10s}{'longest run h':>14s}")
for c in [r["coin"] for r in json.load(open("scan.json"))["liquid"][:10]] + ["BTC"]: # BTC as a control
s = stats(c)
time.sleep(1) # Hyperliquid returns HTTP 429 if info requests come too fast
print(f"{s['coin']:6s}{s['hours']:6d} {s['dir']:20s}{s['form_apr']:15.1f}{s['hold_apr']:14.1f}{s['hold_pos_pct']:14.0f}%{s['hold_carry_bps']:10.1f}{s['longest_run_h']:14d}")Output from our run:
coin hours direction first-15d APR% last-15d APR% hours positive carry bps longest run h
LIT 719 short LT / long HL 4.3 3.3 31% 13.7 11
XMR 719 short HL / long LT 7.3 19.6 88% 80.4 58
ZEC 719 short HL / long LT 0.8 2.8 93% 11.6 221
WLD 719 short LT / long HL 3.0 5.6 42% 23.0 21
NEAR 719 short HL / long LT 0.1 0.2 82% 1.0 65
BNB 719 short LT / long HL 0.3 0.2 25% 0.7 11
SOL 719 short HL / long LT 2.4 -1.9 67% -7.7 41
LINK 719 short HL / long LT 6.6 7.1 89% 29.0 90
ENA 719 short HL / long LT 3.6 2.5 96% 10.2 159
WLFI 719 short LT / long HL 2.1 4.7 38% 19.4 26
BTC 719 short HL / long LT 0.6 -0.2 90% -0.8 114Read the second APR column against the first. LIT looked like a 60% trade on 8 November and had earned 3.3% annualized in the second half, with the favorable direction in only 31% of hours. XMR went the other way: 7.3% annualized in the first half, 19.6% in the second, favorable in 88% of hours, with a run of 58 consecutive hours. ZEC was steady but small: favorable in 93% of hours, 2.8% annualized. BTC, the control, earned nothing (-0.2%).
Step 6: net the carry against the cost to trade
The last step joins the two tests: carry earned per day against the cost to open and close both legs at your size. The result is the number of days of carry that repay the cost. The cost is before trading fees: net carry is the carry minus that cost minus the fees on all four fills. Add Hyperliquid's fee for your tier (see its fee schedule) and Lighter's, which orderBooks lists per market.
"""verdict.py: put it together. For each candidate: carry earned in the last 15 days (persist.py), the cost to open and close both legs
(liquidity.py, four crossings of the visible book), and how many days of that carry repay the cost. Fees are excluded: add your own."""
import json, time
from persist import stats
from liquidity import summarize, SIZE
COINS = [r["coin"] for r in json.load(open("scan.json"))["liquid"][:10]] # ten widest spreads that passed the liquidity floor
print(f"trade size ${SIZE:,} a leg; cost = open + close both legs, bps of notional per leg, before trading fees")
print(f"{'coin':6s}{'carry bps/15d':>14s}{'carry bps/day':>14s}{'cost bps':>10s}{'days to repay':>14s}")
for c in COINS:
s = stats(c); time.sleep(1)
h, l = summarize("hyperliquid", c), summarize("lighter", c)
legs = [h["buy_bps"], h["sell_bps"], l["buy_bps"], l["sell_bps"]]
per_day = s["hold_carry_bps"] / 15
if None in legs:
print(f"{c:6s}{s['hold_carry_bps']:14.1f}{per_day:14.1f}{'depth<size':>10s}{'n/a':>14s}")
continue
cost = sum(legs)
days = cost / per_day if per_day > 0 else float("inf")
print(f"{c:6s}{s['hold_carry_bps']:14.1f}{per_day:14.1f}{cost:10.1f}{days:14.1f}")Output from our run:
trade size $10,000 a leg; cost = open + close both legs, bps of notional per leg, before trading fees
coin carry bps/15d carry bps/day cost bps days to repay
LIT 13.7 0.9depth<size n/a
XMR 80.4 5.4 11.3 2.1
ZEC 11.6 0.8 1.7 2.2
WLD 23.0 1.5 10.8 7.1
NEAR 1.0 0.1 5.3 81.0
BNB 0.7 0.0 4.9 111.9
SOL -7.7 -0.5 0.8 inf
LINK 29.0 1.9 8.7 4.5
ENA 10.2 0.7 9.1 13.5
WLFI 19.4 1.3 27.6 21.3At the last-15-day carry, XMR repays the cost of crossing both books in and out in about 2.1 days, before fees. ZEC takes 2.2 days but earns 0.8 bps a day, so it pays little in absolute terms. LINK takes 4.5 days. NEAR, BNB and SOL do not pay back: their carry is close to zero or negative. LIT's books could not fill $10,000 on one venue at that moment, and its carry is 0.9 bps a day, so it fails on both counts.
Side by side: the same jobs on each venue's own API and on 0xArchive
We ran each job on 8 November 2026.
| Job | Venue APIs (free, no key) | 0xArchive |
|---|---|---|
| Current funding for the 97 shared markets | 3 requests, 0.51 s | 194 requests (2 per market), 22.4 s |
| 30 days of settled hourly funding, one market | Hyperliquid 2 requests (500 rows a page). Lighter 1 request of data, 750 rows a page | 1 request per venue, up to 1,000 rows. Lighter rows match the settled values; Hyperliquid rows are rate observations (see below) |
| Full BTC funding history, Hyperliquid | 61 requests, plus 4 retried after HTTP 429, 30,071 rows from 12 May 2023, 48 s | 31 requests, 30,145 rows from 20 May 2023, 4.0 s |
| Full BTC funding history, Lighter | 23 requests, 15,849 rows from 17 Jan 2025, 29 s | 11 requests, 10,084 rows from 25 Aug 2025, 2.7 s |
| Hourly open interest, 30 days | Hyperliquid: no history call. Lighter: one free daily file per day in its public bucket, every market | 1 request per venue, 720 rows |
| Order book at a past time | Current book only | Point-in-time route, tested at 20 Oct and 8 Nov 2026 |
For the live scan and for settled funding history, the native APIs are enough, and on Hyperliquid and Lighter they go back further than 0xArchive's funding rows (12 May 2023 against 20 May 2023, and 17 January 2025 against 25 August 2025). Use them. The first Hyperliquid rows are eight hours apart, from before hourly funding. The trade-off is request count and pacing: Hyperliquid answered HTTP 429 with a body of null when we paged fast, and its docs give info requests a default weight of 20 against a limit of 1,200 a minute, with extra weight per 20 rows returned by fundingHistory.
Three things about 0xArchive funding rows matter for a scanner:
- Hyperliquid 1-hour rows are observations, not settlements. Compared with settled
fundingHistoryvalues for 30 days, the mean absolute difference was 1.65e-05 for XMR and 6.8e-06 for LIT per hour, up to 1.64e-04. The raw route returns about one sample a minute (119 rows in a 2-hour window) with thepremiumfield, which shows where the running estimate is heading before the hour settles. Use nativefundingHistoryfor what was paid. - Lighter 1-hour rows match settled values. Mean absolute difference from Lighter's own history: 9e-11 for XMR and 2.7e-10 for LIT. 0xArchive also keeps the full precision; Lighter's history rounds to four decimals of a percent.
- Lighter
/currentis the last settled payment, not the estimate for the next one. For six markets we compared it with Lighter's WebSocketfunding_rate(last paid) andcurrent_funding_rate(estimate). It matched the first. Across the 97 shared markets it equalled Lighter'sfunding-ratesestimate to within 1e-9 in 65, and differed in 32. For a live scan, use the estimate.
What to show
A table with one row per market and these columns: Hyperliquid and Lighter annualized funding, spread, open interest on each side, the cost to enter, and the carry earned over the last 15 days. Sort by spread and grey out any row that fails the liquidity floor. Add the cumulative carry chart for the top three. On our run the answer was short: of 97 shared markets, 21 passed the floor, and of the ten widest of those, three repaid their cost in under five days (XMR, ZEC and LINK).
What to know before you build
- Not risk-free. The carry is the funding spread only. Prices can move between venues, either leg can be liquidated, and a spread can flip sign. Open both legs together and confirm both filled: one unfilled leg is a plain directional bet. Collateral on one venue cannot cover margin on the other, so size each leg against its own venue's margin.
- Delisted markets. 56 of Hyperliquid's 235 listed perpetuals are delisted. Skip anything with
isDelistedset. On Lighter, filter onstatusequal to active. - Name mapping. 1000x contracts are named differently (
kPEPEand1000PEPE). Check matches by hand once. - Cost excludes fees. Add trading fees on both venues. At entry tiers, opening and closing with market orders costs 9 bps on Hyperliquid (0.045% twice) and nothing on a Lighter Standard account.
- Visible depth only. Twenty levels a side is a floor on what is resting, not the whole book.
- Gaps in hourly series. BTC's hourly funding on 0xArchive had 301 missing hourly slots across 3.5 years, the largest 9 hours in August 2024. Check for holes before you compute averages.
- Free plan reach. The 30-day window used here fits the Free plan (most recent 30 days, 30-day request span). Longer windows need Build or above. See pricing.
- Pace the native calls. Hyperliquid's info endpoint rate-limits by weight. Sleep between history pages and retry on 429.
FAQ
Is Hyperliquid funding hourly or 8-hourly?
Hourly. Hyperliquid computes an 8-hour rate and pays one eighth of it every hour. Lighter does the same, so both pay hourly.
What are Hyperliquid's funding rates?
Hyperliquid pays funding every hour. With no premium the rate sits at 0.00125% an hour (0.01% per 8 hours: 10.95% a year simple, or 11.6% compounded, the figure Hyperliquid's docs quote), and it is capped at 0.5% an hour. The current rate for every market is in metaAndAssetCtxs and settled history in fundingHistory.
How do I compare Hyperliquid and Lighter funding rates?
Convert both to a decimal per hour. Lighter's funding-rates endpoint is per 8 hours (divide by 8) and its fundings endpoint is in percent (divide by 100). Multiply by 8,760 to annualize.
Is Hyperliquid's open interest one-sided or two-sided?
Two-sided. Hyperliquid's openInterest adds longs and shorts, so halve it before comparing with Lighter's open_interest, which counts one side.
Where can I get historical Hyperliquid and Lighter funding data?
Each venue's own API serves settled hourly funding: Hyperliquid back to May 2023 and Lighter back to January 2025 for BTC. 0xArchive serves both in one schema. See funding data.
Is funding rate arbitrage profitable?
Not by default. On 8 November 2026, 97 markets were shared and 21 were liquid on both venues. At the carry they collected, three of the ten widest liquid spreads would have repaid the cost of crossing both books within five days, before fees. Most of the others did not keep their spread.
Next steps
- A companion BTC notebook with spread bands and carry charts: funding-rate-scanner on GitHub.
- Lighter's own API in full: Lighter API guide. Full venue comparisons: Hyperliquid and Lighter.
- Data behind this scanner: funding, open interest and L2 order books, with the funding API reference.
- Venue pages: Hyperliquid data and Lighter data. Check pricing for history reach.
Tested on 8 November 2026. All code here is prototype code, and live numbers move by the minute.