fix(clmm): retry-ownership gateway side, unified poll status contract, binCount on pool-info - #679
Open
fengtality wants to merge 10 commits into
Open
fix(clmm): retry-ownership gateway side, unified poll status contract, binCount on pool-info#679fengtality wants to merge 10 commits into
fengtality wants to merge 10 commits into
Conversation
…ard; orca position-info contract Gateway-side changes for the gateway#678 retry-ownership work (see docs/retry-architecture.md, included here as the canonical cross-repo design): - solana-error-parser: map Orca Whirlpool 6018 TokenMinSubceeded to SLIPPAGE_EXCEEDED, and attribute custom program errors to the program on the "failed: custom program error" log line instead of the first "invoke" line — simulation-shaped errors open with ComputeBudget, so the DEX-specific error tables were never consulted (the actual #678 MATH_OVERFLOW misreport mechanism). Regression-tested with a full simulation-shaped log. - solana: reject transactions whose compute-estimation simulation returned an error, in both send paths — stale-state failures become a typed 400 before broadcast (zero fees) instead of a broadcast failure. - orca: getPositionInfo returns null ONLY when fetchMaybePosition reports the account does not exist; transient errors now propagate. Callers treat null as "position closed", so a swallowed RPC blip could abandon a live funded position while reporting success. Deliberately NOT included: the in-route close retry loop from 040e99e. Gateway stays a stateless transaction oracle — one request, one attempt, typed errors; retry ownership lives in the Hummingbot connector/executor (see the doc, §6). Validated live on mainnet: forced-failure cascade (fault-injected minimums) had every doomed close rejected pre-broadcast at zero fee cost across 33 attempts, with 6018 correctly surfaced as SLIPPAGE_EXCEEDED. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HahKfEY9rvKnZijrzUAFSq
This was referenced Aug 13, 2026
Greptile SummaryThe PR standardizes Ethereum and Solana transaction polling, adds typed Solana simulation failures, and extends unified CLMM pool information with configurable bins.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains within the eligible follow-up-review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/chains/ethereum/routes/poll.ts | Replaces heuristic and delayed transaction statuses with the shared four-state polling contract and correctly classifies reverted receipts. |
| src/chains/solana/routes/poll.ts | Distinguishes unseen signatures from pending transactions, includes program logs in error parsing, and preserves pending status for transient polling errors. |
| src/chains/solana/solana.ts | Adds shared transaction-status handling and rejects structured simulation failures before broadcasting. |
| src/chains/solana/solana-error-parser.ts | Attributes custom errors to the failing program and maps Orca error 6018 to slippage exceeded. |
| src/connectors/orca/orca.ts | Returns null only for definitively absent position accounts while allowing transient lookup failures to propagate. |
| src/connectors/clmm-v3-utils.ts | Centralizes bigint-based V3 tick traversal and bin amount calculations for multiple SDK numeric representations. |
| src/connectors/pancakeswap/clmm-routes/poolInfo.ts | Adds configurable V3 bins and reports pool token reserves using ERC20 balances. |
| src/trading/clmm/pools.ts | Adds bounded binCount input and forwards it to connectors that support configurable bin distributions. |
| src/schemas/chain-schema.ts | Defines the common transaction-status enum used by both chain polling routes. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Client[Client] --> Poll[Unified transaction poll]
Poll --> ETH[Ethereum receipt and mempool lookup]
Poll --> SOL[Solana transaction and signature-status lookup]
ETH --> Status[NOT_FOUND / FAILED / PENDING / CONFIRMED]
SOL --> Status
PoolClient[CLMM pool-info client] --> Unified[Unified pool-info route]
Unified --> Connector[Chain-specific connector]
Connector --> Bins[Optional V3 bin distribution]
Reviews (10): Last reviewed commit: "docs: cover the poll and CLMM pool-info ..." | Re-trigger Greptile
getTransaction (commitment 'confirmed') returns null both for a transaction awaiting confirmation and for one the cluster has never seen, so /poll reported txStatus 0 (pending) for dropped transactions forever — pollers had no signal to stop waiting on a transaction that can never land once its blockhash expires. The poll route now consults getSignatureStatuses (with history search) when txData is null: a signature the cluster has seen stays UNCONFIRMED (0); an unknown signature returns the new NOT_FOUND (-2), as does a malformed signature. -2 avoids colliding with the Ethereum poll's existing 2/3 mempool heuristics. Transient RPC errors still report UNCONFIRMED so callers keep polling rather than giving up on an unknown outcome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the design-evolution narrative (proposals, verdicts, review logs, deployment diaries) with a clean description: the ownership principle, the sixteen issues found across the four repos, and the architecture as it now stands — layered ownership, close-vs-open asymmetry, the close lifecycle, terminal semantics, the two topologies with the orphan lifecycle, and the bounded transaction-status polling contract (including the new NOT_FOUND poll status). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…reverts as FAILED
The two poll routes spoke different dialects: Solana used a local enum
(-1/0/1), Ethereum used raw numbers including 2 ('likely to be processed')
and 3 ('likely stuck') that no consumer understood, reported not-found as -1
after blocking the request for three 1-second in-route retries, and — via
'typeof receipt.status === number ? 1 : -1' — reported REVERTED transactions
(status 0, which is a number) as CONFIRMED, so a reverted swap polled as
filled.
Both routes now share TransactionStatusCode in chain-schema:
NOT_FOUND (-2) / FAILED (-1) / PENDING (0) / CONFIRMED (1).
Ethereum: not-found returns -2 immediately (no in-route sleeps — the caller
owns pacing and the not-found deadline), mempool is plain PENDING (gas-price
heuristics dropped), and receipt status 0 maps to FAILED.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Updates the retry-architecture doc for the two follow-up changes: the connector's retryable-code opt-in and inner budget are gone (the executor's CLOSING re-entry with max_retries=0 per attempt is the only close retry loop), and both chains' poll routes now share one TransactionStatusCode contract — including the Ethereum findings (2/3 heuristics, in-route retry sleeps, reverts reported as confirmed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found in live testing: /poll parsed only JSON.stringify(meta.err), which carries the error code but names no program — so extractProgramId never matched, every program-specific code fell through to the generic map, and a confirmed-but-failed Orca transaction reported 'UNKNOWN (0x1782)' instead of SLIPPAGE_EXCEEDED. Same misreporting as gateway#678, on the async path. The err object is now parsed together with meta.logMessages, whose 'Program X failed: custom program error' line is what the parser attributes on. Errors raised by programs with no registered table (e.g. a third-party router that CPIs into Whirlpool) correctly stay generic rather than being misattributed to the DEX they called. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Swap GET /trading/clmm/pool-info could never return `bins`: its querystring schema had no binCount and it called every connector as (fastify, network, poolAddress), dropping the parameter that orca/raydium/uniswap already support. Since hummingbot-api and condor read pool info through the unified route, the bin distribution was unreachable outside the per-connector routes. - Unified route accepts binCount and forwards it. Meteora is called without it, as it always returns its own bins. - PancakeSwap CLMM gains binCount. The V3 tick walk moves to a shared clmm-v3-utils helper: the two SDKs disagree on numeric type (@uniswap/v3-sdk is JSBI, @pancakeswap/v3-sdk is native bigint), so the helper works in bigint and each connector adapts its own SDK rather than importing the other's. - Fixes PancakeSwap pool-info token amounts, which were pool.liquidity (V3 virtual liquidity in sqrt-price space) scaled by each token's decimals — reporting the same meaningless figure for both sides. Now ERC20 balanceOf on the pool contract, the same fix Uniswap already carries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
binance.llamarpc.com is dead — it answers nothing, so every BSC read failed (pancakeswap pool-info reported 'Pool not found' for pools that exist). bsc-dataseed.bnbchain.org is BNB Chain's official public endpoint and returns chainId 0x38. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eth.llamarpc.com was unreachable — gateway logged 'Unable to fetch block number' on every startup and all mainnet reads failed. eth-mainnet.g.alchemy.com/public returns chainId 0x1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit that #678 triggered found defects on two read paths the LP flow depends on but that the issue never named — the transaction-status contract the poller reads, and the pool-info contract the dashboard and agents read. Both belong here: they share #678's root cause, a caller unable to tell a definitive answer from a transient one, or unable to ask for what it needs. Adds the CLMM pool-info issues (binCount unreachable through the unified route, PancakeSwap missing bins and reporting virtual liquidity as token amounts, Raydium bypassing Gateway, two dead default RPCs) and a section describing the request chain, the per-connector cost of binCount, and the bin output shape. Notes the fifth repository now in the family. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Gateway-side changes for the gateway#678 LP-close retry-ownership work, plus the CLMM pool-info and transaction-poll fixes found while validating it live. This PR carries the canonical cross-repo design doc:
docs/retry-architecture.md— the reference for all four companion PRs.Fixes #678 (gateway side; the retry itself lives in the Hummingbot PR below).
Typed errors and fail-fast (the #678 mechanism)
6018 TokenMinSubceeded→SLIPPAGE_EXCEEDED, and attribute custom program errors to the program on thefailed: custom program errorlog line instead of the firstinvokeline. Simulation-shaped errors open with a ComputeBudget prelude, so the DEX-specific error tables were never consulted — this is the actual mechanism behind Orca close-position should rebuild and retry after transient failures #678'sMATH_OVERFLOWmisreport.getPositionInfocontract (ported onto the feat(orca): migrate connector to current Whirlpools SDK #676 SDK migration): returnsnullonly whenfetchMaybePositionreports the account does not exist; transient errors propagate. Callers treatnullas "position closed", so a swallowed RPC blip could abandon a live funded position while reporting success.slippagePct(~1%), the exact condition under which Orca close-position should rebuild and retry after transient failures #678 was reachable (the legacy route used a 50% buffer).One transaction-status contract for both chains
The two
/pollroutes spoke different dialects, and both had defects that made a poller unable to act on the answer:txStatus 0(pending) for a signature the cluster had never seen — indistinguishable from one awaiting confirmation, so a dropped transaction polled as pending forever. It now consults the signature-status cache and reports the newNOT_FOUND(-2), which is terminal once the transaction's blockhash expires.typeof receipt.status === 'number' ? 1 : -1, and a revert's status is0, which is a number. It also emitted2/3gas-price heuristics no consumer understood, and blocked the request for three 1-second retries before reporting not-found as-1.TransactionStatusCode:NOT_FOUND (-2) / FAILED (-1) / PENDING (0) / CONFIRMED (1). Transient poll errors reportPENDING— an unknown outcome is a reason to poll again, not to give up./pollparsed onlyJSON.stringify(meta.err), which carries the error code but names no program, so every program-specific code fell through toUNKNOWN. It now parses the err together withmeta.logMessages. Errors raised by programs with no registered table (e.g. a third-party router that CPIs into Whirlpool) correctly stay generic rather than being misattributed to the DEX they called.CLMM pool-info:
binCountGET /trading/clmm/pool-info— the unified route hummingbot-api and condor read through — could never returnbins: its querystring schema had nobinCountand it called every connector as(fastify, network, poolAddress), dropping the parameter that orca/raydium/uniswap already supported.binCountand forwards it; Meteora is called without it, as it always returns its own bins.clmm-v3-utilshelper — the two SDKs disagree on numeric type (@uniswap/v3-sdkis JSBI,@pancakeswap/v3-sdkis nativebigint), so the helper works inbigintand each connector adapts its own SDK rather than importing the other's math.pool.liquidity(V3 virtual liquidity in sqrt-price space) scaled by each token's decimals — the same meaningless figure reported for both sides. Now ERC20balanceOfon the pool contract, the fix Uniswap already carried.Dead default RPCs
eth.llamarpc.com(mainnet) andbinance.llamarpc.com(BSC) answer nothing — gateway logged "Unable to fetch block number" at startup and every read failed, which is why PancakeSwap pool-info reported "Pool not found" for pools that plainly exist. Defaults are noweth-mainnet.g.alchemy.com/public(chainId0x1) andbsc-dataseed.bnbchain.org(chainId0x38).Companion PRs
docs/retry-architecture.md(in the gateway PR)binCounton unified CLMM pool-infoPOSITION_HOLD, fresh position reads, bounded pending-tx pollingbin_countpassthrough, Raydium routed through Gatewaybin_countonget_pool_info(1.5.8)bin_countonget_pool_infoValidation
tsc+ eslint clean; 257 chain tests and 181 connector/trading tests pass, including new coverage for both poll routes (incl. a regression test pinning EVM reverts toFAILED), thebinCountpassthrough, and PancakeSwap pool-info.Validated live on mainnet with the stack deployed from these branches:
binCount=61returns 61 populated bins for orca, raydium, uniswap and pancakeswap (Meteora keeps its own 141), bins straddling the active price correctly; PancakeSwap token amounts match direct on-chainbalanceOf.🤖 Generated with Claude Code
https://claude.ai/code/session_01Mt84XBEMVxbbyMG8fDxDKj