ArchiveAILEENA MACHINA
ANALYSIS2026.05.24Solana · Prop AMM · Reverse Engineering

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 — the offsets don't exist. Part II goes back in to map what is living in those bytes. Most of the account is dead. The live part is six narrow ranges, ~57 bytes total, including a u16 that looks like a price tick.

01 — The dead-pool problem

Part I picked one humidifi pool more or less at random — 41cK…duRN— and concluded humidifi was a price-tick registry based on the 50-sigs-in-one-slot pattern. 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 is, meanwhile, extremely alive — every recent slot fires a handful of invocations against it. Pulling the latest 10 program-level signatures returns 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 residue from prior epochs of activity. Sample at random and you get noise.

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

02 — Polling the live bytes

The recording loop is dull. Every two seconds, batch-fetch the pool account and a handful of Pyth price feeds (SOL, BTC, ETH, USDC, USDT, BONK) in one getMultipleAccounts call. Persist each snapshot as a line in a JSONL file. Sixty ticks at two seconds each is 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 aren't being refreshed often any more; Pyth's pull oracle has eaten that path. Useful negative result: humidifi doesn't source price from on-chain Pyth, because if it did, its bytes would be locked to those constants. They're not. Whatever humidifi is pricing against lives off chain.

03 — The byte map

Diffing every consecutive pair of snapshots and grouping the changed byte positions into contiguous runs produces six ranges:

## 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, maybe a vault pubkey or two. Whatever the live part is, it's 57 bytes wide and laid out in six neat clusters.

04 — What the live bytes do

For each hot range, try the four reasonable u-encodings — u8, u16-LE, u32-LE, u64-LE — at every offset inside it, and classify the resulting time series. A monotonically increasing value is probably a sequence or slot counter. A bounded small-delta value is probably a price tick or EMA. A wide-spread noisy value is either a hash or a packed multi-field record read at the wrong width.

The two 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 aren't bid and ask of the same pair. Their ranges are different orders of magnitude (~26k and ~28k of whatever unit), which rules out the "narrow spread" interpretation. 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 tick for the same instrument
  • A price tick and a depth tick — one says "where", the other "how much"

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

05 — 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 appear 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 useful fingerprint: any other humidifi pool that holds 93 at byte 660 and 109 at byte 676 is almost certainly the same instrument class as Fksff…qVuH.

06 — 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. The interesting thing about Part I's finding — that mints aren't on chain and settlement happens elsewhere — is that 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:

  • Account-change firehose. Subscribe to accountSubscribe on every active humidifi pool. Every notification is a quote update. Even without decoding the exact price field yet, 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. Combined with the active-pool sampling trick, you get a clean catalogue of what humidifi is actively making markets in right now.
  • Price-tick correlation. Re-run the recording with a much wider Pyth-equivalent set — Switchboard, the actual Pyth pull oracle, 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 any consumer that wants one. Currently the only way to get a humidifi quote is to ask humidifi.

07 — Reproducing

Two scripts in pamm-a reproduce everything in this post on a public RPC:

# 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 care isgetProgramAccounts to enumerate humidifi pools; everything else is per-account.

← Part I← Back to Archive