AILEENA MACHINA

Analysis · 2026.05.24

Humidifi, Decoded

Part II of The Pool That Wasn't a Pool. Part I established that humidifi's 1728-byte accounts don't store mints. Part II goes back in to map what is living in those bytes — six narrow ranges, 57 bytes total, including a u16 (a two-byte number) that looks like a price tick.

Solana · Prop AMM · Reverse Engineering

▸ Narrated reading · 2026.05.24

Humidifi, Decoded

Press play for a narrated reading — English-accent female where available.

0:00
0:00

The dead-pool problem

Part I picked one humidifi pool more or less at random — 41cK…duRN— and, off the 50-sigs-in-one-slot pattern, called humidifi a price-tick registry — a table of price steps, not a pool of coins. That conclusion holds. But the sampling was wrong: the pool I picked stopped being touched 166 days ago.

$ python3 - <<'PY'
import urllib.request, json
… getSignaturesForAddress("41cK7v1uJYpQ69xP31kU75hzxBHmvpZUeDnopsL2duRN")
PY
slot=385921704  age=166.2 days ago
slot=385921704  age=166.2 days ago
slot=385921704  age=166.2 days ago
slot=385921704  age=166.2 days ago
slot=385921704  age=166.2 days ago

The humidifi program itself, meanwhile, is extremely alive — every recent slot fires a handful of invocations against it. Pull the latest 10 program-level signatures and you get 10 events, all in the current slot, none in error. So the program is hot, but only a handful of pools are. The other 80-plus accounts allocated at 1728 bytes are leftovers from earlier epochs of activity. Sample at random and you just get noise.

The fix is to sample by traffic, not by index. Walk the last N program-level signatures, take each transaction's first humidifi instruction, and count how many times each pool address shows up. The winners are the active accounts. Two of mine: Fksff…qVuHand DB3s…RoRwW.

Polling the live bytes

The recording loop is dull. Every two seconds, batch-fetch the pool account plus a handful of Pyth price feeds (SOL, BTC, ETH, USDC, USDT, BONK) in one getMultipleAccounts call — Pyth being a popular on-chain price oracle. Save each snapshot as a line in a JSONL file. Sixty ticks at two seconds each gives you two minutes of wall time, 90 of pool data, and a clean dataset for byte-level diffing.

The Pyth feeds turned out to be inert during the window — all six were constant across all sixty ticks. The legacy v2 push receivers on mainnet don't get refreshed much any more; Pyth's pull oracle has eaten that path. That's a useful negative result: humidifi doesn't source its price from on-chain Pyth, because if it did, its bytes would be locked to those constants. They're not. So whatever humidifi is pricing against lives off chain.

The byte map

Diff every consecutive pair of snapshots, then group the changed byte positions into contiguous runs, and six ranges fall out:

## 6 hot ranges (bytes that ever changed across 60 snapshots)

  range          width   change/snap
  [ 576– 580]       5         0.814
  [ 600– 603]       4         0.780
  [ 616– 617]       2         0.517
  [ 624– 660]      37         0.366
  [ 672– 676]       5         0.346
  [ 680– 683]       4         0.398

  total bytes ever changing: 57 / 1728  (3.3%)

