Skip to content

feat(clmm): bin_count, CLMM liquidity methods, and orphan-closable positions (1.5.8) - #25

Open
fengtality wants to merge 10 commits into
mainfrom
feat/clmm-bin-count
Open

feat(clmm): bin_count, CLMM liquidity methods, and orphan-closable positions (1.5.8)#25
fengtality wants to merge 10 commits into
mainfrom
feat/clmm-bin-count

Conversation

@fengtality

@fengtality fengtality commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Two additions to gateway_clmm, both in the unreleased 1.5.8: bin_count on get_pool_info, and the missing liquidity methods needed to manage a CLMM position.

Until now the method could not ask for bins at all. Meteora returns its own bins unconditionally, so it appeared to work — but every other CLMM connector computes them only on request, and there was no way to make that request through the client. Callers who wanted bins had to bypass the typed method and hit the endpoint directly.

pool_info = await client.gateway_clmm.get_pool_info(
    connector="orca",
    network="solana-mainnet-beta",
    pool_address="Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE",
    bin_count=61,      # 0 (default) skips the extra on-chain reads
)

The parameter is only sent when non-zero, so existing calls are byte-identical on the wire.

Also drops a stale note in the docstring claiming Raydium returns 400 for Standard AMM pools — that behaviour came from hummingbot-api's direct-Raydium-API path, which hummingbot/hummingbot-api#217 removes in favour of routing every CLMM connector through Gateway.

CLMM liquidity: add_liquidity, remove_liquidity, and pool_address

The API has had POST /gateway/clmm/add and /remove all along, but this router never exposed them — the only way to shed CLMM liquidity through the client was a full close.

await client.gateway_clmm.remove_liquidity(
    connector="orca", network="solana-mainnet-beta",
    position_address="...", percentage=Decimal("50"),
)

remove_liquidity(percentage=100) empties a position but leaves the account open; close_position() closes it. The docstrings now say so, because picking the wrong one is how an "already closed" position keeps showing up on-chain.

close_position() and collect_fees() also gain an optional pool_address. The API reads a position's pool from its own database, and positions opened by an lp_executor are never in it — the bot opens those straight against Gateway. Without pool_address those calls fail with a 400, so recovering an orphaned LP position requires passing it; GET /executors/positions/orphaned reports it per orphan. This is what lets condor's new manage_clmm tool close an orphan at all.

Requires

pool_address on close/collect-fees is accepted by hummingbot/hummingbot-api#217; against an older API server those calls still 404 for positions it never recorded.

bin_count is forwarded by hummingbot/hummingbot-api#217 to Gateway's unified /trading/clmm/pool-info, which learns binCount in hummingbot/gateway#679. Against an older API server the parameter is ignored, not an error.

Companion PRs

Validation

Verified against a live mainnet stack (Gateway + hummingbot-api deployed from the companion branches): bin_count=61 returns 61 populated bins for orca, raydium, uniswap and pancakeswap, while Meteora keeps returning its own 141 and ignores the parameter. bin_count=0 returns none for all of them.

The liquidity methods are wire-compatible additions over endpoints the API already served; they are exercised through condor's manage_clmm tests rather than live here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Mt84XBEMVxbbyMG8fDxDKj

get_pool_info could not request the per-tick liquidity distribution, so
callers wanting bins had to bypass the client and hit the endpoint directly.
It now forwards bin_count, which hummingbot-api passes to Gateway. Meteora
always returns its bins and ignores it; orca, raydium, uniswap and
pancakeswap compute them on request, so the default of 0 keeps pool-info
cheap.

Also drops the stale note about Raydium rejecting Standard AMM pools — that
came from hummingbot-api's direct-Raydium-API path, which has been removed in
favour of routing every CLMM connector through Gateway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ct_fees

The API has POST /gateway/clmm/add and /remove, but this router never exposed
them — the only way to shed CLMM liquidity through the client was a full close.
Adds add_liquidity() and remove_liquidity(), and documents the distinction that
matters: remove_liquidity(percentage=100) empties a position but leaves the
account open, while close_position() closes it.

close_position() and collect_fees() gain an optional pool_address. The API reads
a position's pool from its own database; positions opened by an lp_executor are
never in it, because the bot opens those straight against Gateway. Without
pool_address those calls now fail with a 400, so recovering an orphaned position
requires passing it — GET /executors/positions/orphaned reports it per orphan.

Folded into the unreleased 1.5.8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mt84XBEMVxbbyMG8fDxDKj
@fengtality fengtality changed the title feat(clmm): bin_count on get_pool_info (1.5.8) feat(clmm): bin_count, CLMM liquidity methods, and orphan-closable positions (1.5.8) Aug 17, 2026
fengtality and others added 8 commits August 18, 2026 15:50
The API's pool_address parameter was a silent no-op (Gateway's
positions-owned route has no pool filter), removed there; drop it here
too. Each returned row carries its own pool_address.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
…tion_info

Mirrors the new hummingbot-api endpoints closing the gaps against Gateway's
unified trading routes: pre-trade position quoting, CLMM pool creation
(AMM had it, CLMM did not), and fetching one position by address.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
Both AMM and CLMM create_pool take extra_params (Gateway-native key names:
configAddress, feeConfigIndex, openTime, binStep, feeBps, ammConfigIndex,
fee, tickSpacing, ammConfig, gasPrice, maxGas, slippagePct) instead of
per-connector named kwargs — the same contract as clmm open. Unknown keys
are rejected by the API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
…e_to_remove; extras coverage

- Never force slippage to 1.0: omit the field when the caller omits it so the
  connector-configured slippage applies; an explicit 0 is preserved (swap
  quote/execute, clmm open/add, amm execute-swap/add-liquidity).
- Send search filters as query parameters on /gateway/swaps/search and
  /gateway/clmm/positions/search — hapi reads query params on those POSTs and
  silently ignored the JSON body, so filters, pagination, and refresh never
  applied.
- Rename CLMM remove_liquidity's percentage to percentage_to_remove (hapi's
  field name; the old key 422s) and expose slippage_pct (orca honors it).
- Expose extra_params on swap quote/execute (approximateIfNoExactOut for the
  jupiter/dflow/okx/titan routers) and clmm add_liquidity; add slippage_pct to
  amm create_pool.
- Correct create-pool docstrings to the accepted extra_params vocabulary
  (binStep/feeBps/ammConfigIndex/configAddress) and drop keys hapi rejects;
  fix the swaps-summary example to volume_by_quote_token.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
hapi removed /gateway/amm/quote-swap and execute-swap after Gateway
folded them into the unified /trading/swap route; use
gateway_swap.get_swap_quote/execute_swap with connector as name/type
(e.g. 'raydium/amm', 'meteora/clmm').

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
hapi's /gateway/clmm/pools takes a network parameter instead of assuming
mainnet-beta; without it here, no caller could reach any other network.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
add_pool POSTed to /gateway/pools, which exists only as a deprecated GET,
and omitted the required base_address/quote_address; delete_pool called
/gateway/pools/{address}, which hapi does not serve at all. Both were
404/405s. The network-scoped add_network_pool/delete_network_pool already
cover the same ground correctly and are what every caller uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
Every router here is a hand-written wrapper and nothing checks it against the API, so
a renamed or dropped route survives as a method that reads fine and fails only when
someone calls it. Four had been failing that way:

- docker.pull_image sent {name, tag}; the route requires a single {image_name} carrying
  the tag. A 422 on every call. condor's gateway-install flow calls this, and its tests
  mock the client, so nothing caught it.
- docker's start/stop/remove container methods addressed /docker/container/{name}/...;
  the routes are /docker/{start,stop,remove}-container/{name}. remove is a POST taking
  archive_locally and s3_bucket, not a DELETE taking force.
- docker.get_container_status has no route at all — read a container's state from
  get_active_containers()/get_exited_containers(), both of which take a name_filter.
- bot_orchestration's import_strategy_for_bot and configure_bot address routes the API
  does not serve.

The whole rate_oracle module goes too: the API has no rate-oracle router, so all seven
of its methods could only 404. market_data.get_prices pointed at it for "the oracle's
own rates"; that note now says what is actually true.

Adds the two AMM search routes the client never wrapped (events and positions), and
gateway_amm to the sync client's router list, which had been left out.

test/test_calls_match_api_spec.py is the durable half: it parses every _get/_post call
out of the routers and checks the path, query keys and body keys against a vendored
openapi.json. All four breaks above fail it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166iQoxKce23GkUwuQJxdkr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant