Created
April 12, 2026 05:47
-
-
Save harishkotra/81311273b7fef7624c3157c7364f97e6 to your computer and use it in GitHub Desktop.
Ultra low-latency SOL PnL solver using getTransactionsForAddress (RPC-only, no indexing)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * SOL PnL Ultra Solver (single-file submission) | |
| * RPC-only, no indexing, optimized for low latency on getTransactionsForAddress. | |
| * | |
| * Run: | |
| * RPC_ENDPOINTS="https://rpc1,https://rpc2" \ | |
| * TARGET_ADDRESS="wallet_pubkey" \ | |
| * START_SLOT=0 \ | |
| * END_SLOT=350000000 \ | |
| * PROBE_CONCURRENCY=24 \ | |
| * FULL_CONCURRENCY=32 \ | |
| * SEED_BUCKETS=96 \ | |
| * MIN_PROBE_SPAN=2048 \ | |
| * MAX_PROBE_DEPTH=24 \ | |
| * node --experimental-strip-types sol_pnl_ultra_single.ts | |
| */ | |
| type JsonRpcRequest = { | |
| jsonrpc: "2.0"; | |
| id: number; | |
| method: string; | |
| params: unknown[]; | |
| }; | |
| type JsonRpcSuccess<T> = { | |
| jsonrpc: "2.0"; | |
| id: number; | |
| result: T; | |
| }; | |
| type JsonRpcFailure = { | |
| jsonrpc: "2.0"; | |
| id: number; | |
| error: { code: number; message: string; data?: unknown }; | |
| }; | |
| type SignatureTx = { | |
| slot: number; | |
| signature: string; | |
| }; | |
| type FullTx = { | |
| slot: number; | |
| signature: string; | |
| transaction?: { | |
| message?: { | |
| accountKeys?: Array<string | { pubkey: string }>; | |
| }; | |
| }; | |
| meta?: { | |
| preBalances?: number[]; | |
| postBalances?: number[]; | |
| err?: unknown; | |
| }; | |
| }; | |
| type GtfaResult<T> = { | |
| data?: T[]; | |
| paginationToken?: string | null; | |
| }; | |
| type SlotRange = { | |
| startSlot: number; | |
| endSlot: number; | |
| depth: number; | |
| }; | |
| type EndpointState = { | |
| url: string; | |
| inflight: number; | |
| ewmaMs: number; | |
| }; | |
| class RpcPool { | |
| private nextId = 1; | |
| private readonly endpoints: EndpointState[]; | |
| constructor(rawEndpoints: string[]) { | |
| const urls = rawEndpoints.map((x) => x.trim()).filter(Boolean); | |
| if (!urls.length) throw new Error("No RPC endpoints provided"); | |
| this.endpoints = urls.map((url) => ({ url, inflight: 0, ewmaMs: 300 })); | |
| } | |
| private chooseEndpoint(exclude?: Set<string>): EndpointState { | |
| let best: EndpointState | undefined; | |
| let bestScore = Number.POSITIVE_INFINITY; | |
| for (const ep of this.endpoints) { | |
| if (exclude?.has(ep.url)) continue; | |
| const score = ep.ewmaMs * (ep.inflight + 1); | |
| if (score < bestScore) { | |
| bestScore = score; | |
| best = ep; | |
| } | |
| } | |
| if (!best) throw new Error("No endpoint available"); | |
| return best; | |
| } | |
| private async post<T>(ep: EndpointState, body: unknown): Promise<T> { | |
| const t0 = performance.now(); | |
| ep.inflight += 1; | |
| try { | |
| const res = await fetch(ep.url, { | |
| method: "POST", | |
| headers: { "content-type": "application/json" }, | |
| body: JSON.stringify(body), | |
| }); | |
| const json = (await res.json()) as T; | |
| const dt = performance.now() - t0; | |
| ep.ewmaMs = ep.ewmaMs * 0.8 + dt * 0.2; | |
| return json; | |
| } finally { | |
| ep.inflight -= 1; | |
| } | |
| } | |
| async call<T>(method: string, params: unknown[], attempts = 3): Promise<T> { | |
| const tried = new Set<string>(); | |
| let lastErr: unknown; | |
| for (let i = 0; i < attempts; i++) { | |
| const ep = this.chooseEndpoint(tried); | |
| tried.add(ep.url); | |
| const req: JsonRpcRequest = { | |
| jsonrpc: "2.0", | |
| id: this.nextId++, | |
| method, | |
| params, | |
| }; | |
| try { | |
| const json = await this.post<JsonRpcSuccess<T> | JsonRpcFailure>(ep, req); | |
| if ("error" in json) throw new Error(`${json.error.code}: ${json.error.message}`); | |
| return json.result; | |
| } catch (err) { | |
| lastErr = err; | |
| } | |
| } | |
| throw lastErr instanceof Error ? lastErr : new Error(String(lastErr)); | |
| } | |
| async callBatch<T>(calls: Array<{ method: string; params: unknown[] }>, attempts = 3): Promise<T[]> { | |
| const tried = new Set<string>(); | |
| let lastErr: unknown; | |
| for (let i = 0; i < attempts; i++) { | |
| const ep = this.chooseEndpoint(tried); | |
| tried.add(ep.url); | |
| const batch: JsonRpcRequest[] = calls.map((c) => ({ | |
| jsonrpc: "2.0", | |
| id: this.nextId++, | |
| method: c.method, | |
| params: c.params, | |
| })); | |
| try { | |
| const json = await this.post<Array<JsonRpcSuccess<T> | JsonRpcFailure>>(ep, batch); | |
| const byId = new Map<number, JsonRpcSuccess<T> | JsonRpcFailure>(); | |
| for (const r of json) byId.set(r.id, r); | |
| return batch.map((req) => { | |
| const item = byId.get(req.id); | |
| if (!item) throw new Error(`Missing batch response for id=${req.id}`); | |
| if ("error" in item) throw new Error(`${item.error.code}: ${item.error.message}`); | |
| return item.result; | |
| }); | |
| } catch (err) { | |
| lastErr = err; | |
| } | |
| } | |
| throw lastErr instanceof Error ? lastErr : new Error(String(lastErr)); | |
| } | |
| } | |
| class SolPnlUltraSolver { | |
| private readonly rpc: RpcPool; | |
| private readonly address: string; | |
| private readonly startSlot: number; | |
| private readonly endSlot: number; | |
| private readonly probeConcurrency: number; | |
| private readonly fullConcurrency: number; | |
| private readonly probeLimit: number; | |
| private readonly fullLimit: number; | |
| private readonly minProbeSpan: number; | |
| private readonly maxProbeDepth: number; | |
| private readonly seedBuckets: number; | |
| constructor( | |
| rpc: RpcPool, | |
| address: string, | |
| cfg: { | |
| startSlot: number; | |
| endSlot: number; | |
| probeConcurrency: number; | |
| fullConcurrency: number; | |
| probeLimit: number; | |
| fullLimit: number; | |
| minProbeSpan: number; | |
| maxProbeDepth: number; | |
| seedBuckets: number; | |
| } | |
| ) { | |
| this.rpc = rpc; | |
| this.address = address; | |
| this.startSlot = cfg.startSlot; | |
| this.endSlot = cfg.endSlot; | |
| this.probeConcurrency = cfg.probeConcurrency; | |
| this.fullConcurrency = cfg.fullConcurrency; | |
| this.probeLimit = cfg.probeLimit; | |
| this.fullLimit = cfg.fullLimit; | |
| this.minProbeSpan = cfg.minProbeSpan; | |
| this.maxProbeDepth = cfg.maxProbeDepth; | |
| this.seedBuckets = cfg.seedBuckets; | |
| } | |
| private signaturesQuery(range: SlotRange, sortOrder: "asc" | "desc") { | |
| return { | |
| transactionDetails: "signatures", | |
| sortOrder, | |
| limit: this.probeLimit, | |
| filters: { slot: { gte: range.startSlot, lte: range.endSlot } }, | |
| }; | |
| } | |
| private fullQuery(range: SlotRange, paginationToken?: string) { | |
| return { | |
| transactionDetails: "full", | |
| sortOrder: "asc", | |
| limit: this.fullLimit, | |
| encoding: "json", | |
| maxSupportedTransactionVersion: 0, | |
| filters: { slot: { gte: range.startSlot, lte: range.endSlot } }, | |
| ...(paginationToken ? { paginationToken } : {}), | |
| }; | |
| } | |
| private static splitRange(range: SlotRange, parts: 2 | 4): SlotRange[] { | |
| if (parts === 2) { | |
| const mid = Math.floor((range.startSlot + range.endSlot) / 2); | |
| return [ | |
| { startSlot: range.startSlot, endSlot: mid, depth: range.depth + 1 }, | |
| { startSlot: mid + 1, endSlot: range.endSlot, depth: range.depth + 1 }, | |
| ]; | |
| } | |
| const span = range.endSlot - range.startSlot + 1; | |
| const q = Math.max(1, Math.floor(span / 4)); | |
| const a = range.startSlot; | |
| const b = Math.min(range.endSlot, a + q - 1); | |
| const c = Math.min(range.endSlot, b + q); | |
| const d = Math.min(range.endSlot, c + q); | |
| return [ | |
| { startSlot: a, endSlot: b, depth: range.depth + 1 }, | |
| { startSlot: b + 1, endSlot: c, depth: range.depth + 1 }, | |
| { startSlot: c + 1, endSlot: d, depth: range.depth + 1 }, | |
| { startSlot: d + 1, endSlot: range.endSlot, depth: range.depth + 1 }, | |
| ].filter((r) => r.startSlot <= r.endSlot); | |
| } | |
| private async findActiveWindow(): Promise<{ activeStart: number; activeEnd: number } | null> { | |
| const range: SlotRange = { startSlot: this.startSlot, endSlot: this.endSlot, depth: 0 }; | |
| const [first, last] = await Promise.all([ | |
| this.rpc.call<GtfaResult<SignatureTx>>("getTransactionsForAddress", [ | |
| this.address, | |
| { ...this.signaturesQuery(range, "asc"), limit: 1 }, | |
| ]), | |
| this.rpc.call<GtfaResult<SignatureTx>>("getTransactionsForAddress", [ | |
| this.address, | |
| { ...this.signaturesQuery(range, "desc"), limit: 1 }, | |
| ]), | |
| ]); | |
| const firstSlot = first.data?.[0]?.slot; | |
| const lastSlot = last.data?.[0]?.slot; | |
| if (typeof firstSlot !== "number" || typeof lastSlot !== "number") return null; | |
| return { activeStart: firstSlot, activeEnd: lastSlot }; | |
| } | |
| private initialRanges(activeStart: number, activeEnd: number): SlotRange[] { | |
| const total = activeEnd - activeStart + 1; | |
| const size = Math.max(1, Math.floor(total / Math.max(1, this.seedBuckets))); | |
| const out: SlotRange[] = []; | |
| for (let s = activeStart; s <= activeEnd; ) { | |
| const e = Math.min(activeEnd, s + size - 1); | |
| out.push({ startSlot: s, endSlot: e, depth: 0 }); | |
| s = e + 1; | |
| } | |
| return out; | |
| } | |
| private async partition(activeStart: number, activeEnd: number): Promise<{ leaves: SlotRange[]; probeCalls: number }> { | |
| const queue = this.initialRanges(activeStart, activeEnd); | |
| const leaves: SlotRange[] = []; | |
| let probeCalls = 0; | |
| while (queue.length) { | |
| const batch = queue.splice(0, this.probeConcurrency); | |
| const calls = batch.map((r) => ({ | |
| method: "getTransactionsForAddress", | |
| params: [this.address, this.signaturesQuery(r, "asc")], | |
| })); | |
| const res = await this.rpc.callBatch<GtfaResult<SignatureTx>>(calls); | |
| probeCalls += batch.length; | |
| for (let i = 0; i < batch.length; i++) { | |
| const range = batch[i]; | |
| const rr = res[i]; | |
| const count = rr.data?.length ?? 0; | |
| const saturated = count >= this.probeLimit || !!rr.paginationToken; | |
| const span = range.endSlot - range.startSlot + 1; | |
| if (!saturated) { | |
| if (count > 0) leaves.push(range); | |
| continue; | |
| } | |
| if (range.depth >= this.maxProbeDepth || span <= this.minProbeSpan) { | |
| leaves.push(range); | |
| continue; | |
| } | |
| const parts: 2 | 4 = span > this.minProbeSpan * 8 ? 4 : 2; | |
| queue.push(...SolPnlUltraSolver.splitRange(range, parts)); | |
| } | |
| } | |
| return { leaves, probeCalls }; | |
| } | |
| private async fetchRangeFull(range: SlotRange): Promise<{ txs: FullTx[]; calls: number }> { | |
| const out: FullTx[] = []; | |
| let calls = 0; | |
| let token: string | undefined; | |
| while (true) { | |
| const page = await this.rpc.call<GtfaResult<FullTx>>("getTransactionsForAddress", [ | |
| this.address, | |
| this.fullQuery(range, token), | |
| ]); | |
| calls += 1; | |
| const data = page.data ?? []; | |
| if (!data.length) break; | |
| out.push(...data); | |
| if (!page.paginationToken) break; | |
| token = page.paginationToken; | |
| } | |
| return { txs: out, calls }; | |
| } | |
| private async fetchAll(leaves: SlotRange[]): Promise<{ txs: FullTx[]; fullCalls: number }> { | |
| const queue = [...leaves]; | |
| const out: FullTx[] = []; | |
| let fullCalls = 0; | |
| const workers = Array.from({ length: Math.max(1, this.fullConcurrency) }, async () => { | |
| while (true) { | |
| const range = queue.pop(); | |
| if (!range) break; | |
| const r = await this.fetchRangeFull(range); | |
| out.push(...r.txs); | |
| fullCalls += r.calls; | |
| } | |
| }); | |
| await Promise.all(workers); | |
| return { txs: out, fullCalls }; | |
| } | |
| private computePnlLamports(txs: FullTx[]): { pnlLamports: bigint; successCount: number; failedCount: number } { | |
| let pnlLamports = 0n; | |
| let successCount = 0; | |
| let failedCount = 0; | |
| for (const tx of txs) { | |
| const keys = tx.transaction?.message?.accountKeys ?? []; | |
| const idx = keys.findIndex((k) => (typeof k === "string" ? k : k?.pubkey) === this.address); | |
| if (idx < 0) continue; | |
| const pre = tx.meta?.preBalances?.[idx]; | |
| const post = tx.meta?.postBalances?.[idx]; | |
| if (typeof pre !== "number" || typeof post !== "number") continue; | |
| pnlLamports += BigInt(post - pre); | |
| if (tx.meta?.err) failedCount++; | |
| else successCount++; | |
| } | |
| return { pnlLamports, successCount, failedCount }; | |
| } | |
| async run() { | |
| const t0 = performance.now(); | |
| const active = await this.findActiveWindow(); | |
| if (!active) { | |
| return { | |
| address: this.address, | |
| slotWindow: { requestedStart: this.startSlot, requestedEnd: this.endSlot, activeStart: null, activeEnd: null }, | |
| txCount: 0, | |
| pnlLamports: "0", | |
| pnlSOL: 0, | |
| successCount: 0, | |
| failedCount: 0, | |
| perf: { totalMs: Number((performance.now() - t0).toFixed(2)), probeCalls: 2, fullCalls: 0, totalCalls: 2 }, | |
| }; | |
| } | |
| const { leaves, probeCalls } = await this.partition(active.activeStart, active.activeEnd); | |
| const { txs, fullCalls } = await this.fetchAll(leaves); | |
| const seen = new Set<string>(); | |
| const uniq: FullTx[] = []; | |
| for (const tx of txs) { | |
| if (!seen.has(tx.signature)) { | |
| seen.add(tx.signature); | |
| uniq.push(tx); | |
| } | |
| } | |
| uniq.sort((a, b) => a.slot - b.slot); | |
| const pnl = this.computePnlLamports(uniq); | |
| const totalMs = performance.now() - t0; | |
| return { | |
| address: this.address, | |
| slotWindow: { | |
| requestedStart: this.startSlot, | |
| requestedEnd: this.endSlot, | |
| activeStart: active.activeStart, | |
| activeEnd: active.activeEnd, | |
| }, | |
| shardCount: leaves.length, | |
| txCount: uniq.length, | |
| pnlLamports: pnl.pnlLamports.toString(), | |
| pnlSOL: Number((Number(pnl.pnlLamports) / 1_000_000_000).toFixed(9)), | |
| successCount: pnl.successCount, | |
| failedCount: pnl.failedCount, | |
| perf: { | |
| totalMs: Number(totalMs.toFixed(2)), | |
| probeCalls, | |
| fullCalls, | |
| totalCalls: probeCalls + fullCalls + 2, | |
| txPerSec: Number(((uniq.length / totalMs) * 1000).toFixed(2)), | |
| }, | |
| }; | |
| } | |
| } | |
| function intEnv(name: string, fallback: number): number { | |
| const v = Number(process.env[name] ?? String(fallback)); | |
| if (!Number.isFinite(v)) throw new Error(`Invalid number for ${name}`); | |
| return Math.floor(v); | |
| } | |
| async function main() { | |
| const endpoints = (process.env.RPC_ENDPOINTS ?? process.env.RPC_ENDPOINT ?? "") | |
| .split(",") | |
| .map((x) => x.trim()) | |
| .filter(Boolean); | |
| const address = process.env.TARGET_ADDRESS ?? ""; | |
| if (!endpoints.length || !address) { | |
| console.error("Need RPC_ENDPOINT(S) and TARGET_ADDRESS"); | |
| process.exit(1); | |
| } | |
| const startSlot = intEnv("START_SLOT", 0); | |
| const endSlot = intEnv("END_SLOT", 350_000_000); | |
| if (startSlot > endSlot) { | |
| console.error("START_SLOT must be <= END_SLOT"); | |
| process.exit(1); | |
| } | |
| const solver = new SolPnlUltraSolver(new RpcPool(endpoints), address, { | |
| startSlot, | |
| endSlot, | |
| probeConcurrency: intEnv("PROBE_CONCURRENCY", 24), | |
| fullConcurrency: intEnv("FULL_CONCURRENCY", 32), | |
| probeLimit: Math.min(1000, intEnv("PROBE_LIMIT", 1000)), | |
| fullLimit: Math.min(100, intEnv("FULL_LIMIT", 100)), | |
| minProbeSpan: intEnv("MIN_PROBE_SPAN", 2048), | |
| maxProbeDepth: intEnv("MAX_PROBE_DEPTH", 24), | |
| seedBuckets: intEnv("SEED_BUCKETS", 96), | |
| }); | |
| const out = await solver.run(); | |
| console.log(JSON.stringify(out, null, 2)); | |
| } | |
| main().catch((err) => { | |
| console.error(err); | |
| process.exit(1); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment