Skip to content

fix: Gateway fixes bundle (Orca, Raydium, Solana sends and balances, PancakeSwap BigInt, Uniswap SDK dedupe, Infura for Solana, CNDR token) - #735

Open
fengtality wants to merge 59 commits into
developmentfrom
fix/gateway-fixes-bundle
Open

fengtality wants to merge 59 commits into
developmentfrom
fix/gateway-fixes-bundle

Conversation

@fengtality

@fengtality fengtality commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR bundles six fix PRs and one token-list addition into one. Each fix branch is merged with its own commits, so every commit message still gives that fix's full reason. It replaces #723, #724, #725, #726, #731 and #733, which are closed. Their descriptions have the details.

Closes #713. Closes #684.

From What it fixes
#723 Fixes ported from a Gateway fork:
  • Orca pool-info works when Orca's API is down.
  • Raydium AMM v4 and CPMM pool state is read from the chain, and CLMM pools owned by another program are refused.
  • Raydium AMM v4 add gets slippage headroom.
  • PancakeSwap-Sol fixes.
  • A resent Solana transaction that already landed is reported, not failed.
  • Router quote-cache, 0x and request-validation fixes.
  • Tests that ran no Gateway code are removed.
#724 Solana fixes: native SOL is found by mint, not by symbol; balances return 4xx errors and are keyed by mint; Meteora add/open fixes; a wallet activity route.
#725 PancakeSwap CLMM execute-swap failed with "Cannot convert … to a BigInt" (#713).
#726 Infura as a Solana RPC provider (#684).
#731 Two copies of @uniswap/v3-sdk gave "Unexpected pool type in route". One pnpm override now dedupes them.
feat/add-cndr-token Adds Condor (CNDR, mint ESqMjA15hbH5NnHqjXo4S1A4CU4hPuKjkvi2mJ3oEihC, 6 decimals) to the default Solana mainnet token list.
#733 Solana sends through QuickNode did not land, because QuickNode drops skipPreflight: true sends. Sends now run preflight and are rebroadcast every 2 s until they confirm or expire.

Review fixes (added in this PR)

  • ca384c41c Infura keys in rate-limit errors. The rate-limit interceptor had its own URL redactor, which missed Infura's /v3/<key> path keys. A 429 put the key in errors that public routes return. The interceptor now uses the shared redactor from services/logger, which also redacts user information. This also affects the Ethereum Infura provider.
  • d9d74c461 Orca pool-info held up by a stalled Orca API. The Orca API lookup now has a 5 s deadline that also covers reading the response body. When the deadline passes, pool-info answers from the chain with tvlUsdc and yieldOverTvl null.

Conflict resolved

The only conflict was in Solana._sendAndConfirmRawTransaction, between #723 and #733.

Testing (merged branch)

  • pnpm build passes.
  • pnpm generate:openapi leaves openapi.json unchanged.
  • pnpm lint has 0 errors.
  • pnpm test -w 3 passes 173 suites (1364 tests), run from a clean conf/ as CI does.

🤖 Generated with Claude Code

Jupiter, 0x, Uniswap and PancakeSwap executed any cached quoteId, so a quote
from one connector could be run through another's execute path, with a cached
shape and signing path it was never built for. DFlow, OKX and Titan each
checked a `connector` field they stored inside the quote.

The quote cache now owns the check: a quote is stored under its connector and
`get`/`getRequest` return null to any other, so every execute path answers 400
"Quote not found or expired". One test runs all seven execute paths against
another connector's quote; DFlow's own copy of that case goes.
The route called validateAddress outside any handler, so a malformed address
came back as a raw 500, and two tests asserted that 500. It now answers 400
with the chain's own message ("Invalid Solana address format: ..."), and the
tests assert it. The non-existent-wallet test is renamed to what it checks:
the removal succeeds.
… imports

- test/lifecycle: jest ignored the directory, so none of it ran. One file
  sent live mainnet transactions behind a MANUAL_TEST skip, one checked its
  fixture JSON against itself, one called getPool with a stale signature.
  Its pancakeswap-sol fixtures go with it.
- test/helpers, test/constants, test/mocks/0x, test/mocks/orca: no importer.
- null-token-handling tested `?.` and `||` on local variables;
  tick-data-provider asserted its own mock and the v3-sdk Pool; the raydium
  sdk-breaking-changes, sdk-upgrade-integration and slippage files imported
  only the SDK; raydium-sdk-integration and raydium-setOwner-integration
  asserted mocks of the class under test (setOwner's reinitialisation stays
  covered in raydium.test.ts); orca-tick-array-compatibility tested a SDK
  call Gateway never makes.
- The okx/titan/dflow/jupiter schemas tests listed the keys of response
  types used only as Static<> return types; tsc enforces them.
…at kept it alive

- Raydium.executeTransaction, prepareWallet and the private getPairKey had no
  caller; findDefaultPool always returned null, so quotePosition's
  symbol-lookup branch (and its two symbol parameters) could never run.
- Orca.getPools and getRawPosition had no caller. The API-to-schema mapping
  test now goes through getPoolInfo, which the pool-info route uses.
- isSlippageError, isInsufficientBalanceError, HardwareWalletService
  listDevices and verifyConnection, PoolService.getChainForConnector: no
  caller. The PROGRAM_IDS test restated constants.
- SolanaPriorityFees.clearCache and QuoteCache clear/size existed for tests.
  The priority-fee suite reloads the module per test instead, and its
  per-network cache test now proves separate entries (two fetches, two fees)
  instead of passing with one global key.
- pancakeswap-sol getPositionsOwned took a poolAddress the route never
  passes.
- handlePoolError matched "Unsupported chainNetwork", which nothing throws;
  its test only reached it through a mock that threw that text. The mock now
  uses the real parser and the test asserts its "Invalid chainNetwork".
- clmm/routes: the Schema Validation block's bare 400s mostly came from a
  different guard (chainNetwork and walletAddress have defaults), and the
  rest repeated the REQUIRE_A_FIELD table; the Connector Support tests only
  asserted "not 404" on a registered route.
- The five "Replaced routes are gone" 404s: nothing in src can register a
  /connectors path, and a route added back would lack an operationId, which
  spec/operation-ids catches.
- pool-address-pin's "old connector/type form" was the enum rejection the
  next test covers; that test now asserts the message.
- response-components' "no longer publishes" names are guarded by the
  Amm.../Clmm... twin-naming test.
- The unknown-connector enum rejection was asserted nine times through one
  connectorField helper; the two create-pool files held nothing else.
- connector.routes booted a second app for what app.integration's
  trading-types test now checks: chain, networks, names.
- universal-router quoteSwap (Uniswap, PancakeSwap): two copies of the SELL
  quote left over from the removed protocols filter; a jest.doMock('ethers')
  that never reached the route, imported once in beforeAll; mocks of
  getOrFetchToken, which src does not have. The eth/Eth and ETH/ETH cases
  fold into their siblings, which take the same branches.
- universal-router: constructor tests that only checked toBeDefined.
- router-attempted-route: a check the two exact-string tests imply.
- okx: a "known vector" that computed one HMAC twice, and a query-string
  test the exact-header test covers.
- 0x executeSwap: three tests that echoed mocked quoteSwap/executeQuote
  results become one that checks the composition: a firm quote for the
  wallet, then executeQuote on that quote.
- Rate limits: the Solana balances route's message tests asserted text the
  test's own mock wrote; the Ethereum interceptor's retry/pass-through tests
  repeated rpc/rate-limit-connection, where its three distinct ones (four
  attempts, JSON-RPC body, sendTransaction not retried) now live; two more
  hit branches already covered; the propagation "edge cases" tested the
  interceptor, not the chain, and the Ethereum provider-read tests passed a
  mocked rejection through a one-line wrapper. A dead heliusService stub
  goes too.
- estimate-gas and status: "different networks", "consistent format", high
  and zero priority fees took the success path already covered; one
  missing-network default test is kept of four.
- config: update-config-network-files asserted fs/yaml were not called by a
  module that never imports them (its error case moved to config-utils,
  which now also proves the 500 carries the cause); four config-routes
  duplicates, one of them a "slippagePct special case" that does not exist.
  GET /config/chains moves next to the other config routes.
- services: error-handler's helper, throw/catch and instanceof tests
  restated httpErrors and JavaScript (the status names become one table);
  http-client property echoes; a source grep of findPools; a numeric
  passphrase that is still a string.
- rpc: helius and infura URL cases that take the same branch (infura's
  chain map becomes one table); a websocket failure case the generic one
  covers.
- The base58 wallet tests patched the key parser, so they repeated
  add/invalid-key; tokens' "update existing" hit the same mocked addToken;
  etherscan's six constructor cases become one supported, one not; three
  parser fallback codes become a table.
…adroom

Raydium AMM v4 deposits failed with "Exceeded desired slippage limit" (0x1e)
whenever the quote side was the limiting one (e.g. 0.05 SOL / 5.2 USDC into
SOL-USDC), while the base-limited case built fine. The v4 branch capped the
OTHER side at the user's raw amount (or the bare quote); the program computes
that side from live vault reserves, which sit slightly off the API reserves the
quote used, so the cap left no room and the deposit was refused at any
slippagePct.

The fixed side stays exactly what the user gave; the other side's max is now the
quoted amount x (1 + slippage) — the same `...AmountMax` quote-liquidity already
reports. otherAmountMin is unchanged. CPMM path untouched.

Verified against mainnet-beta: 0.05/5.2, 0.049956589/5.2 and 0.05/6 into
58oQChx4... all build; CPMM BCdZ9d2J... still builds.
PancakeSwap's Solana CLMM is a fork with Raydium's account layout, so a
PancakeSwap pool decoded cleanly through Raydium's getClmmPoolInfo and was
served as a Raydium pool. getClmmPoolInfo now reads the pool account's owner
first and answers null (the not-found path) unless it is Raydium's CLMM
program.
…dium's API

On mainnet-beta the AMM routes took pool info (reserves, LP supply, keys) from
Raydium's HTTP API, which lags the chain, and decided AMM v4 vs CPMM from the
API's programId. Quotes, liquidity quotes, position info and the add/remove
builds could all price against stale reserves, and a pool the API did not
know could not be used at all.

- getAmmPoolType reads the pool account's owner: AMM v4 or CPMM, 404 for a
  missing account, 400 for any other program.
- getAmmPoolFromRpc returns the SDK's chain-read poolInfo, poolKeys and
  rpcData for either type; every AMM route builds and quotes from it.
  getPoolfromAPI and getPoolType are gone.
- getAmmPoolInfo throws instead of answering null for a pool it cannot read.
- quote-liquidity feeds computePairAmount the reserves net of the PnL the pool
  owes out (AMM v4) and LP supply in whole tokens (CPMM), the units the SDK
  expects; position info computes the wallet's share from raw reserves and
  raw LP supply.
A swap from native SOL by a wallet with no wSOL account created that account
twice: once as the missing input account, then again inside wrapSOL, which
creates it too. The second create fails, so the swap could not run until the
wallet had a wSOL account. For native SOL the wrap alone creates it now.
…point

The PancakeSwap Solana routes loaded the wallet's keypair with getWallet,
signed in the route, simulated and sent the raw transaction themselves. That
works for a local keypair only: a Ledger wallet could not open, add to,
remove from, collect fees on or close a position, create a pool, or swap, and
the priority fee and compute budget were set by hand instead of the way every
other Solana connector sets them.

Every route now builds the transaction unsigned and hands it to
Solana.sendAndConfirmTransactionForWallet, which simulates, sizes the compute
budget, adds the priority fee and signs for the wallet's type, then re-fetches
it with getConfirmedTransactionData like the other connectors. The builders
keep a compute-unit limit for the simulation only and no longer take a
priority fee; PancakeswapSol's unused setOwner, prepareWallet and
signTransaction go.
…wap direction

swap_v2 needs, as remaining accounts, the pool's first initialized tick array
in the swap direction followed by the next initialized ones that way. The
builder passed only the array holding the current tick, and skipped even that
when it was uninitialized, so a swap failed whenever the current array was
empty or the swap crossed into the next one.

The arrays are now found from the pool's tick_array_bitmap and its bitmap
extension (up to three, in the order the swap crosses them), and the
extension account is passed ahead of them. A swap in a direction with no
initialized array fails before sending, saying the pool has no liquidity that
way.
… owner

