Skip to content

Fix inverted BUY price, add binCount to CLMM pool-info, extend Ethereum tx receipt polling#642

Open
fengtality wants to merge 6 commits into
developmentfrom
fix/clmm-quote-price-and-pool-info
Open

Fix inverted BUY price, add binCount to CLMM pool-info, extend Ethereum tx receipt polling#642
fengtality wants to merge 6 commits into
developmentfrom
fix/clmm-quote-price-and-pool-info

Conversation

@fengtality

@fengtality fengtality commented May 27, 2026

Copy link
Copy Markdown
Contributor

Three related improvements: two to the CLMM connectors, and one to Ethereum tx execution.

1. BUY-side price inversion fix (Orca + Meteora)

The CLMM quote-swap endpoints for Orca and Meteora computed
price = outputAmount / inputAmount regardless of side. On BUY the
route passes (input=quote, output=base) to the helper, so the returned
price came back as base/quote (inverted) on BUY but quote/base on SELL.
For the same pool callers saw e.g. 0.987 on BUY vs 1.012 on SELL.

  • Orca (clmm-routes/quoteSwap.ts): reconstruct price = quote/base
    in the route from the helper's amounts + the request's side. Helper
    signature unchanged.
  • Meteora (clmm-routes/quoteSwap.ts): flip the BUY branch's
    price expression to amountIn / amountOut. SELL was already correct.
  • Raydium: verified upstream code already branches per side correctly
    (BUY: amountIn/amountOut, SELL: amountOut/amountIn). No change.

Regression tests in test/connectors/orca/clmm-routes/quoteSwap.test.ts
assert the price unit stays quote/base on both sides.

2. binCount param on pool-info (Orca, Uniswap, Raydium)

Mirrors Meteora's pool-info.bins[] shape across the other CLMM
connectors. Passing ?binCount=N to /connectors/<dex>/clmm/pool-info
returns N tick-spacing-wide bins centered on the active tick:

{
  // … standard pool-info fields …
  "bins": [
    { "binId": -276211, "price": 1.01146, "baseTokenAmount": 0,   "quoteTokenAmount": 100 },
    
    { "binId": -276200, "price": 1.01258, "baseTokenAmount": 1.5, "quoteTokenAmount": 0   }
  ]
}

Default behavior is unchanged: binCount defaults to 0, so existing
pool-info callers pay no extra RPC cost. Token amounts are derived via V3
sqrt-price math against the active liquidity covering each bin.

Implementation per connector

Connector Mechanism
Orca fetchAllPositionWithFilter + positionWhirlpoolFilter → one getProgramAccounts returns every open position; sum L per bin; tryGetAmountDeltaA/B for amounts.
Uniswap Parallel pool.ticks(tick) reads at each bin boundary; propagate L from pool.liquidity() via the V3 spec; SqrtPriceMath.getAmount{0,1}Delta for amounts.
Raydium PoolUtils.fetchComputeClmmInfo + PoolUtils.fetchMultiplePoolTickArrays → tick-array cache; look up liquidityNet via TickUtils.getTickArrayStartIndexByTick + TickUtils.getTickOffsetInArray; propagate L outward; LiquidityMath.getTokenAmount{A,B}FromLiquidity for amounts.

Base schema

PoolInfoSchema gains an optional bins array (using the existing
BinLiquiditySchema). GetPoolInfoRequest gains an optional
binCount (0..401, default 0). The Meteora-specific bins field
moves up to the base since Meteora was already returning the same shape.

3. Extended tx receipt polling (Ethereum)

Ethereum.handleTransactionExecution previously returned null as soon as
_transactionExecutionTimeoutMs (default 30s) elapsed, even when the tx
was about to confirm in the next block. Ethereum blocks are ~12s, so a
healthy tx can need a few more blocks; reporting null at 30s caused
callers to treat confirmed txs as failures.

After the initial race against the timeout, the method now polls
getTransactionReceipt every 5s for an additional 90s before returning
null. Receipts with status: 0 (reverted) are still surfaced
immediately by tx.wait.

Also tightened routes/approve.ts to handle the null-receipt case the
timeout path can produce — the previous receipt.status === -1 check
crashed with TypeError on null.

Tests

File Tests added
test/connectors/orca/clmm-routes/quoteSwap.test.ts 2 (price-unit regression)
test/connectors/orca/clmm-routes/poolInfo.test.ts 4 (binCount cases)
test/connectors/uniswap/clmm-routes/pool-info.test.ts 4 (binCount cases)
test/connectors/raydium/clmm-routes/poolInfo.test.ts 6 (new file)
test/chains/ethereum/handle-transaction-execution.test.ts 3 (new file — fast path, extended poll success, extended poll exhaustion)

binCount suites cover: no binCount → no bins[]; binCount=0 → no
bins[]; binCount=Nbins[] length N with shape assertions;
binCount above schema max → 400.

All 245 tests across the affected suites pass.


QA smoke checklist

Run a local gateway against mainnet (pnpm start --passphrase=<…>) and walk
through the items below. Each should match the stated expectation.

A. BUY-side price inversion fix

  • GET /connectors/orca/clmm/quote-swap?network=mainnet-beta&baseToken=SOL&quoteToken=USDC&amount=1&side=SELLprice is ~SOL spot in USDC (e.g. ~180).
  • GET /connectors/orca/clmm/quote-swap?network=mainnet-beta&baseToken=SOL&quoteToken=USDC&amount=1&side=BUYprice is also in USDC per SOL (same order of magnitude as SELL, not ~0.005).
  • Same two calls against /connectors/meteora/clmm/quote-swap — BUY price must be in quote/base, not inverted.
  • Spot-check /connectors/raydium/clmm/quote-swap (untouched) still returns the correct unit on both sides.

B. binCount on pool-info

  • GET /connectors/<orca|uniswap|raydium>/clmm/pool-info?network=…&poolAddress=… (no binCount) → response has no bins field; latency unchanged vs development.
  • Same with &binCount=0 → still no bins field.
  • Same with &binCount=21bins array of length 21; binId strictly increasing; the bin containing the active tick has both baseTokenAmount and quoteTokenAmount non-negative; bins above the active tick have quoteTokenAmount = 0; bins below have baseTokenAmount = 0.
  • &binCount=999 (above schema max) → 400.
  • Compare Orca + Uniswap + Raydium bin output for the same pair (e.g. WETH/USDC) — shapes line up qualitatively against the dex's UI liquidity chart.

C. Ethereum tx receipt extended polling

  • Submit any non-trivial Ethereum approval (e.g. POST /chains/ethereum/approve) during a period of normal block times — completes within ~30s as before, receipt returned, no behavioral change.
  • Submit an approval during congestion (or against a slower L2 with transactionExecutionTimeoutMs lowered to 5s in config) — the route should still complete with a real receipt as long as the tx confirms within ~2min total; logs should show "polling for receipt up to a further 90000ms" followed by "(after extended poll)".
  • Send a tx with deliberately too-low gas price so it never confirms — after ~2min the route should return a clean timeout error (no crash, no TypeError on receipt.status).

🤖 Generated with Claude Code

The CLMM quote-swap endpoints for Orca and Meteora were computing
`price = outputAmount / inputAmount` regardless of side. On BUY this
returns base/quote (since input=quote, output=base), while on SELL it
correctly returns quote/base — so BUY and SELL quotes for the same pool
reported prices at different scales (e.g. 0.987 vs 1.012 for a USDC/USDM1
pool).

This broke any client comparing the two sides (arb checkers, mid-price
calculators, etc.) and disagreed with what the Uniswap CLMM connector
returns for the same conceptual quantity.

Fix:
- Orca `clmm-routes/quoteSwap.ts`: reconstruct `price = quote/base` in
  the route from the helper's `(inputAmount, outputAmount)` and the
  request's `side`, keeping the helper signature unchanged.
- Meteora `clmm-routes/quoteSwap.ts`: invert the BUY branch's `price`
  expression so it returns quote/base; SELL branch already correct.
- Add regression tests asserting price units stay quote/base on both
  sides (Orca only — Meteora has no existing quoteSwap test).

Raydium CLMM already correctly branches per side (`price = in/out` on
BUY, `out/in` on SELL), so no change needed there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors Meteora's `pool-info.bins[]` shape across the Orca, Uniswap, and
Raydium CLMM connectors. Passing `?binCount=N` to `/connectors/<dex>/clmm/pool-info`
returns N tick-spacing-wide bins centered on the active tick, each reporting:

    { binId, price, baseTokenAmount, quoteTokenAmount }

with token amounts derived from V3 sqrt-price math against the active
liquidity covering that bin.

Default behavior is unchanged: `binCount` defaults to 0, so existing
pool-info callers pay no extra RPC cost.

Implementation per connector
============================
Orca   — `computeOrcaBinDistribution` in `orca.utils.ts`. One
         `fetchAllPositionWithFilter` + `positionWhirlpoolFilter` RPC
         returns every open Position; we sum liquidity per bin and convert
         to amounts via `tryGetAmountDeltaA/B` from whirlpools-core.

Uniswap — `computeUniswapBinDistribution` in `uniswap.utils.ts`. Parallel
         `pool.ticks(tick)` reads at every bin boundary, propagate L
         outward from `pool.liquidity()` via the V3 spec, then
         `SqrtPriceMath.getAmount{0,1}Delta` for token amounts.

Raydium — schema only. `binCount` is accepted on the request so the API
          contract is consistent across all CLMM connectors, but bin
          computation is deferred to a follow-up PR (needs
          `PoolUtils.fetchMultiplePoolTickArrays` + `LiquidityMath` wiring).

Base schema
===========
`PoolInfoSchema` gains an optional `bins` array (using the existing
`BinLiquiditySchema`), and `GetPoolInfoRequest` gains an optional
`binCount` (0..401, default 0). The Meteora-specific `bins` field
moves up to the base since Meteora was already returning the same shape.

Tests
=====
- `test/connectors/orca/clmm-routes/poolInfo.test.ts`: 4 new cases
- `test/connectors/uniswap/clmm-routes/pool-info.test.ts`: 4 new cases
- `test/connectors/raydium/clmm-routes/poolInfo.test.ts`: 5 cases (new file)
Each suite covers: no binCount → no bins[]; binCount=0 → no bins[];
binCount=N → bins[] length N with shape assertions; binCount > max → 400.
For Raydium the binCount>0 case asserts the schema is accepted but
`bins` stays undefined until the impl lands.

All 23 new + existing tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fengtality fengtality changed the title Fix inverted BUY price in Orca + Meteora CLMM quote-swap Fix inverted BUY price + add binCount distribution to CLMM pool-info May 27, 2026
fengtality and others added 2 commits May 27, 2026 11:04
Adds the Raydium half of the binCount feature added in the previous commit
— previously schema-only because the SDK wiring was non-trivial.

`computeRaydiumBinDistribution` in `raydium.utils.ts`:
- Calls `PoolUtils.fetchComputeClmmInfo` + `PoolUtils.fetchMultiplePoolTickArrays`
  to pull every tick array for the pool (one getProgramAccounts).
- For each bin boundary in the window, looks up the corresponding tick via
  `TickUtils.getTickArrayStartIndexByTick` + `TickUtils.getTickOffsetInArray`
  and reads its `liquidityNet` (zero-defaulted for uninitialized ticks).
- Walks L outward from `pool.liquidity` at the current tick.
- Computes per-bin token amounts via `LiquidityMath.getTokenAmount{A,B}FromLiquidity`,
  splitting at the active sqrtPrice when the bin straddles the current tick.

The Raydium pool-info route is wired the same way as Orca/Uniswap:
binCount=0 (default) → unchanged behavior; binCount > 0 → populated bins[].

Tests updated in `test/connectors/raydium/clmm-routes/poolInfo.test.ts`:
- omits bins[] when binCount missing
- omits bins[] when binCount=0
- returns bins[] of length N when binCount=N
- rejects binCount above the schema max

All 24 CLMM pool-info tests pass (Orca 12 + Uniswap 6 + Raydium 6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
handleTransactionExecution previously returned null as soon as
_transactionExecutionTimeoutMs (default 30s) elapsed, even when the tx
was about to confirm in the next block. Ethereum blocks are ~12s, so a
healthy tx can need a few more blocks; reporting null at 30s caused
callers to treat confirmed txs as failures.

Now: after the initial race against the timeout, poll
getTransactionReceipt every 5s for an additional 90s before returning
null. Receipt with status=0 (reverted) is still returned immediately.

Also tighten approve.ts to handle the null-receipt case the timeout
path can produce — previously crashed with TypeError on receipt.status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fengtality fengtality changed the title Fix inverted BUY price + add binCount distribution to CLMM pool-info Fix inverted BUY price, add binCount to CLMM pool-info, extend Ethereum tx receipt polling May 27, 2026
@fengtality fengtality requested a review from nikspz May 27, 2026 18:16
@fengtality fengtality added this to the v2.15 milestone May 27, 2026

@nikspz nikspz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit a78cd86

@nikspz nikspz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit a78cd86
* binCount param on pool-info

commit a78cd86

  • binCount Support on pool-info
    • No binCount parameter

    • binCount=0

    • binCount=21

      • orca binCount=21
        • curl -X'GET' 'http://localhost:15888/connectors/orca/clmm/pool-info?network=mainnet-beta&poolAddress=Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE&binCount=21' \ -H'accept: application/json'
          • {"address":"Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE","baseTokenAddress":"So11111111111111111111111111111111111111112","quoteTokenAddress":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","feePct":0.04,"price":82.07876886210782,"baseTokenAmount":264417.14518099,"quoteTokenAmount":6526347.147977,"activeBinId":-25003,"liquidity":"900273092941807","sqrtPrice":"5284880106715257754","tvlUsdc":28195598.093698423,"protocolFeeRate":0.13,"yieldOverTvl":0.00123070689684141,"binStep":4,"bins": [ {
            "binId":-25044,"price":81.73485156378344,"baseTokenAmount":0,"quoteTokenAmount":48667.724389 },
            {
            "binId":-25040,"price":81.76755040882699,"baseTokenAmount":0,"quoteTokenAmount":48659.558916 },
            {
            "binId":-25036,"price":81.80026233537062,"baseTokenAmount":0,"quoteTokenAmount":48810.60096 },
            {
            "binId":-25032,"price":81.83298734864772,"baseTokenAmount":0,"quoteTokenAmount":48893.098401 },
            {
            "binId":-25028,"price":81.86572545389376,"baseTokenAmount":0,"quoteTokenAmount":49239.217015 },
            {
            "binId":-25024,"price":81.8984766563463,"baseTokenAmount":0,"quoteTokenAmount":49507.443652 },
            {
            "binId":-25020,"price":81.93124096124505,"baseTokenAmount":0,"quoteTokenAmount":49538.891393 },
            {
            "binId":-25016,"price":81.96401837383176,"baseTokenAmount":0,"quoteTokenAmount":49699.401169 },
            {
            "binId":-25012,"price":81.99680889935026,"baseTokenAmount":0,"quoteTokenAmount":51144.066291 },
            {
            "binId":-25008,"price":82.02961254304651,"baseTokenAmount":0,"quoteTokenAmount":51211.337389 },
            {
            "binId":-25004,"price":82.06242931016861,"baseTokenAmount":315.618391173,"quoteTokenAmount":25673.827361 },
            {
            "binId":-25000,"price":82.09525920596671,"baseTokenAmount":628.264696752,"quoteTokenAmount":0 },
            {
            "binId":-24996,"price":82.12810223569304,"baseTokenAmount":631.370367869,"quoteTokenAmount":0 },
            {
            "binId":-24992,"price":82.16095840460197,"baseTokenAmount":638.157795331,"quoteTokenAmount":0 },
            {
            "binId":-24988,"price":82.19382771794994,"baseTokenAmount":632.905465597,"quoteTokenAmount":0 },
            {
            "binId":-24984,"price":82.22671018099557,"baseTokenAmount":634.878634089,"quoteTokenAmount":0 },
            {
            "binId":-24980,"price":82.25960579899949,"baseTokenAmount":635.159627226,"quoteTokenAmount":0 },
            {
            "binId":-24976,"price":82.2925145772245,"baseTokenAmount":634.785838904,"quoteTokenAmount":0 },
            {
            "binId":-24972,"price":82.32543652093544,"baseTokenAmount":635.43029333,"quoteTokenAmount":0 },
            {
            "binId":-24968,"price":82.35837163539934,"baseTokenAmount":635.426175191,"quoteTokenAmount":0 },
            {
            "binId":-24964,"price":82.39131992588523,"baseTokenAmount":634.031203489,"quoteTokenAmount":0 }
            ]
            }
      • raydium binCount=21 ✅
        • curl -X'GET' 'http://localhost:15888/connectors/raydium/clmm/pool-info?network=mainnet-beta&poolAddress=3ucNos4NbumPLZNWztqGHNFFgkHeRMBQAVemeeomsUxv&binCount=21' \ -H'accept: application/json'
          • {"address":"3ucNos4NbumPLZNWztqGHNFFgkHeRMBQAVemeeomsUxv","baseTokenAddress":"So11111111111111111111111111111111111111112","quoteTokenAddress":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","feePct":0.04,"price":81.92395627765741,"baseTokenAmount":57445.75183911,"quoteTokenAmount":750491.153116,"activeBinId":-25021,"binStep":1,"bins": [ {
            "binId":-25031,"price":81.84117064740515,"baseTokenAmount":0,"quoteTokenAmount":1230.27416 },
            {
            "binId":-25030,"price":81.8493547644699,"baseTokenAmount":0,"quoteTokenAmount":1230.462831 },
            {
            "binId":-25029,"price":81.85753969994633,"baseTokenAmount":0,"quoteTokenAmount":1230.524352 },
            {
            "binId":-25028,"price":81.86572545391633,"baseTokenAmount":0,"quoteTokenAmount":1230.585877 },
            {
            "binId":-25027,"price":81.87391202646171,"baseTokenAmount":0,"quoteTokenAmount":1230.647405 },
            {
            "binId":-25026,"price":81.88209941766436,"baseTokenAmount":0,"quoteTokenAmount":1230.802709 },
            {
            "binId":-25025,"price":81.89028762760613,"baseTokenAmount":0,"quoteTokenAmount":1230.864248 },
            {
            "binId":-25024,"price":81.89847665636889,"baseTokenAmount":0,"quoteTokenAmount":1230.926714 },
            {
            "binId":-25023,"price":81.90666650403452,"baseTokenAmount":0,"quoteTokenAmount":1231.581288 },
            {
            "binId":-25022,"price":81.91485717068491,"baseTokenAmount":0,"quoteTokenAmount":1228.134611 },
            {
            "binId":-25021,"price":81.92304865640199,"baseTokenAmount":13.330328373,"quoteTokenAmount":136.074225 },
            {
            "binId":-25020,"price":81.93124096126763,"baseTokenAmount":14.990570168,"quoteTokenAmount":0 },
            {
            "binId":-25019,"price":81.93943408536376,"baseTokenAmount":14.989820696,"quoteTokenAmount":0 },
            {
            "binId":-25018,"price":81.9476280287723,"baseTokenAmount":14.989071261,"quoteTokenAmount":0 },
            {
            "binId":-25017,"price":81.95582279157516,"baseTokenAmount":14.988321864,"quoteTokenAmount":0 },
            {
            "binId":-25016,"price":81.96401837385432,"baseTokenAmount":14.987572504,"quoteTokenAmount":0 },
            {
            "binId":-25015,"price":81.97221477569173,"baseTokenAmount":14.986823181,"quoteTokenAmount":0 },
            {
            "binId":-25014,"price":81.98041199716928,"baseTokenAmount":14.986073896,"quoteTokenAmount":0 },
            {
            "binId":-25013,"price":81.98861003836899,"baseTokenAmount":15.01038904,"quoteTokenAmount":0 },
            {
            "binId":-25012,"price":81.99680889937284,"baseTokenAmount":15.598386912,"quoteTokenAmount":0 },
            {
            "binId":-25011,"price":82.00500858026277,"baseTokenAmount":15.498974524,"quoteTokenAmount":0 }
            ]
            }
      • uniswap binCount=21 ✅
        • curl -X'GET' 'http://localhost:15888/connectors/uniswap/clmm/pool-info?network=mainnet&poolAddress=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8&binCount=21' \ -H'accept: application/json'
          • {"address":"0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8","baseTokenAddress":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","quoteTokenAddress":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","feePct":0.3,"price":0.000498320978391003,"baseTokenAmount":6053317.598705,"quoteTokenAmount":9153.455248545379,"activeBinId":200277,"binStep":60,"bins": [ {
            "binId":199620,"price":0.0004666088883392882,"baseTokenAmount":0,"quoteTokenAmount":71.41601909626858 },
            {
            "binId":199680,"price":0.0004694168166367824,"baseTokenAmount":0,"quoteTokenAmount":71.28285950156209 },
            {
            "binId":199740,"price":0.0004722416423006168,"baseTokenAmount":0,"quoteTokenAmount":71.45496538066126 },
            {
            "binId":199800,"price":0.0004750834670146519,"baseTokenAmount":0,"quoteTokenAmount":73.07830150147461 },
            {
            "binId":199860,"price":0.00047794239307465456,"baseTokenAmount":0,"quoteTokenAmount":73.420773102625 },
            {
            "binId":199920,"price":0.00048081852339198057,"baseTokenAmount":0,"quoteTokenAmount":73.78661790759385 },
            {
            "binId":199980,"price":0.0004837119614972787,"baseTokenAmount":0,"quoteTokenAmount":73.83856530534415 },
            {
            "binId":200040,"price":0.0004866228115442178,"baseTokenAmount":0,"quoteTokenAmount":72.94454908976876 },
            {
            "binId":200100,"price":0.0004895511783132358,"baseTokenAmount":0,"quoteTokenAmount":72.67563603880568 },
            {
            "binId":200160,"price":0.0004924971672153113,"baseTokenAmount":0,"quoteTokenAmount":72.87983122868094 },
            {
            "binId":200220,"price":0.0004954608842957584,"baseTokenAmount":5955.214685,"quoteTokenAmount":69.99470890888112 },
            {
            "binId":200280,"price":0.0004984424362380436,"baseTokenAmount":145299.267864,"quoteTokenAmount":0 },
            {
            "binId":200340,"price":0.0005014419303676262,"baseTokenAmount":128390.268615,"quoteTokenAmount":0 },
            {
            "binId":200400,"price":0.0005044594746558214,"baseTokenAmount":126684.914596,"quoteTokenAmount":0 },
            {
            "binId":200460,"price":0.0005074951777236875,"baseTokenAmount":125236.122513,"quoteTokenAmount":0 },
            {
            "binId":200520,"price":0.0005105491488459349,"baseTokenAmount":124554.914073,"quoteTokenAmount":0 },
            {
            "binId":200580,"price":0.0005136214979548607,"baseTokenAmount":123144.767183,"quoteTokenAmount":0 },
            {
            "binId":200640,"price":0.0005167123356443051,"baseTokenAmount":120215.755998,"quoteTokenAmount":0 },
            {
            "binId":200700,"price":0.0005198217731736326,"baseTokenAmount":119683.987157,"quoteTokenAmount":0 },
            {
            "binId":200760,"price":0.000522949922471737,"baseTokenAmount":118793.711816,"quoteTokenAmount":0 },
            {
            "binId":200820,"price":0.0005260968961410706,"baseTokenAmount":113639.36098,"quoteTokenAmount":0 }
            ]
            }
    • Invalid binCount

    • Orca Connector Test Summary

      • No binCount / binCount=0: Passed. ✅The bins field is completely omitted, the response structure is clean, and the API latency matches development baseline.
      • Invalid binCount=999 Validation: Passed. ✅ The gateway correctly triggers a schema validation error: Value must be less than 401.
      • binCount=21 Array Analysis: Passed with minor remarks. ❗ ✅
        • Active Tick & Asset Distribution: The pool's activeBinId is -25003. The crossover bin containing the active price is binId: -25004, which properly holds non-zero amounts for both assets (baseTokenAmount: 315.61 and quoteTokenAmount: 25673.82). This is the mathematically correct and expected behavior for the specific tick range where the current price resides.
        • Bin ID Increments: The binId values are strictly and monotonically increasing (binId[i] < binId[i+1]). The sequence advances with a step of 4 (-25044, -25040, -25036...), which perfectly aligns with the pool's native "binStep": 4 parameter (Orca Whirlpool tick spacing). This fully satisfies the specification requirements for this protocol configuration.
    • Raydium ✅

      • No binCount / binCount=0: Passed. The bins field is completely omitted from the response payload.
      • Invalid binCount=999 Validation: Passed. The validation handles the error uniformly across all gateway connectors, properly throwing the Value must be less than 401 exception.
      • binCount=21 Array Analysis: Passed perfectly (100% Spec Compliance).
      • Strictly Increasing Bin IDs: The identifiers increment sequentially with a strict step of 1 (-25031, -25030, -25029...), perfectly matching the pool's "binStep": 1.
        • Active Tick Behavior: The activeBinId is -25021, serving as the exact midpoint of the 21-bin array. As strictly required by the spec, this specific bin holds non-negative amounts for both assets simultaneously (baseTokenAmount: 13.330328373 and quoteTokenAmount: 136.074225).
        • Bid/Ask Asset Separation: All bins below the active tick (-25031 to -25022) contain only quoteTokenAmount (baseTokenAmount: 0). All bins above the active tick (-25020 to -25011) contain only baseTokenAmount (quoteTokenAmount: 0).
    • Final Verdict

      • Orca ✅
        • minor wording adjustment recommended regarding active bin interpretation
      • Raydium ✅
        • implementation appears fully compliant with PR specification
      • Uniswap: ok ✅

