feat(gateway): execute a held quote, and surface what a quote did not say - #26
Merged
Conversation
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
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
hummingbot-api serves /gateway/swap/execute-quote and this client had no method for it, which is one of the three layers that left Gateway's two-step swap flow unreachable. A router quote holds a price; execute_swap prices again and throws that away. Spec refreshed from the API that now serves the route. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GuR4iDtH4ZfT51rJfs3DhK
The API now returns `approximation` on a swap quote, the only change in the spec. The client returns quote dicts unchanged, so the field arrives on its own; what was missing is any reason for a caller to look at it. A BUY is an ExactOut order, and a thin token with no ExactOut route is quoted by pricing the sell leg and quoting that input forward — about 2.5%. The order is silently resized rather than overcharged, which is exactly what a caller who asked for a specific quantity needs to know. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GuR4iDtH4ZfT51rJfs3DhK
…oyed An executor response now carries volume_traded_quote beside filled_amount_quote. They are the same number for an executor that places orders and deliberately different for an LP executor, whose filled amount is the capital it deposited — depositing capital trades nothing, and the volume its range actually saw is derived from the fees it earned. The three volume descriptions changed with it: "total filled volume" is the phrasing that made a deposit look like trading. get_executor's docstring names both, and the Gateway swap fields the wheel now reports — transaction_hash, swap_provider, wallet_address, and the LIVE slippage_pct, which reads above the configured start exactly when a widening has already happened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GuR4iDtH4ZfT51rJfs3DhK
This was referenced Aug 21, 2026
Merged
The API now accepts them, so expose them here. Gateway needs GeckoTerminal for exactly one thing on that route -- deciding which DEX an address belongs to, and whether it is amm or clmm -- and the pool's base, quote and fee always come from the connector afterwards. Passing both answers that directly and skips the lookup. It matters for the callers most likely to save a pool. An LP provider config of 'meteora/clmm' splits straight into these two arguments, and the pools such a caller opens against are the newest ones, which are exactly the ones an indexer is least likely to have seen. Both or neither: passing one alone sends no params rather than half a request, and the API rejects the mismatched case with the legal values.
… type The docstring described the two-field split -- filled_amount_quote as the capital deployed, volume_traded_quote as the volume -- and both halves are now wrong. There is one field, and an LP executor reports the swaps that crossed its range rather than its deposit, derived from the fees it earned.
cardosofede
approved these changes
Aug 26, 2026
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
Client coverage for the unified Gateway surface, and the two-step swap flow. Supersedes #25, which was opened from a branch that has since been replaced; the work continues here on
feat/clmm-bin-count.Pairs with hummingbot/hummingbot-api#221.
A quote you can hold, then commit to
/trading/router/execute-quoteexisted on Gateway and was reachable from no layer above it — a caller could take a quote and had no way to execute that quote, only to re-price.execute_quote(connector, network, wallet_address, quote_id)closes the gap.Router-only, and deliberately so: a quote id refers to route calldata Gateway cached, which pool-scoped amm/clmm swaps have no equivalent of — they price against a pool at execution. Naming a non-router connector fails here rather than silently re-pricing, which would defeat the point of the flow.
An approximated BUY is now visible
A BUY is an ExactOut order, and many thin tokens have no ExactOut route, so Gateway falls back to quoting the sell leg and quoting that input forward — paying the pool fee and crossing the spread twice. Measured at a near-constant ~2.5% across eleven pools spanning $17 to $1,963 of liquidity, and reached for only on the thin, high-fee pools where it hurts most.
The caller is not overcharged; the order is silently resized, which is what matters to a strategy that asked for a specific quantity.
approximationnow travels to the caller, andget_swap_quote's docstring says what to do about it — includingextra_params={'approximateIfNoExactOut': False}to require an exact route instead.Volume generated, separated from capital deployed
An executor response now carries
volume_traded_quotebesidefilled_amount_quote. They are the same number for an executor that places orders — the amount it filled IS its volume — and deliberately different for an LP executor, whose filled amount is the capital it deposited. Depositing capital trades nothing.The surface is pinned, not assumed
The routers are hand-written wrappers with nothing checked at import time, so a route the API renames stays here as a method that looks fine and 404s the first time someone calls it. That is how the whole rate-oracle module survived after the API stopped serving
/rate-oracle/*, and howpull_imagekept sending{name, tag}after the route began requiring{image_name}.The vendored spec plus
test_calls_match_api_spec.pynow check every path+method a router calls, every literal query key, and every literal body key — and fail if the count of unchecked (computed) calls ever swamps the checked ones.Companion PRs
/tradingroutes, signing-route gate, decimal strings on the wirechainNetworkon tokens/pools, volume from feesbin_countonget_pool_infoValidation
337 tests pass. The vendored spec is regenerated from the API's own
openapi.json, so the checks above run against the surface hummingbot-api#221 actually serves.🤖 Generated with Claude Code
https://claude.ai/code/session_01GuR4iDtH4ZfT51rJfs3DhK