getAssociatedTokenAddressSync throws TokenOwnerOffCurveError when the owner
is not on the ed25519 curve, which is every program-derived address. The
PancakeSwap Solana builders, Orca's position token account derivations and
Solana.wrapSOL / unwrapSOL (with the unwrap route) passed allowOwnerOffCurve
false, so none of them could act for a wallet that is a PDA. They pass true
now; for an on-curve wallet the derived address is the same.
…easing liquidity

decrease_liquidity_v2 pays a pool's active rewards into the wallet's reward
token accounts and fails AccountNotInitialized when one is missing, so
remove, collect-fees and close could not run for a wallet without them.
buildRewardAccountSetup creates each idempotently ahead of the decrease.
…ailure

Sending signed bytes that have already landed (an identical transaction built
on an unchanged blockhash, or a retry after a lost confirmation) makes the RPC
answer "This transaction has already been processed". The send path logged it
as a failed send and returned unconfirmed with no signature, so the caller saw
a failure for a transaction that was on chain.

The landed transaction's signature is the first one in the wire bytes; the
send now fetches that transaction and reports it as it stands: confirmed, or
unconfirmed if it landed with an error. If the RPC cannot find it, that is an
error naming the signature.
… tests run the real hook

- intent-is-not-dropped tested a copy of app.ts's x-connectors hook, and
  the copy had already drifted from production's message. The hook is now
  enforceConnectorFields in schema-keywords, installed by app.ts and by the
  test. Its "accepts" cases asserted only "not 400"; they now show the
  request reached the handler.
- chain-network-guard matched a source string to keep liquidity routes
  from parsing chainNetwork themselves. It now sends every route of the
  CLMM and AMM plugins a Solana connector with an Ethereum selector and
  expects the refusal. That found the pool-swap routes (quote-swap,
  execute-swap) looking up a pool for meteora on "mainnet" and answering
  404; they now refuse the pair with the registry's 400 first.
- learns-pool-and-tokens asserted only that quotes record nothing. It now
  proves a fill records the pinned pool, and that a failed record does not
  fail the fill.
- GET /pools' 400 test sent a chainNetwork the schema rejects, so the
  route's own mapping of a listPools failure never ran.
- spec/addressing skipped routes whose chainNetwork has no enum; it now
  finds chainNetwork by name and requires it on every pools/tokens write.
- positionsOwned (Raydium, Meteora, Orca): "returns all positions" and
  "empty array" asserted the mocked connector result back; each keeps its
  invalid-wallet 400, and the kept behaviours stay.
- Seven Orca "defaults network/wallet" tests exercised one schema default;
  one test now proves it through collect-fees: mainnet-beta and the
  configured wallet.
- Orca: a copy-pasted poolInfo block, a duplicate not-found, three identical
  quotePosition rejections (now one, asserting 500), the pool-lookup
  fallback that pool-address-pin covers, a deployment check the first
  swap-quote test already makes, and binCount-above-max checks that
  trading/clmm/pool-info-bin-count owns.
- Raydium: pool-not-found tests whose mock threw the 404 itself, a
  compute-unit constant, setOwner call-order counters, a base-shape echo and
  the binCount duplicate.
- Meteora: an AMM pool-info fixture asserted back, three fetch-pools
  parameter tests (now one passing all four), an empty-result echo, a
  generic 500, a weaker copy of the price-impact test; the 50% removal case
  joins the delegation table.
- estimate-gas (both chains): the fee is the one number these routes
  compute, and no test checked it; the first test now asserts
  (fee/CU x CU + 5000 lamports) in SOL and gas price x 300,000 in ETH.
- config-utils: the network-over-chain precedence test only checked
  toBeDefined; it now gives the chain a conflicting nodeURL and asserts the
  network's wins.
- config-routes-v2 GET: the mocked getConfig threw the 404s and returned the
  configs the tests then asserted. The merge and 404 are tested for real in
  config-utils; the route tests now check what the route does — pass the
  namespace, answer with the result, pass a 404 through.
- logger: the half of updateLoggerToStdout's test that proves the stdout
  transport is removed was commented out; it now counts console
  transports.
- http-client: "removes trailing slash" asserted toBeDefined; it now checks
  the URL fetch receives.
- rpc-provider-simple: asserted its own fixture and a stale provider list;
  it now checks rpcProvider comes from the chain namespace, defaults to
  "url", and nodeURL from each network's namespace.