@rapcmia rapcmia moved this from Backlog to Under Review in Pull Request Board Jun 5, 2026
Two bugs in the Orca CLMM quote helper, surfaced on the BUY path:

1. amountOut wrong on BUY: getOrcaSwapQuote always used
   swapQuoteByInputToken, so "BUY 0.2 SOL" was computed as "spend 0.2
   USDC" and returned ~0.00243 SOL out. Add a `side` param and use
   swapQuoteByOutputToken for BUY (exact-output), matching executeSwap
   and the Raydium/Meteora convention. BUY now returns amountOut=0.2 SOL
   with amountIn derived as the required quote amount.

2. priceImpactPct inflated (~676000%) on B->A swaps: the bToA branch
   compared 1/executionPrice (B per A) against 1/currentPrice (A per B),
   mixing units and inflating impact by ~currentPrice^2. Normalize
   executionPrice to B-per-A before comparing to currentPrice. BUY/SELL
   now agree (~0.04% for the same-size trade).

Verified live against pool Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fengtality

Copy link
Copy Markdown
Contributor Author

Follow-up: fixed two remaining Orca BUY-path bugs (commit f2a0283)

While QA-ing the BUY-side price inversion fix, we found the inverted price was only the cosmetic symptom — the BUY path had two deeper bugs in the quote helper (getOrcaSwapQuote) that this PR's route-level price reconstruction didn't address. Both are now fixed.

1. amountOut wrong on BUY (exact-input vs exact-output)

getOrcaSwapQuote always called swapQuoteByInputToken, so a request to BUY 0.2 SOL was computed as spend 0.2 USDCamountOut: 0.00243 SOL.

Fix: added a side param and use swapQuoteByOutputToken for BUY (exact-output), so amount is treated as the desired base output. This matches what executeSwap.ts already does, and the Raydium/Meteora convention.

2. priceImpactPct inflated to ~676,000% on B→A swaps

The bToA branch compared 1/executionPrice (B-per-A) against 1/currentPrice (A-per-B) — a unit mismatch that inflated impact by ~currentPrice². Normalized executionPrice to B-per-A before comparing.

Live verification — pool Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE

Field BUY (before) BUY (after) SELL (after)
amountOut 0.00243147 0.2 SOL ✅ 12.88 USDC
amountIn 0.2 (misread as USDC) 12.89 USDC ✅ 0.2 SOL
price 82.25 64.44 64.40
priceImpactPct 676213.8% 0.040% 0.040%

BUY and SELL prices now agree to within the spread/fee, both in quote/base units. (The real pool price is ~$64.4/SOL; the original ~$82 was an artifact of the inverted math.)

Tests

typecheck clean; 14/14 Orca quote-swap tests pass, incl. a new regression asserting side is forwarded to the helper so the exact-output path is selected on BUY.

