fix(solana): typed close errors + pre-broadcast simulation guard; Orca position-info contract (retry architecture) - #679
Open
fengtality wants to merge 6 commits into
Open
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 transaction polling, adds typed Solana simulation and program-error handling, and tightens Orca’s position-existence contract.
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/solana/solana.ts | Adds shared status handling, pre-broadcast simulation-error guards, and typed handling for transactions that land with errors. |
| src/chains/solana/solana-error-parser.ts | Attributes custom errors to the program identified by the failure log and maps Orca error 6018 to slippage exceeded. |
| src/chains/solana/routes/poll.ts | Distinguishes signatures observed by the cluster from unknown signatures and includes program logs when parsing landed failures. |
| src/chains/ethereum/routes/poll.ts | Adopts the shared polling contract and correctly classifies reverted receipts as failed. |
| src/connectors/orca/orca.ts | Returns null only when the position account is definitively absent and propagates other position-fetch failures. |
| src/schemas/chain-schema.ts | Defines the shared cross-chain transaction status code contract. |
| docs/retry-architecture.md | Documents stateless Gateway attempts, upstream retry ownership, terminal position semantics, and orphan recovery. |
Sequence Diagram
sequenceDiagram
participant Client
participant Gateway
participant RPC
participant Program
Client->>Gateway: Submit one transaction attempt
Gateway->>RPC: Simulate transaction
alt Simulation returns program error
RPC-->>Gateway: Error and program logs
Gateway->>Gateway: Parse failing program and typed error
Gateway-->>Client: Typed error before broadcast
else Simulation succeeds
RPC-->>Gateway: Compute usage
Gateway->>Program: Sign and broadcast
Program-->>Gateway: Confirmation result
Gateway-->>Client: Confirmed result or typed failure
end
Reviews (6): Last reviewed commit: "fix(poll): attribute failed-transaction ..." | 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>
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. This PR also 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).
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. Regression-tested with a full simulation-shaped log.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.040e99e. Gateway stays a stateless transaction oracle (one request = one attempt, typed errors); retry ownership lives upstream — see doc §6. Note this is now load-bearing: the migrated close route quotes at the configslippagePct(~1%), the exact condition under which Orca close-position should rebuild and retry after transient failures #678 was reachable (legacy route used a 50% buffer).Companion PRs
docs/retry-architecture.md(in the gateway PR)Validation
tsc+ eslint clean; 74 targeted jest tests pass. Validated live on mainnet: a forced-failure cascade (fault-injected minimums) had all 33 doomed close attempts rejected pre-broadcast at zero fee cost, with 6018 correctly surfaced asSLIPPAGE_EXCEEDED; funded open/close cycles pass on the deployed image.🤖 Generated with Claude Code
https://claude.ai/code/session_01HahKfEY9rvKnZijrzUAFSq