- Ethereum's `chain` getter, "for backward compatibility", returned the
  network and had no reader.
- rpc-connection-interceptor re-exported the Solana wrapper under its old
  name, createRateLimitAwareConnection; nothing imports it.
- getConnector took a `_chain` it ignored, "kept for compatibility"; its
  one caller, the Ethereum poll route, no longer passes it.
parseTokenAmount went through Number.toFixed: (0.1).toFixed(18) is
"0.100000000000000006", so every 0x quote for 0.1 of an 18-decimal token
asked for 6 wei more than requested. It now converts through Decimal,
rounding down.

The quoteSwap/executeQuote tests mocked formatTokenAmount to return the
amounts they asserted, and mocked getOrFetchToken and a TokenService no path
uses. They now check the params sent to 0x by side, Gateway's amounts, the
transaction the wallet sends and what the confirmation handler receives;
the rewritten quote test is what found the rounding.
- Uniswap and PancakeSwap AMM quotes build a real v2 Pair from reserves and
  assert hand-computed outputs and each SDK's slippage rule; the "token not
  found" test now reaches the real guard instead of a mocked rejection.
- The universal-router tests assert an exact V2 output; the no-route case
  now fails because every pool read reverts, not on a TypeError.
- Jupiter and DFlow quotes assert a router fraction of 0.001 comes back as
  0.1 percent, replacing the price-impact source scan (its unit tests
  stay).
- create-pool asserts the fee-tier message; pool-info's bins test keeps
  only the call arguments, not the mock's output.
…and Orca add