Files: src/connectors/orca/orca.utils.ts, src/connectors/orca/clmm-routes/quoteSwap.ts, test/connectors/orca/clmm-routes/quoteSwap.test.ts

@fengtality

Copy link
Copy Markdown
Contributor Author

@nikspz see above for fixes to the issues you found.

@nikspz nikspz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi Mike @fengtality , could you check the BUY priceImpactPct calculation for Raydium (priceImpactPct = 99649.68% is incorrect)

  • commit f2a0283
    • Test performed:
      • cloned and installed gwPR642 and latest development branch
      • connected gateway solana and ethereum
      • change nodeURL
      • BUY-side Price Inversion Fix
      • binCount Support on pool-info
        • No binCount parameter
        • binCount=0
        • binCount=21
          • orca binCount=21
            • curl -X'GET' 'http://localhost:15888/connectors/orca/clmm/pool-info?network=mainnet-beta&poolAddress=Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE&binCount=21' \ -H'accept: application/json'
              • Response 200
                • {"address":"Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE","baseTokenAddress":"So11111111111111111111111111111111111111112","quoteTokenAddress":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","feePct":0.04,"price":66.61523519682865,"baseTokenAmount":257378.784947234,"quoteTokenAmount":4205443.780287,"activeBinId":-27090,"liquidity":"468108050146613","sqrtPrice":"4761091251569524424","tvlUsdc":21411358.552854326,"protocolFeeRate":0.13,"yieldOverTvl":0.00259516727657384,"binStep":4,"bins": [ {
                  "binId":-27132,"price":66.33322679428692,"baseTokenAmount":0,"quoteTokenAmount":24348.845628 },
                  {
                  "binId":-27128,"price":66.35976406526358,"baseTokenAmount":0,"quoteTokenAmount":24271.592987 },
                  {
                  "binId":-27124,"price":66.38631195274098,"baseTokenAmount":0,"quoteTokenAmount":24273.639123 },
                  {
                  "binId":-27120,"price":66.41287046096635,"baseTokenAmount":0,"quoteTokenAmount":24262.106783 },
                  {
                  "binId":-27116,"price":66.4394395941886,"baseTokenAmount":0,"quoteTokenAmount":24264.577469 },
                  {
                  "binId":-27112,"price":66.4660193566584,"baseTokenAmount":0,"quoteTokenAmount":23982.424901 },
                  {
                  "binId":-27108,"price":66.49260975262811,"baseTokenAmount":0,"quoteTokenAmount":23992.882378 },
                  {
                  "binId":-27104,"price":66.51921078635175,"baseTokenAmount":0,"quoteTokenAmount":23967.201092 },
                  {
                  "binId":-27100,"price":66.545822462085,"baseTokenAmount":0,"quoteTokenAmount":24149.732436 },
                  {
                  "binId":-27096,"price":66.57244478408539,"baseTokenAmount":0,"quoteTokenAmount":24156.816141 },
                  {
                  "binId":-27092,"price":66.59907775661199,"baseTokenAmount":142.732245614,"quoteTokenAmount":14653.056556 },
                  {
                  "binId":-27088,"price":66.6257213839257,"baseTokenAmount":362.711901196,"quoteTokenAmount":0 },
                  {
                  "binId":-27084,"price":66.65237567028908,"baseTokenAmount":362.639369696,"quoteTokenAmount":0 },
                  {
                  "binId":-27080,"price":66.67904061996636,"baseTokenAmount":363.084414373,"quoteTokenAmount":0 },
                  {
                  "binId":-27076,"price":66.70571623722348,"baseTokenAmount":362.828706071,"quoteTokenAmount":0 },
                  {
                  "binId":-27072,"price":66.7324025263282,"baseTokenAmount":362.790378156,"quoteTokenAmount":0 },
                  {
                  "binId":-27068,"price":66.75909949154979,"baseTokenAmount":361.945081184,"quoteTokenAmount":0 },
                  {
                  "binId":-27064,"price":66.78580713715942,"baseTokenAmount":361.627401363,"quoteTokenAmount":0 },
                  {
                  "binId":-27060,"price":66.8125254674299,"baseTokenAmount":368.739393042,"quoteTokenAmount":0 },
                  {
                  "binId":-27056,"price":66.83925448663564,"baseTokenAmount":368.64210925,"quoteTokenAmount":0 },
                  {
                  "binId":-27052,"price":66.86599419905292,"baseTokenAmount":367.212291329,"quoteTokenAmount":0 }
                  ]
                  }
          • raydium binCount=21
            • curl -X'GET' 'http://localhost:15888/connectors/raydium/clmm/pool-info?network=mainnet-beta&poolAddress=3ucNos4NbumPLZNWztqGHNFFgkHeRMBQAVemeeomsUxv&binCount=21' \ -H'accept: application/json'
              • Response 200
                • {"address":"3ucNos4NbumPLZNWztqGHNFFgkHeRMBQAVemeeomsUxv","baseTokenAddress":"So11111111111111111111111111111111111111112","quoteTokenAddress":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","feePct":0.04,"price":66.65921278561191,"baseTokenAmount":56667.281221896,"quoteTokenAmount":859009.847508,"activeBinId":-27083,"binStep":1,"bins": [ {
                  "binId":-27093,"price":66.59241851478039,"baseTokenAmount":0,"quoteTokenAmount":809.495824 },
                  {
                  "binId":-27092,"price":66.59907775663187,"baseTokenAmount":0,"quoteTokenAmount":809.536298 },
                  {
                  "binId":-27091,"price":66.60573766440753,"baseTokenAmount":0,"quoteTokenAmount":809.576774 },
                  {
                  "binId":-27090,"price":66.61239823817397,"baseTokenAmount":0,"quoteTokenAmount":809.481981 },
                  {
                  "binId":-27089,"price":66.6190594779978,"baseTokenAmount":0,"quoteTokenAmount":809.522454 },
                  {
                  "binId":-27088,"price":66.62572138394559,"baseTokenAmount":0,"quoteTokenAmount":809.562929 },
                  {
                  "binId":-27087,"price":66.63238395608398,"baseTokenAmount":0,"quoteTokenAmount":809.603406 },
                  {
                  "binId":-27086,"price":66.63904719447959,"baseTokenAmount":0,"quoteTokenAmount":809.643886 },
                  {
                  "binId":-27085,"price":66.64571109919903,"baseTokenAmount":0,"quoteTokenAmount":809.684367 },
                  {
                  "binId":-27084,"price":66.65237567030896,"baseTokenAmount":0,"quoteTokenAmount":809.72485 },
                  {
                  "binId":-27083,"price":66.65904090787599,"baseTokenAmount":11.834027005,"quoteTokenAmount":20.879987 },
                  {
                  "binId":-27082,"price":66.66570681196677,"baseTokenAmount":12.14665492,"quoteTokenAmount":0 },
                  {
                  "binId":-27081,"price":66.67237338264796,"baseTokenAmount":12.146047633,"quoteTokenAmount":0 },
                  {
                  "binId":-27080,"price":66.67904061998624,"baseTokenAmount":12.145440376,"quoteTokenAmount":0 },
                  {
                  "binId":-27079,"price":66.68570852404824,"baseTokenAmount":12.14483315,"quoteTokenAmount":0 },
                  {
                  "binId":-27078,"price":66.69237709490064,"baseTokenAmount":12.144225953,"quoteTokenAmount":0 },
                  {
                  "binId":-27077,"price":66.69904633261012,"baseTokenAmount":12.143618788,"quoteTokenAmount":0 },
                  {
                  "binId":-27076,"price":66.7057162372434,"baseTokenAmount":12.143011652,"quoteTokenAmount":0 },
                  {
                  "binId":-27075,"price":66.71238680886711,"baseTokenAmount":12.142404547,"quoteTokenAmount":0 },
                  {
                  "binId":-27074,"price":66.71905804754799,"baseTokenAmount":12.141797473,"quoteTokenAmount":0 },
                  {
                  "binId":-27073,"price":66.72572995335274,"baseTokenAmount":12.141443213,"quoteTokenAmount":0 }
                  ]
                  }
          • uniswap binCount=21
            • curl -X'GET' 'http://localhost:15888/connectors/uniswap/clmm/pool-info?network=mainnet&poolAddress=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8&binCount=21' \ -H'accept: application/json'
              • {"address":"0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8","baseTokenAddress":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","quoteTokenAddress":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","feePct":0.3,"price":0.000593941914975587,"baseTokenAmount":5602672.877412,"quoteTokenAmount":8648.510892959619,"activeBinId":202033,"binStep":60,"bins": [ {
                "binId":201420,"price":0.0005586272355741603,"baseTokenAmount":0,"quoteTokenAmount":97.52415987389263 },
                {
                "binId":201480,"price":0.0005619889058331697,"baseTokenAmount":0,"quoteTokenAmount":97.79106044601471 },
                {
                "binId":201540,"price":0.0005653708057304972,"baseTokenAmount":0,"quoteTokenAmount":97.01121647508225 },
                {
                "binId":201600,"price":0.0005687730570027306,"baseTokenAmount":0,"quoteTokenAmount":94.3292099172906 },
                {
                "binId":201660,"price":0.000572195782119036,"baseTokenAmount":0,"quoteTokenAmount":94.81733984442243 },
                {
                "binId":201720,"price":0.0005756391042855666,"baseTokenAmount":0,"quoteTokenAmount":95.39063547305015 },
                {
                "binId":201780,"price":0.0005791031474498973,"baseTokenAmount":0,"quoteTokenAmount":95.6540138353081 },
                {
                "binId":201840,"price":0.0005825880363054866,"baseTokenAmount":0,"quoteTokenAmount":95.89200641066837 },
                {
                "binId":201900,"price":0.0005860938962961652,"baseTokenAmount":0,"quoteTokenAmount":96.44641418254369 },
                {
                "binId":201960,"price":0.0005896208536206515,"baseTokenAmount":0,"quoteTokenAmount":91.97432360731055 },
                {
                "binId":202020,"price":0.0005931690352370938,"baseTokenAmount":122108.703298,"quoteTokenAmount":20.12035066710245 },
                {
                "binId":202080,"price":0.0005967385688676416,"baseTokenAmount":155177.671249,"quoteTokenAmount":0 },
                {
                "binId":202140,"price":0.0006003295830030415,"baseTokenAmount":157722.380557,"quoteTokenAmount":0 },
                {
                "binId":202200,"price":0.0006039422069072638,"baseTokenAmount":153420.496534,"quoteTokenAmount":0 },
                {
                "binId":202260,"price":0.000607576570622155,"baseTokenAmount":152108.162448,"quoteTokenAmount":0 },
                {
                "binId":202320,"price":0.0006112328049721184,"baseTokenAmount":154200.116333,"quoteTokenAmount":0 },
                {
                "binId":202380,"price":0.0006149110415688247,"baseTokenAmount":153694.737876,"quoteTokenAmount":0 },
                {
                "binId":202440,"price":0.0006186114128159475,"baseTokenAmount":153151.07105,"quoteTokenAmount":0 },
                {
                "binId":202500,"price":0.0006223340519139317,"baseTokenAmount":149308.833733,"quoteTokenAmount":0 },
                {
                "binId":202560,"price":0.0006260790928647863,"baseTokenAmount":148151.870343,"quoteTokenAmount":0 },
                {
                "binId":202620,"price":0.0006298466704769088,"baseTokenAmount":147420.500646,"quoteTokenAmount":0 }
                ]
                }
        • Invalid binCount
      • Orca: Fixed
      • Meteora: Fixed
      • Raydium: ok, but BUY priceImpactPct calculation appears broken
        • ⚠️ Price inversion fixed, but another issue exists.
          • BUY:
            • "price": 66.362265
              "priceImpactPct": 99649.68240999784
          • SELL:
            • "price": 66.047745
              "priceImpactPct": 0.03262043287540081
          • The BUY/SELL prices are consistent, so the inversion bug appears fixed.
          • However:
          • priceImpactPct = 99649.68%
            • is clearly incorrect.
          • For a 0.2 SOL swap it should be somewhere around:
            • 0.00x%
              0.0x%
              <1%
            • not 99,649%.
      • binCount Support
        • Orca: ok
          • 21 bins returned
          • Sorted ascending
          • Increment = 4
          • Matches binStep = 4
          • Active bin region contains both assets
          • Lower bins quote-only
          • Upper bins base-only
        • Raydium: ok
          • 21 bins returned
          • Increment = 1
          • Matches binStep = 1
          • Active bin contains both assets
          • Lower bins quote-only
          • Upper bins base-only
        • Uniswap: ok
          • 21 bins returned
          • Increment = 60
          • Matches binStep = 60
          • Active bin region contains both assets:
            "binId":202020,
            "baseTokenAmount":122108.703298,
            "quoteTokenAmount":20.12035066710245
          • Lower bins quote-only
          • Upper bins base-only

Three related bugs in the Raydium CLMM quote/execute swap path:

- priceImpactPct was extracted via Number(response.priceImpact) * 100, but
  priceImpact is an SDK Percent object. Number(Percent) is NaN (toString is
  "[object Object]") and toSignificant() already returns a percentage, so the
  value collapsed to 0 (or, with older code, a nonsense 99649%). Now uses
  response.priceImpact.toSignificant(8) with no extra scaling, on both sides.

- slippage was passed as new BN(slippagePct / 100), which truncates any
  sub-100% slippage to 0, disabling slippage entirely. Now passes the fraction
  (slippagePct / 100) directly as the SDK expects.

- The BUY (ExactOut) path passed the wrong baseMint (always mintB) with the
  amount in the output token's decimals, then reverse-engineered the input via
  a 1/x decimals hack (convertAmountIn). That inverted maxAmountIn so it came
  out *less* than amountIn once slippage worked. Now calls computeAmountIn with
  baseMint = output token, so amountIn/maxAmountIn return directly in the input
  token's units (slippage already included). Deleted convertAmountIn and the
  manual slippage recompute in executeSwap.

Adds test/connectors/raydium/clmm-routes/quote-swap.test.ts covering BUY, SELL,
and the not-found path, asserting the price-impact scaling, slippage fraction,
and maxAmountIn > amountIn invariant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants