Add StealthEX swap plugin - #485
Conversation
adc61e1 to
b442b38
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
b442b38 to
5a63ddd
Compare
5a63ddd to
8ae9a4d
Compare
paullinator
left a comment
There was a problem hiding this comment.
Review of the StealthEX swap plugin. Six items, numbered for reference: 1 (must fix), 2 (warning), 3-6 (must fix). Item 1 is the main one and includes a suggested implementation approach.
- Must fix - token identity is guessed from the Edge currency code instead of resolved from the contract address.
- Warning - EVM chains are selected through a static network table rather than the numeric chain ID.
- Must fix - resolved currencies are cached for the lifetime of the plugin with no expiry.
- Must fix - every currency lookup failure is reported as an unsupported pair.
- Must fix -
/rates/rangemapsNotAlloweddifferently from the estimate and exchange paths. - Must fix - fixed-rate orders are created without requiring
rate_id.
b9d541d to
83c9640
Compare
Repo docs added on this branch
Issues 1, 3, 4, 5 and 6 of this review are those invariants, and this round's fixup brings the plugin in line with each. StealthEX API compliance and gap reportIssue 2 (numeric EVM chain id) cannot be fixed on this side, so it is written up rather than coded, together with every other gap against https://gist.github.com/j0ntz/ae71f231a5939489cf4611325058c00c The gaps, each verified against the live API on 2026-08-18:
Order identification and the unauthenticated status page pass as-is. |
|
All six items handled. 1: assets now resolve from the full |
4978ebe to
125c91a
Compare
7299094 to
6bd3f85
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6bd3f85. Configure here.
StealthEX identifies assets by a symbol plus a network, so the chain mapping spells out both halves for a chain's native asset and the network its tokens live on. Token symbols are not unique across listings, so each side of a quote is confirmed against StealthEX's own currency record before quoting. Quotes use fixed rates when the route offers them, which also covers quoting by the amount the user wants to receive, and fall back to floating rates otherwise.
…ntegration surfaced `.cursor/BUGBOT.md` gains per-endpoint auth, catalog cache expiry, and the scope a fixed-then-floating fallback is allowed to cover. Each one cost a review round on this integration and none of them fails loudly. `docs/API_REQUIREMENTS.md` states the rate-type requirement behind the last of those: a provider that publishes rate types per ASSET but not per ROUTE leaves the client attempting a type and inferring support from the failure, which reads the same as an outage.
6bd3f85 to
2944889
Compare






CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Description
Adds StealthEX as a centralized swap provider, on the v4 API.
Asana: https://app.asana.com/0/1215088146871429/1217498055202092
Asset identity. StealthEX names every asset by a
symbolplus anetwork, sosrc/mappings/stealthex.tsrecords both halves per chain: the symbol and network of the chain's native asset, plus the network its tokens live on. A chain's native asset usually sits onmainnet, but plenty do not (BNB onbsc, AVAX onavax-c, WAXP onwax, OSMO oncosmos, ETH on each L2's own network), which is why the mapping spells out both rather than deriving one from the other.Token identity comes from the CONTRACT ADDRESS, never from the Edge currency code. The plugin pulls StealthEX's full
/v4/currenciescatalog (paged at 250, about a thousand listings, page 0 then batches of four), indexes it by provider network, and canonicalizes each listing'scontract_addressthrough that wallet's owncurrencyConfig.getTokenIdso the index is keyed by Edge tokenId. Casing differences and junk values in the provider's contract field drop out of that step. A native asset must match the mapped symbol on its network AND carry no contract address, so a token listing can never be resolved for a native swap. A token StealthEX lists without a contract address (USDC on Tron among them) stays unsupported rather than quoted on a ticker match.The catalog is cached for an hour and replaced atomically, only after every page arrives non-empty. A failed or empty refresh keeps the previous good index and leaves the stamp expired, so the next quote retries instead of caching an outage. The expiry is not theoretical: ARRR and ZANO were both listed on 2026-08-14 and are absent from the catalog on 2026-08-18.
Rate type. Quotes use a fixed rate when both assets support one, which is also what makes "quote by the amount I want to receive" work (StealthEX only offers reversed estimation on fixed rates). Both assets advertising fixed support does not guarantee the pair has a fixed route, so a missing route falls back to a floating quote with
isEstimate: true.fetchSwapQuoterunscheckInvalidTokenIdsfirst, so the shared same-asset guard and the default invalid-token list apply here as they do in the sibling central plugins.Other notes. Requests go through
io.fetchCors: StealthEX blocks browser-origin requests, and swap plugins run inside a WebView. Limits come fromPOST /v4/rates/rangeand map toSwapBelowLimitError/SwapAboveLimitError;NoPair/NoExchangeRoute/RouteIsDisabled/MarketUnavailablemap toSwapCurrencyErrorandNotAllowedto a reasonlessSwapPermissionError: StealthEX returnsNotAllowedfor causes it does not name (it was observed refusing a route minutes after the identical call succeeded), so claiming a geographic restriction would be a guess.Telos and Monad are listed by StealthEX but mapped to
null: neither listing carries an address format, so which address space they expect cannot be confirmed, and a wrong guess loses funds.Errors. One helper maps a StealthEX error kind to its Edge error for every call site (
/rates/range,/rates/estimated-amount,/exchanges), so a kind cannot mean an unsupported pair on one path and a generic failure on another. A catalog outage raises a real provider error rather thanSwapCurrencyError, so it keeps its error ranking instead of reading as an unsupported pair.Fixed orders always carry a locked rate. A fixed estimate that comes back without
rate.idis treated as no fixed route: a direct quote falls back to floating with the range, limit check and estimate re-run for that rate, soisEstimatealways describes the order actually created.Max quotes create one order, not two.
getMaxSwappableruns the quote function twice, so the quote path is split:fetchQuoteresolves the route, checks limits and returns both addresses without creating anything;fetchProbeOrderbuilds the probespendInfofrom it (the user's own from-address,skipChecks: true) sogetMaxSpendablecan price the network fee; andfetchSwapQuoteInneris the only caller that POSTs/exchanges. Above the maximum the probe quotes the range maximum instead of throwing, because StealthEX rejects an out-of-range amount at/rates/estimated-amountwith400 InvalidAmountand a max swap that fits once the fee is subtracted would otherwise never get priced; the real quote still raisesSwapAboveLimitError. Measured against the live API, a max quote issued two orders before this and one after.Trust boundary. The deposit amount StealthEX returns becomes a signed spend, so a
fromquote (a max quote arrives here as one) rejects an order whose deposit exceeds the requested amount. A reversed quote pins the receive side, so there is nothing local to bound the source against.This branch is rebased onto #482, which landed
AGENTS.md,.cursor/BUGBOT.md, the reworked plugin guide and the plugin template. The overlapping docs commit on this branch was reduced to what #482 does not already cover: three review rules in.cursor/BUGBOT.md(per-endpoint auth, catalog cache expiry, and the scope a fixed-then-floating fallback is allowed to cover) and the rate-type requirement indocs/API_REQUIREMENTS.mdthat the last of those argues back to the provider.A compliance and gap report for StealthEX v4 against
docs/API_REQUIREMENTS.mdis published at https://gist.github.com/j0ntz/ae71f231a5939489cf4611325058c00cCompanion GUI PR wires the plugin up: EdgeApp/edge-react-gui#6160
Note
Medium Risk
New swap path handles real deposits and signed spends with provider-returned amounts; mistakes in mapping, limits, rate fallback, or the trust boundary could misquote or over-debit users, though the implementation follows established central-plugin patterns.
Overview
Adds StealthEX as a new centralized swap provider on the v4 API, registered in
src/index.tsasstealthex.The plugin (
src/swap/central/stealthex.ts) pages and caches the/currenciescatalog (hourly TTL, batched fetches), resolves assets by contract address via a per-chain token index, then quotes through range → estimate → a single/exchangesorder. It prefers fixed rates when both assets advertise them and falls back to floating only when the fixed route is missing (not on limit/pair errors);quoteFor: 'to'uses reversed estimation on fixed routes only. Max swaps use a quote-only probe (skipChecks, no order) before one real order; deposit amounts from the provider are floored to integer native units and capped against the requested from-amount on from quotes.Chain coverage lives in
src/mappings/stealthex.ts(symbol + network per chain, explicitnullwhere support is unconfirmed).Docs and review guidance are updated: CHANGELOG, new Rate types section in
docs/API_REQUIREMENTS.md, and BUGBOT rules for per-endpoint auth, catalog TTL, and scoped rate-type fallback.Reviewed by Cursor Bugbot for commit 2944889. Bugbot is set up for automated code reviews on this repo. Configure here.