Raydium CLMM open-position and Orca add-liquidity threw a raw 500 for a
malformed wallet or position address (new PublicKey / kit's address()); both
now answer 400 before touching the chain, and their tests assert the exact
status and message instead of "any status >= 400". Orca add's null-instance
test, which passed only because a null instance (never returned by
getInstance) crashed the route, now checks a failed position read: a 500 with
its cause and nothing sent.
- Error tests that accepted "any status >= 400" assert the exact status and
  message; three that passed only because a null Orca instance crashed the
  route, which getInstance never returns, are deleted.
- Orca create-pool's tickSpacing case was rejected by the schema, not the
  connector; it now sends binStep values the schema admits and hits the
  connector's guard. removeLiquidity sends 0% for the same reason.
- Meteora execute-swap mocked SDK methods src no longer calls; it now checks
  pool.swap for SELL and swapExactOut for BUY, each direction's tokens.
- pancakeswap-sol open-position encoding tested Borsh; it now decodes
  Gateway's own builder output, so the {some: false} bug would fail it.
  positionsOwned gives the NFT filter fungible accounts to drop.
- Raydium: the pool fee is read from a nonzero config; add-liquidity
  asserts the base-limited amounts; Orca pool info and quote-swap assert
  the fee and per-mint decimal scaling, not keys.
- position-rent's three distinct cases move next to liquidityWithoutRent's
  other tests in account-lifecycle.
Simulation runs the transaction without the compute-budget instructions and
reports only what the caller's instructions used, but the prepared
transaction carries two of them (set limit, set price) at 150 units each. A
percentage margin cannot cover a fixed cost: a plain SOL transfer simulates at
~150 units, gets a limit of ceil(150 x 1.1) = 165, then spends 450, so it
lands on chain and fails with "Computational budget exceeded" after the user
has signed. Every expensive instruction hid this behind its own margin; only
the cheapest transactions hit it.

Both the local-keypair send and the external-signer (Ledger) path now add
the 450 units the budget instructions cost on top of the 10% margin.
getClmmPoolInfo logged a failed AMM config read and answered feePct:
undefined. The config read now runs after the ownership/decode check and
throws "could not read Raydium CLMM config <addr> for pool <pool>: <cause>"
(or ": account not found"), which the pool-info route maps to a 500 with
that message like any other non-HTTP failure. Pools that aren't Raydium's
still answer null (404).
… SPL

getTokenProgramForMint answered TOKEN_PROGRAM_ID whenever the mint read
failed or returned nothing, so an RPC hiccup on a Token-2022 mint built a
transaction against the wrong program. It now returns the owner only when
it is SPL or Token-2022, and throws on a failed read, a missing account, or
any other owner.

The open-position encoding test served its pool account for every read,
mints included; it now answers mint reads with an SPL-owned account.
When Orca's API had no pool (or the lookup failed), pool info answered
tvlUsdc: 0, a real-looking figure for a pool nobody measured. It is now
null, and OrcaPoolInfoSchema admits null. The API mapping also parses the
string the API sends into a number, and a missing figure there is null
rather than undefined.

Nothing downstream requires the figure: the unified pool-info response
doesn't carry tvlUsdc, and no in-process caller reads it.
new PublicKey(walletAddress) ran unguarded after the Solana and Orca
instances loaded, so a bad wallet was a 500 "Invalid public key input".
Wallet and position are now both parsed up front and a malformed one is
400 "Invalid <wallet|position> address: <value>", before any chain read.

The connector's own "at least one amount" guard is gone: its only caller,
the unified /trading/clmm/add route, rejects that case first with its own
message, so the connector's could never fire.
…not found"

fetchPoolInfo caught every error and answered null, so a rate-limited RPC,
a failed fee() call or a connector's own read error all read as "pool not
found". Now:
- null only when the connector says it has no such pool;
- an unsupported connector, or a connector without pool info for the
  type, is a 400;
- any failure reading the pool is a 500
  "Failed to fetch pool info for <pool> from <connector> <type>: <cause>".

Callers: POST /pools, POST /pools/save/:address and GET /pools/find/:address
(via fetchDetailedPoolInfo) already pass HTTP errors through, so the 500
reaches the client with its cause. Their null branches now answer 404
"Pool <addr> not found in <connector> <type> on <network>" instead of a 400
"Unable to fetch pool-info", whose handlePoolError case is gone.
smart-order-router and universal-router-sdk@4 depend on @uniswap/v4-sdk@1.27.0,
which pins @uniswap/v3-sdk@3.27.0. Gateway and router-sdk use v4-sdk@2.3.0 and
v3-sdk@3.31.0, so the lockfile had two copies of each and router-sdk's instanceof
pool checks could fail with 'Unexpected pool type in route'. Override v4-sdk to
2.3.0 so one copy of each is installed.
Through the QuickNode Solana RPC, a transaction sent with skipPreflight is
accepted (the RPC answers its signature) but never reaches a leader, so every
send timed out at blockhash expiry. Rebroadcasting the same bytes every 2 s
(15 resends) did not help; the same send with preflight on landed in about
a second, and the same code through the public RPC lands either way.

Every broadcast now uses one SEND_OPTIONS with preflight on at 'confirmed'.
A transaction that preflight refuses was not broadcast, so the send now
throws its parsed program error (slippage, balance, or simulation failed)
instead of reporting an unconfirmed send with no signature.
Gateway sends with maxRetries 0, so the RPC hands the transaction to the
leader once and a dropped send is never retried. While the send waits to
confirm (WebSocket or polling), the same signed bytes are now sent again
every REBROADCAST_INTERVAL_MS (2 s), with the same send options. The loop
stops when the confirmation ends, when the block height passes the
lastValidBlockHeight the confirmation uses, or when the RPC says the
transaction was already processed. Nothing is re-signed and the blockhash
is not changed, so the transaction can land only once.

POST /chains/solana/submit (#721) sends through the same
_sendAndConfirmRawTransaction, so it gets the rebroadcast too.
…ight

Stopping the rebroadcast awaited the loop, so a slow getBlockHeight or resend
held back a send that had already confirmed. Stopping now only sets the flag
and wakes the timer; the loop catches its own errors and schedules nothing
after it is stopped.
A resend that returns after the stop scheduled one more timer before the
loop saw the flag. The loop condition now checks it first.
…t/fixes-integration

# Conflicts:
#	src/chains/solana/solana.ts
@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge, with no new actionable issues found.

Summary

Bundles fixes for Solana sends and balances, Orca and Raydium pool reads, PancakeSwap swaps, router quotes, and Infura support.

  • Solana sends preflight and rebroadcast until they confirm or expire.
  • Solana wallet activity can be read in pages.
  • Solana balances use token mints to identify assets.
  • Solana can use Infura RPC without exposing provider keys in errors.
  • Orca pool info keeps its on-chain data when the API stalls.
  • Raydium AMM and CPMM routes read pool state from the chain.
  • PancakeSwap Solana swaps and position actions build the accounts they need.
  • PancakeSwap EVM swaps no longer pass an average price as a price limit.
  • Router quotes stay with their connector and Uniswap uses one shared SDK version.
  • Pool and connector requests check their fields and report clearer results.
  • The default Solana mainnet token list includes Condor (CNDR).

Reviews (3) · Last reviewed commit: "Merge branch 'feat/add-cndr-token' into ..." · Reviewed by Greptile

Comment thread src/chains/solana/solana.ts Resolved
The rate-limit interceptor had its own redactUrl that removed only query keys
and user information. A 429 on an Infura URL (/v3/<key>) put the key in the
error message, which public routes such as /chains/solana/balances return.
The interceptor now uses the shared redactUrl from services/logger, which
also redacts user information.
…ld up

pool-info reads the pool on chain and the Orca API in parallel. The API
request had no deadline, so a stalled API held up the chain answer until the
connection failed. getPoolInfo now aborts after ORCA_API_TIMEOUT_MS (5 s),
body included, and pool-info answers with tvlUsdc and yieldOverTvl null.
@fengtality fengtality changed the title fix: Gateway fixes bundle (Orca, Raydium, Solana sends and balances, PancakeSwap BigInt, Uniswap SDK dedupe, Infura for Solana) fix: Gateway fixes bundle (Orca, Raydium, Solana sends and balances, PancakeSwap BigInt, Uniswap SDK dedupe, Infura for Solana, CNDR token) Oct 20, 2026
@rapcmia rapcmia self-assigned this Oct 21, 2026
@rapcmia rapcmia moved this to Under Review in Pull Request Board Oct 21, 2026
@rapcmia rapcmia modified the milestones: v2.17, v2.18 Oct 21, 2026
@rapcmia

rapcmia commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

8b667b1

Build and tests


Solana

  • Sends through QuickNode now land: they run with preflight and are rebroadcast until they confirm. All 18 live sends confirmed, and preflight caught 2 bad ones before any fee was paid. ✅
  • Infura works as the Solana RPC provider: balances, pool info and quotes all work through it, and the key is hidden in the logs. ✅
  • Condor (CNDR) is in the Solana token list and works in balances, quotes and pool creation. ✅
  • The new wallet-activity endpoint (GET /chains/solana/transactions) returns an error when the wallet has a newer-format (version-1) transaction. ❌
    • Cause: solana-activity.ts:377 asks the RPC for legacy/version-0 transactions only (maxSupportedTransactionVersion: 0), so one newer transaction fails the whole page. Reproduces every time on the test wallet once the page reaches that transaction (limit=13 works, limit=14 returns 500).
  • The balance endpoint does not show wrapped SOL (WSOL). ⚠️

Raydium

  • Swap, add liquidity and remove liquidity work on mainnet, with balances matching exactly. ✅
  • Pools owned by another program are correctly rejected. ✅

PancakeSwap-Solana


Meteora

  • Open, add, remove and close work on mainnet, and all position rent was refunded. ✅

Orca

  • Pool info and quotes work, including when Orca's analytics service is slow. ✅

PancakeSwap EVM (BSC)

  • The BigInt fix works: approve, swap and reverse swap all succeed with no BigInt error. ✅

Found while testing this PR's changes to these routes that are pre-existing on development branch ❗

  • Raydium: add liquidity reports a slightly higher SOL amount, because it includes account rent.
  • Raydium: removing all liquidity leaves an empty LP account holding a small rent deposit (about 0.0015 SOL).
  • Raydium and PancakeSwap-Solana: the slippage setting has no effect when removing liquidity.
  • PancakeSwap-Solana: position info shows unrealistically large uncollected-fee numbers.
  • PancakeSwap-Solana: add liquidity and SOL swaps leave a small leftover wrapped-SOL balance and rent behind.
  • Meteora: add and remove report the SOL amount off by the transaction fee.
  • Meteora: close fails if the position was already fully emptied with remove, so close directly instead.
  • PancakeSwap EVM: the reported swap output comes from the quote, not the amount actually received. This code is on development too, but there swaps failed first with the BigInt error (PancakeSwap CLMM - swaps fail with BigInt conversion errors while Uniswap succeeds #713) that this PR fixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Under Review

Development

Successfully merging this pull request may close these issues.

2 participants