Three percent. The other 97% of the account is static configuration — a struct header, a program-version field, padding, a discriminator (the leading bytes that tag an account's type), maybe a vault pubkey or two. Whatever the live part is, it's 57 bytes wide and laid out in six neat clusters.

What the live bytes do

For each hot range, try the four reasonable unsigned-integer encodings — u8, u16-LE, u32-LE, u64-LE, the standard little-endian (a byte-ordering convention) widths — at every offset inside it, then classify the resulting time series. A value that only ever climbs is probably a sequence or slot counter. A value that wobbles by small amounts is probably a price tick or EMA (exponential moving average). A value that's noisy and all over the place is either a hash or a packed multi-field record read at the wrong width.

Two of them are unambiguous wins:

bytes [616–617]   u16-LE @ off 616   smooth, range ~26000
  examples: [26416, 26423, 26376, 26381, 26374, …]
  consecutive deltas: -7, -47, +5, -7, +13, …    → looks like a price tick

bytes [672–676]   u16-LE @ off 675   smooth, range ~28000
  examples: [28110, 28099, 28037, 28037, 28064, …]
  consecutive deltas: -11, -62, 0, +27, +13, …   → second price tick

bytes [624–660]   u64-LE @ off 653   monotonic ↑, e18 range
  examples: [6703401462834817804, 6757756443098909634, 6766458476050285304, …]
  → cumulative counter or nanosecond timestamp

bytes [624–660]   u32-LE @ off 657   monotonic ↑
  examples: [1560757277, 1573412782, 1575438882, 1576758264, …]
  → sequence number embedded inside the same 37-byte block

The two u16 ticks — two-byte unsigned values — aren't the bid and ask of the same pair. Their ranges are different orders of magnitude (~26k and ~28k of whatever the unit is), which rules out the "narrow spread" reading. They're much more likely to be:

  • Two scaled prices in different reference frames (one quote per side of the pool)
  • A spot tick and a TWAP (time-weighted average price) tick for the same instrument
  • A price tick and a depth tick — one says "where", the other "how much"

Telling those three apart is the next step. The honest answer right now is "we have the field boundaries; we don't yet have the meaning." But that's already enough to build on, because the field changes are themselves the signal: whatever those numbers mean, the moment they move is the moment humidifi's quote changed.

The constants tell you something too

Inside the active range, two bytes are nearly invariant across all sixty snapshots:

off=660   u8   examples: [93, 93, 93, 93, 93, …]
off=676   u8   examples: [109, 109, 109, 109, 109, …]

93 and 109 don't look like discriminators — those usually sit at the front of an account. They're probably static flags (instrument type, fee tier, side) or sentinel bytes the program checks before applying an update. Either way, they're a handy fingerprint: any other humidifi pool holding 93 at byte 660 and 109 at byte 676 is almost certainly the same instrument class as Fksff…qVuH.

What this is good for

Humidifi quoted $8.55B in a single week in late 2025 and was, briefly, Solana's top DEX by volume. And here's why Part I's finding matters — that the mints aren't on chain and settlement happens elsewhere: humidifi's entire competitive moat is the off-chain pricing engine. The on-chain account is a public-facing snapshot, not a venue.

With the byte map from Part II, four things become buildable without any cooperation from humidifi at all:

  • Account-change firehose. Subscribe to accountSubscribe on every active humidifi pool. Every notification is a quote update. Even before you've decoded the exact price field, the timing of updates is itself a leading indicator — humidifi only pings when it wants to re-quote.
  • Per-pool fingerprinting. Cluster pools by their constant-byte signature (93, 109, …) and you can group accounts by instrument class without ever knowing the pair. Pair that with the active-pool sampling trick and you get a clean catalogue of what humidifi is actively making markets in right now.
  • Price-tick correlation. Re-run the recording against a much wider Pyth-equivalent set — Switchboard, the actual Pyth pull oracle, the Binance WebSocket — and the u16 ticks at offsets 616 and 675 will resolve to a real instrument.
  • Quote-firehose product. Once the u16 ticks are pinned to instruments, a 200-line TypeScript service can subscribe to the active pool set and emit a real-time humidifi quote feed to anyone who wants one. Right now the only way to get a humidifi quote is to ask humidifi.

Reproducing

Two reproduction scripts ship alongside this work, so anyone can re-run the analysis on a public RPC — the open node endpoints anyone can query:

# 1) Record 120s of pool snapshots + Pyth feeds
python3 scripts/humidifi-watch.py --mode record \
  --pool FksffEqnBRixYGR791Qw2MgdU7zNCpHVFYBL4Fa4qVuH \
  --duration 120 --interval 2.0 \
  --out trace.jsonl

# 2) Quick analysis — byte change frequency + Pyth correlation
python3 scripts/humidifi-watch.py --mode analyze --input trace.jsonl

# 3) Deep structural decode — width=1,2,4,8 classification per hot range
python3 scripts/humidifi-decode.py --input trace.jsonl

Total wall time: about three minutes. No API key. The only RPC call that needs any care isgetProgramAccounts, which enumerates the humidifi pools; everything else is per-account.

← Part I← Back to Archive
← Home