Skip to content

meowyx/computing-sol-algo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sol-pnl-race

Lowest-latency algorithm for computing lifetime SOL PnL of any Solana wallet using only Helius RPC. Entry for Mert's mini Solana dev weekend competition.

Contest announcement


What we built

A 4-phase adaptive pipeline that computes SOL PnL for any wallet — sparse or busy — without knowing transaction density upfront. It also outputs a full balance curve over time with peak/trough detection.

Results:

Wallet type Transactions Pipeline latency Naive baseline Speedup
Sparse ~500 2–4s ~5s ~1.5x
Busy ~34,000 ~80s ~262s 3.2x

PnL verified to the lamport against naive sequential baseline across all test wallets.


How it works

Day 1 — Measure first, build second

Before writing the algorithm, we ran experiments to understand the RPC:

  • Experiment 1 (latency curve): Measured single-call latency for sigs@[100,500,1000] and full@[10,50,100] across sparse and busy wallets. Found that sig calls are ~150ms regardless of limit, full@100 is ~565ms on busy wallets, and tail ratios go up to 2.9x.

  • Experiment 2 (concurrency knee): Fired N parallel sig calls for N=4,8,16,32,64,128. Wall-clock was flat from N=4 to N=32 (~580ms), jumped to 962ms at N=64 and 1744ms at N=128. This told us: cap concurrency at 16.

  • Naive baseline: Sequential paginated full@100 sweep. 33,392 txs took 284.6 seconds. This is the yardstick.

All experiment data is in experiment/bench-results.md.

Day 2 — The algorithm (4 phases)

Phase 1 — Discovery (1 round trip, ~700ms)

Fire two getTransactionsForAddress calls in parallel:

  • sort=ASC, limit=1000 — oldest 1000 signatures
  • sort=DESC, limit=1000 — newest 1000 signatures

One round trip tells us: the full slot range, density at both ends, and whether the wallet has ≤2000 txs (batches overlap → done). Sparse wallets are fully discovered here — no wasted calls.

Phase 2 — Gap Fill (~2s, skipped for small wallets)

Only runs when Phase 1 leaves a middle gap (>2000 txs). Partition the gap into density-adaptive chunks (density × 1.5 safety multiplier — our estimator runs ~30% low). Fire all chunks in parallel. Any chunk returning exactly 1000 sigs = overflow → recursively split in half.

Phase 3 — Full Fetch (16 parallel streams)

Key insight we discovered: pagination tokens are slot:txIndex — you can fabricate them to start from any position in history. Instead of slow slot-range filtered calls, we:

  1. Pick 16 evenly-spaced starting points from discovered signatures
  2. Fabricate pagination tokens for each position
  3. Run 16 parallel keyset-paginated streams
  4. Each stream stops when it reaches the next stream's territory

This uses Helius's fast pagination path rather than range-scan queries.

Phase 4 — PnL Extraction

For each transaction chronologically: find the wallet's index in accountKeys, compute post_balance[idx] - pre_balance[idx], accumulate. Builds a full balance curve with peak and trough detection. Uses status: any to include failed transactions (fees still count toward PnL).

What we learned

The bottleneck on busy wallets isn't the algorithm — it's server response time. Each full@100 response takes ~3 seconds to return regardless of how you request it (slot filter, pagination, batch getTransaction). On a Developer plan (50 req/s), the effective throughput is ~450 transactions/second. For 34k txs, that's a ~76s floor.

Our 3.2x speedup over naive comes from parallelizing discovery and gap fill (Phases 1-2), not from Phase 3. The naive baseline makes 342 sequential calls; we make the same number but 16-wide.


Quick start

# One-time setup
cp .env.example .env
# paste your HELIUS_API_KEY into .env

# Run the submission (full pipeline)
cargo run --release -p submission -- <wallet_address>

# Dev CLI (experiments, baselines, individual phases)
cd experiment
cargo run --release -- compute <address>     # full pipeline with dev output
cargo run --release -- discovery <address>   # Phase 1 only
cargo run --release -- baseline <address>    # naive sequential sweep
cargo run --release -- profile <address>     # classify a wallet

Dev commands use the disk cache at experiment/cache/ by default. Add --no-cache to force live calls.


Tuning constants

All derived from Phase 0 experiments, not guesses.

Constant Value Source
MAX_CONCURRENT_REQUESTS 16 E2: knee between N=32 and N=64
Phase 1 parallelism 2 calls fixed (ASC + DESC)
Phase 2 chunk target 800 sigs 1000 cap minus 20% headroom
Density safety multiplier 1.5x Phase 1 estimator runs ~30% low
Fabricated token streams 16 matches concurrency cap

Directory layout

computing-sol-algo/
├── submission/          ← the algorithm Mert runs (self-contained)
│   ├── Cargo.toml
│   ├── README.md        ← submission-specific docs
│   └── src/
│       ├── main.rs      ← CLI: takes address, prints JSON result
│       ├── lib.rs       ← module registry
│       ├── rpc.rs       ← Helius client (sigs, full, batch, fabricated pagination)
│       ├── types.rs     ← JSON-RPC structs
│       ├── config.rs    ← tuning constants
│       ├── discovery.rs ← Phase 1
│       ├── gap_fill.rs  ← Phase 2
│       ├── fetch.rs     ← Phase 3
│       ├── pipeline.rs  ← orchestration
│       └── pnl.rs       ← Phase 4 + balance curve
│
├── experiment/          ← dev sandbox (not shipped)
│   ├── bench-results.md ← E1 + E2 raw data
│   ├── baselines.json   ← ground-truth PnL per wallet
│   ├── correctness-check.md ← pipeline vs baseline verification
│   ├── wallets.txt      ← test wallet addresses
│   ├── cache/           ← gitignored record-and-replay
│   └── src/
│       ├── main.rs      ← dev CLI with all subcommands
│       ├── rpc.rs       ← Helius client + disk cache wrapper
│       ├── cache.rs     ← SHA256-keyed disk cache
│       ├── baseline.rs  ← naive sequential sweep
│       ├── experiments.rs ← E1 + E2 harnesses
│       └── (shared algorithm files)
│
├── .env.example         ← env template
└── mert.png             ← contest announcement

Correctness verification

Pipeline PnL verified against fresh naive baseline (both live, no cache, run within minutes of each other):

Wallet Txs Baseline PnL Pipeline PnL Match
6F7c... (sparse) 441 0 0 exact
Fzyeeepi... (sparse) 722 0 0 exact
BoKeXuYd... (busy, 34k) 34,066 166,580,562 166,580,562 exact

Full details in experiment/correctness-check.md.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages