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 agoThe 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
accountSubscribeon 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.