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
|
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.
6bd3f85 to
2944889
Compare
ff4cc3d is the only revision of this document that relaxed anything, and none of the changes below appear in its commit message, which describes its scope as mapping errors to edge-core-js classes, naming the integration points, and expanding the Reporting API section. The reference point throughout is Paul's "2025 Exchange Provider API Requirements" doc, which ef51604 transcribes almost verbatim. Native units are now the single convention, stated once: The document had drifted into per-section conventions. Section 3 asked for display units and called itself "the one place the native-unit preference does not apply", section 6 asked for display units citing StandardTx's asSafeNumber typing, and sections 4 and 6 asked for native units. Amount Representation now states outright that it governs every amount field and that no section carries a different one, sections 3, 4 and 6 all read the same, and the asSafeNumber fact moves to where it belongs: it constrains Edge's storage, not the provider's wire format, and the reporting plugin already converts. The source doc states no convention at all. Its only amounts example sits inside the error-handling section, whose purpose is to show that every applicable error returns in one response, so the display units in it are incidental to the point rather than a decision. Reading a convention out of an incidental example is what drifted twice. Section 3's example also loses a 1000x error. The December example was 9.789 BTC against 1,000,000 USDT, two sides of the same $1M cap. #442 converted both correctly (978900000 satoshis, 1000000000000 micro-units) but labelled the satoshi value "0.009789 BTC" in its comment. ff4cc3d resolved the value/comment mismatch by trusting the comment, rewriting the limit as the display string "0.009789", which is roughly $1,000 against a 1,000,000 USDT destination. The value is restored and the comment corrected. Six requirements weakened in ff4cc3d, each weaker than both #442 and the source doc, are restored: - Section 1, EVM chainId: back to must, with the reason the source doc gives and ff4cc3d dropped along with the obligation. Numeric chain ids let a newly listed EVM work the day it is added, with no new mapping entry. - Section 6, pagination: back to a required start date, end date and record count. ff4cc3d made date filtering one of three acceptable approaches, so an offset-only API conformed. - Section 6, required fields: depositAddress/payoutAddress, depositTxid/payoutTxid, depositTokenId/payoutTokenId and depositEvmChainId/payoutEvmChainId are required again. - Section 6, chain identifier: ff4cc3d rewrote the table around StandardTx names and lost sourceNetwork/destinationNetwork with no replacement row, leaving tokenId as the only asset identifier and no way to tell which chain a contract address belongs to. StandardTx has the field as depositChainPluginId/payoutChainPluginId. - Section 9: the authKey is Edge-generated and cryptographically random again. ff4cc3d replaced it with "a unique per-user identifier" and listed provider-created sessions as acceptable, inverting who issues it. - Section 14: accepting a return URI is the requirement again, flat, where ff4cc3d had listed it as one of three equal options. It is not a choice between equivalents. Any step taking a card payment, Apple Pay or Google Pay has to run in the system browser, where Edge cannot observe the page and a registered redirect is the only route back. The observable signals ff4cc3d listed alongside it work only for the non-payment steps Edge shows in its own WebView, and the section now says which is which. Every example now carries native amounts annotated with their display equivalent, and native amounts are specified as JSON strings, since a high-decimal asset in native units runs past the safe integer range. AGENTS.md and .cursor/BUGBOT.md pick up the consequences. AGENTS.md names this doc as pre-implementation reading, both in the docs list and at the top of "Starting a new provider", where the point is that a gap you are about to work around may be the provider's to fix rather than yours. Both files also record what the doc is not: it states what Edge asks partners for, not what any given provider sends. Most existing integrations predate the request and send decimals, so the doc settles nothing in a units argument about a specific provider, and a live response still does. docs/CREATING_AN_EXCHANGE_PLUGIN.md carried the same defect, in the block plugin authors copy. Its Step 6 limit sample called denominationToNative unconditionally and unrounded, then derived one nativeLimit and threw it as both SwapBelowLimitError and SwapAboveLimitError, so no single rounding direction could have been right for it. It now selects the side, rounds a minimum up and a maximum down, and says in a comment that the unit convention has to be confirmed against a live response. Step 5 gains the same caveat. That sample also failed the guide's own pre-PR checklist, which already required every denominationToNative result to be rounded and minimums to round up. The two documents state different things and had drifted into each other's job. API_REQUIREMENTS.md states what Edge asks a partner to send, so it can name one convention. CREATING_AN_EXCHANGE_PLUGIN.md states what plugin code does with what arrives, so it cannot: compliance is not guaranteed, and most existing integrations predate the request. Folded in from #485, which drops its documentation commits: section 4 gains a rate-types requirement, and BUGBOT.md gains per-endpoint auth, catalog cache expiry, and the scope a fixed-then-floating fallback is allowed to cover. The rate-types requirement is an addition rather than a restoration. Neither the source doc nor #442 says anything about rate types, and it creates a new must on partners: a provider publishing rate types per asset but not per route leaves the client attempting a type and inferring support from the failure, which is indistinguishable from an outage. The StealthEX plugin on that branch confirms the Step 6 fix rather than motivating it. It already rounds its below-limit with ceil and its above-limit with floor, which is what the corrected sample now shows and what the old one did not. CHANGELOG gains entries for both documents. This repo lists doc-only work there: two of the four Unreleased entries are doc-only today. Both documents also get a prose pass against the house writing rules, which neither had had. 16 em dashes are replaced with commas, colons or sentence breaks, including one inside a code comment that a fence hid from the linter. Gone with them: two "Additionally" openers, two "What matters is that" forward references, three count-announcement openers, "load-bearing", and a run of seven consecutive bullets that each carried mid-prose bold, so none of it landed. Headings move to sentence case in both files, and the plugin guide's opening line stops restating its own title. Its table of contents was also missing the pre-PR checklist. No requirement changes in that pass, every in-document anchor still resolves, and no-slop-lint.sh returns clean on all four files.
ff4cc3d is the only revision of this document that relaxed anything, and none of the changes below appear in its commit message, which describes its scope as mapping errors to edge-core-js classes, naming the integration points, and expanding the Reporting API section. The reference point throughout is Paul's "2025 Exchange Provider API Requirements" doc, which ef51604 transcribes almost verbatim. Native units are now the single convention, stated once: The document had drifted into per-section conventions. Section 3 asked for display units and called itself "the one place the native-unit preference does not apply", section 6 asked for display units citing StandardTx's asSafeNumber typing, and sections 4 and 6 asked for native units. Amount Representation now states outright that it governs every amount field and that no section carries a different one, sections 3, 4 and 6 all read the same, and the asSafeNumber fact moves to where it belongs: it constrains Edge's storage, not the provider's wire format, and the reporting plugin already converts. The source doc states no convention at all. Its only amounts example sits inside the error-handling section, whose purpose is to show that every applicable error returns in one response, so the display units in it are incidental to the point rather than a decision. Reading a convention out of an incidental example is what drifted twice. Section 3's example also loses a 1000x error. The December example was 9.789 BTC against 1,000,000 USDT, two sides of the same $1M cap. #442 converted both correctly (978900000 satoshis, 1000000000000 micro-units) but labelled the satoshi value "0.009789 BTC" in its comment. ff4cc3d resolved the value/comment mismatch by trusting the comment, rewriting the limit as the display string "0.009789", which is roughly $1,000 against a 1,000,000 USDT destination. The value is restored and the comment corrected. Six requirements weakened in ff4cc3d, each weaker than both #442 and the source doc, are restored: - Section 1, EVM chainId: back to must, with the reason the source doc gives and ff4cc3d dropped along with the obligation. Numeric chain ids let a newly listed EVM work the day it is added, with no new mapping entry. - Section 6, pagination: back to a required start date, end date and record count. ff4cc3d made date filtering one of three acceptable approaches, so an offset-only API conformed. - Section 6, required fields: depositAddress/payoutAddress, depositTxid/payoutTxid, depositTokenId/payoutTokenId and depositEvmChainId/payoutEvmChainId are required again. - Section 6, chain identifier: ff4cc3d rewrote the table around StandardTx names and lost sourceNetwork/destinationNetwork with no replacement row, leaving tokenId as the only asset identifier and no way to tell which chain a contract address belongs to. StandardTx has the field as depositChainPluginId/payoutChainPluginId. - Section 9: the authKey is Edge-generated and cryptographically random again. ff4cc3d replaced it with "a unique per-user identifier" and listed provider-created sessions as acceptable, inverting who issues it. - Section 14: accepting a return URI is the requirement again, flat, where ff4cc3d had listed it as one of three equal options. It is not a choice between equivalents. Any step taking a card payment, Apple Pay or Google Pay has to run in the system browser, where Edge cannot observe the page and a registered redirect is the only route back. The observable signals ff4cc3d listed alongside it work only for the non-payment steps Edge shows in its own WebView, and the section now says which is which. Every example now carries native amounts annotated with their display equivalent, and native amounts are specified as JSON strings, since a high-decimal asset in native units runs past the safe integer range. AGENTS.md and .cursor/BUGBOT.md pick up the consequences. AGENTS.md names this doc as pre-implementation reading, both in the docs list and at the top of "Starting a new provider", where the point is that a gap you are about to work around may be the provider's to fix rather than yours. Both files also record what the doc is not: it states what Edge asks partners for, not what any given provider sends. Most existing integrations predate the request and send decimals, so the doc settles nothing in a units argument about a specific provider, and a live response still does. docs/CREATING_AN_EXCHANGE_PLUGIN.md carried the same defect, in the block plugin authors copy. Its Step 6 limit sample called denominationToNative unconditionally and unrounded, then derived one nativeLimit and threw it as both SwapBelowLimitError and SwapAboveLimitError, so no single rounding direction could have been right for it. It now selects the side, rounds a minimum up and a maximum down, and says in a comment that the unit convention has to be confirmed against a live response. Step 5 gains the same caveat. That sample also failed the guide's own pre-PR checklist, which already required every denominationToNative result to be rounded and minimums to round up. The two documents state different things and had drifted into each other's job. API_REQUIREMENTS.md states what Edge asks a partner to send, so it can name one convention. CREATING_AN_EXCHANGE_PLUGIN.md states what plugin code does with what arrives, so it cannot: compliance is not guaranteed, and most existing integrations predate the request. Folded in from #485, which drops its documentation commits: section 4 gains a rate-types requirement, and BUGBOT.md gains per-endpoint auth, catalog cache expiry, and the scope a fixed-then-floating fallback is allowed to cover. The rate-types requirement is an addition rather than a restoration. Neither the source doc nor #442 says anything about rate types, and it creates a new must on partners: a provider publishing rate types per asset but not per route leaves the client attempting a type and inferring support from the failure, which is indistinguishable from an outage. The StealthEX plugin on that branch confirms the Step 6 fix rather than motivating it. It already rounds its below-limit with ceil and its above-limit with floor, which is what the corrected sample now shows and what the old one did not. Both documents also get a prose pass against the house writing rules, which neither had had. 16 em dashes are replaced with commas, colons or sentence breaks, including one inside a code comment that a fence hid from the linter. Gone with them: two "Additionally" openers, two "What matters is that" forward references, three count-announcement openers, "load-bearing", and a run of seven consecutive bullets that each carried mid-prose bold, so none of it landed. Headings move to sentence case in both files, and the plugin guide's opening line stops restating its own title. Its table of contents was also missing the pre-PR checklist. No requirement changes in that pass, every in-document anchor still resolves, and no-slop-lint.sh returns clean on all four files.
ff4cc3d is the only revision of this document that relaxed anything, and none of the changes below appear in its commit message, which describes its scope as mapping errors to edge-core-js classes, naming the integration points, and expanding the Reporting API section. The reference point throughout is Paul's "2025 Exchange Provider API Requirements" doc, which ef51604 transcribes almost verbatim. Native units are now the single convention, stated once: The document had drifted into per-section conventions. Section 3 asked for display units and called itself "the one place the native-unit preference does not apply", section 6 asked for display units citing StandardTx's asSafeNumber typing, and sections 4 and 6 asked for native units. Amount Representation now states outright that it governs every amount field and that no section carries a different one, sections 3, 4 and 6 all read the same, and the asSafeNumber fact moves to where it belongs: it constrains Edge's storage, not the provider's wire format, and the reporting plugin already converts. The source doc states no convention at all. Its only amounts example sits inside the error-handling section, whose purpose is to show that every applicable error returns in one response, so the display units in it are incidental to the point rather than a decision. Reading a convention out of an incidental example is what drifted twice. Section 3's example also loses a 1000x error. The December example was 9.789 BTC against 1,000,000 USDT, two sides of the same $1M cap. #442 converted both correctly (978900000 satoshis, 1000000000000 micro-units) but labelled the satoshi value "0.009789 BTC" in its comment. ff4cc3d resolved the value/comment mismatch by trusting the comment, rewriting the limit as the display string "0.009789", which is roughly $1,000 against a 1,000,000 USDT destination. The value is restored and the comment corrected. Six requirements weakened in ff4cc3d, each weaker than both #442 and the source doc, are restored: - Section 1, EVM chainId: back to must, with the reason the source doc gives and ff4cc3d dropped along with the obligation. Numeric chain ids let a newly listed EVM work the day it is added, with no new mapping entry. - Section 6, pagination: back to a required start date, end date and record count. ff4cc3d made date filtering one of three acceptable approaches, so an offset-only API conformed. - Section 6, required fields: depositAddress/payoutAddress, depositTxid/payoutTxid, depositTokenId/payoutTokenId and depositEvmChainId/payoutEvmChainId are required again. - Section 6, chain identifier: ff4cc3d rewrote the table around StandardTx names and lost sourceNetwork/destinationNetwork with no replacement row, leaving tokenId as the only asset identifier and no way to tell which chain a contract address belongs to. StandardTx has the field as depositChainPluginId/payoutChainPluginId. - Section 9: the authKey is Edge-generated and cryptographically random again. ff4cc3d replaced it with "a unique per-user identifier" and listed provider-created sessions as acceptable, inverting who issues it. - Section 14: accepting a return URI is the requirement again, flat, where ff4cc3d had listed it as one of three equal options. It is not a choice between equivalents. Any step taking a card payment, Apple Pay or Google Pay has to run in the system browser, where Edge cannot observe the page and a registered redirect is the only route back. The observable signals ff4cc3d listed alongside it work only for the non-payment steps Edge shows in its own WebView, and the section now says which is which. Every example now carries native amounts annotated with their display equivalent, and native amounts are specified as JSON strings, since a high-decimal asset in native units runs past the safe integer range. AGENTS.md and .cursor/BUGBOT.md pick up the consequences. AGENTS.md names this doc as pre-implementation reading, both in the docs list and at the top of "Starting a new provider", where the point is that a gap you are about to work around may be the provider's to fix rather than yours. Both files also record what the doc is not: it states what Edge asks partners for, not what any given provider sends. Most existing integrations predate the request and send decimals, so the doc settles nothing in a units argument about a specific provider, and a live response still does. docs/CREATING_AN_EXCHANGE_PLUGIN.md carried the same defect, in the block plugin authors copy. Its Step 6 limit sample called denominationToNative unconditionally and unrounded, then derived one nativeLimit and threw it as both SwapBelowLimitError and SwapAboveLimitError, so no single rounding direction could have been right for it. It now selects the side, rounds a minimum up and a maximum down, and says in a comment that the unit convention has to be confirmed against a live response. Step 5 gains the same caveat. That sample also failed the guide's own pre-PR checklist, which already required every denominationToNative result to be rounded and minimums to round up. The two documents state different things and had drifted into each other's job. API_REQUIREMENTS.md states what Edge asks a partner to send, so it can name one convention. CREATING_AN_EXCHANGE_PLUGIN.md states what plugin code does with what arrives, so it cannot: compliance is not guaranteed, and most existing integrations predate the request. Folded in from #485, which drops its documentation commits: section 4 gains a rate-types requirement, and BUGBOT.md gains per-endpoint auth, catalog cache expiry, and the scope a fixed-then-floating fallback is allowed to cover. The rate-types requirement is an addition rather than a restoration. Neither the source doc nor #442 says anything about rate types, and it creates a new must on partners: a provider publishing rate types per asset but not per route leaves the client attempting a type and inferring support from the failure, which is indistinguishable from an outage. The StealthEX plugin on that branch confirms the Step 6 fix rather than motivating it. It already rounds its below-limit with ceil and its above-limit with floor, which is what the corrected sample now shows and what the old one did not. Both documents also get a prose pass against the house writing rules, which neither had had. 16 em dashes are replaced with commas, colons or sentence breaks, including one inside a code comment that a fence hid from the linter. Gone with them: two "Additionally" openers, two "What matters is that" forward references, three count-announcement openers, "load-bearing", and a run of seven consecutive bullets that each carried mid-prose bold, so none of it landed. Headings move to sentence case in both files, and the plugin guide's opening line stops restating its own title. Its table of contents was also missing the pre-PR checklist. No requirement changes in that pass, every in-document anchor still resolves, and no-slop-lint.sh returns clean on all four files.
2944889 to
8ec625a
Compare
Adherence to #489The plugin is written against Three of those rules came out of this integration and are in #489 rather than here:
Where #489 is stricter than the doc this plugin was first written against, the plugin already complies:
The compliance and gap report is regenerated against #489 and re-verified against the live API today: https://gist.github.com/j0ntz/ae71f231a5939489cf4611325058c00c Section 2 (order identification) passes. Sections 1, 3, 4 and 5 are gaps on the StealthEX side, section 6 is partial, and section 7 needs a written answer from StealthEX rather than a code change. Issue 2 from the review is the section 1 gap. |
📸 Test evidence: funded activation on ALGO, XLM and SOLstealthex quote fresh algo stealthex swap success fresh algo fresh algo wallet funded xlm min balance notice stealthex quote fresh xlm stealthex swap success fresh xlm fresh xlm wallet funded stealthex quote fresh sol stealthex swap success fresh sol fresh sol wallet funded fresh hedera placeholder address Captured by the agent's in-app test run (build-and-test). |




















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.The review rules and the API requirement this integration surfaced are in #489: per-endpoint auth, catalog cache expiry, the scope a fixed-then-floating fallback is allowed to cover, and the per-route rate-type reporting StealthEX does not publish.
A compliance and gap report for StealthEX v4 against
docs/API_REQUIREMENTS.mdis published at https://gist.github.com/j0ntz/ae71f231a5939489cf4611325058c00cSection 7 of that report, account activation, now reads Pass, settled with real funded swaps on every reserve network Edge and StealthEX both support: XRP (26.337 TRX for 6.1363 XRP), ALGO (0.0035 ETH on Base for 87.20 ALGO), XLM (1968.5 DGB for 42.697 XLM) and SOL (0.0036226 ETH on Base for 0.09283 SOL). Each destination wallet was created in Edge for the test and did not exist on chain when the swap was placed; each payout created the account at exactly the quoted amount, with nothing needed from Edge or from the user. StealthEX also pre-checks the destination on XRP, where a payout below the 1 XRP base reserve to an unactivated address is refused outright (
NotAllowed, "This address can not be used") while the same payout to an activated address is accepted. That pre-check is chain-specific: Algorand accepts an order whose payout would land below its 0.1 ALGO minimum balance, and every Stellar route already prices its minimum above the 1 XLM reserve, so the case cannot be ordered there at all. Hedera stays untestable from Edge, because a fresh Edge Hedera wallet has no account id to receive into. The one wart is that the refusal reuses the catch-allNotAllowedkind, so this plugin can only surface it as a reasonlessSwapPermissionError; that is filed as an ask under the report's error-handling section.Companion 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.