Skip to content

Add SimpleSwap swap support - #488

Open
j0ntz wants to merge 4 commits into
masterfrom
jon/simpleswap-swap-support
Open

Add SimpleSwap swap support#488
j0ntz wants to merge 4 commits into
masterfrom
jon/simpleswap-swap-support

Conversation

@j0ntz

@j0ntz j0ntz commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Asana: https://app.asana.com/0/1215088146871429/1217205278669378

Adds SimpleSwap as a centralized swap provider. This is SimpleSwap's own
integration from #481,
recreated on an EdgeApp branch so it can land from here. Their three commits are
preserved with their authorship; the fourth commit is Edge's review pass.

Companion GUI PR: EdgeApp/edge-react-gui#6167.

What the plugin does

src/swap/central/simpleswap.ts calls SimpleSwap API v3 (ranges, estimates,
exchanges) with an x-api-key. It prefers a fixed rate and falls back to a
float rate, checks the pair limits before creating an order, and maps API errors
onto the Edge swap error types. A max quote uses a local fee probe rather than
creating and abandoning a live order. Chain mappings and a mapctl synchronizer
come from SimpleSwap's live currency list, and test/simpleswap.test.ts covers
the mapping for every EdgeCurrencyPluginId plus both quote directions.

Init options: SIMPLESWAP_INIT: { "apiKey": "..." }. The factory throws on an
empty key, so builds without the key skip the plugin.

The Edge review pass (commit 4)

Seven defects. The first four came from driving the plugin against the live API;
the last three came from the reviewer bots on this PR. Each was re-verified
against the live API after the fix:

  • Fixed-rate orders to memo and tag chains failed outright. POST /v3/exchanges with fixed: true returns 500 "check extra_id" whenever the
    destination currency carries hasExtraId (XRP, XLM, and every other tag
    chain). Edge pays out to the user's own non-custodial wallet and has no
    destination tag to send, and an empty or null extraIdTo is rejected the same
    way, so the float flow is the only one those pairs can use. The plugin raised
    the 500 as a hard error and never retried, so every swap to those chains
    failed. The retry now covers any fixed-flow failure, not swap errors alone.
  • A float-flow server error masked the fixed flow's clean SwapCurrencyError,
    so an unsupported pair surfaced as "SimpleSwap returned error code 500". Both
    errors are ranked now and the more actionable one is thrown.
  • denominationToNative results reached the core unrounded. A live ATOM
    quote produced toNativeAmount = 2346482116.51. Minimums round up, maximums
    and receive amounts round down.
  • The provider-echoed amountFrom drove the spend with no bound against the
    requested amount. Both directions are bounded now: a from quote requires the
    echoed source amount to equal the amount the order was created from (above it
    overspends, below it underpays a live order), and a reverse quote rejects a
    destination amount below the pinned receive amount.
  • A numeric deposit tag was dropped. extraIdFrom cleaned through
    asOptional(asString), so a JSON number tag (XRP, XLM, TON) failed the cleaner
    after the order already existed, and tag 0 was lost. It cleans through
    asNumberString now, and an empty tag is treated as absent rather than
    becoming an empty memo.
  • A failure after order creation could open a second order. Everything after
    POST /v3/exchanges returns now runs inside a fatal boundary: the response
    cleaner, the amount arithmetic, the bound checks and the spend construction
    all raise a fatal error that getQuote rethrows instead of retrying. The
    float retry can only run on a failure that happened before the order existed.

Verification

npm run verify passes; the SimpleSwap suite is 26 tests. Driven end to end on
the iOS simulator with every other swap provider disabled locally so the engine
had to route through SimpleSwap: an 80 XLM to XMR swap executed to the success
scene, and the Stellar send carried SimpleSwap's numeric deposit memo.
Screenshots below.

Known gaps, tracked separately

SimpleSwap is receive-only for Zano and Pirate Chain (no sell pair exists in any
direction), and Zcash payouts accept transparent and TEX addresses only, so
there is no unified-address withdrawal. Those are partner-side gaps under
discussion, not plugin defects; the plugin surfaces each as a clean
SwapCurrencyError.

A fixed-rate quote that failed the range check never tried the float
  flow, whose limits can be wider, so users were blocked from a possible
  swap. Float is now attempted after any currency or limit error from the
  fixed flow; if the float flow lacks the pair entirely, the more
  actionable fixed-rate limit error is surfaced instead. Also types the
  catch clauses as unknown.
Pass the estimate's validUntil through ensureInFuture so a stale or
  clock-skewed timestamp can't produce an already-expired quote.
@j0ntz

j0ntz commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence

simpleswap terms modal

simpleswap terms modal

simpleswap quote

simpleswap quote

swap success

swap success

xlm tx with memo

xlm tx with memo

stellar broadcast

stellar broadcast

Captured by the agent's in-app test run (build-and-test).

@j0ntz
j0ntz marked this pull request as ready for review August 19, 2026 23:25
@j0ntz
j0ntz force-pushed the jon/simpleswap-swap-support branch from a7891ec to f3955f2 Compare August 19, 2026 23:25
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f3955f2. Configure here.

Comment thread src/swap/central/simpleswap.ts Outdated
Comment thread src/swap/central/simpleswap.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Agentic security review of SimpleSwap swap support. One medium finding on partner-returned amounts used for the signed spend without matching the locally pinned quote side.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/swap/central/simpleswap.ts Outdated
@j0ntz
j0ntz force-pushed the jon/simpleswap-swap-support branch from f3955f2 to d0da4e9 Compare August 19, 2026 23:35

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agentic security review of SimpleSwap swap support. One remaining medium issue: post-create amount conversion can still throw a non-fatal error and open a second live order.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread src/swap/central/simpleswap.ts Outdated
Four defects found by driving the plugin against SimpleSwap's live API:

- Fixed-rate order creation returns HTTP 500 "check extra_id" for any
  destination chain whose currency record carries hasExtraId (XRP, XLM and
  every other tag chain). Edge pays out to the user's own non-custodial
  wallet and has no destination tag to send, and an empty or null extraIdTo
  is rejected the same way, so the float flow is the only one those pairs
  can use. The retry now covers any fixed-flow failure rather than swap
  errors alone.
- A server error from the float flow masked the fixed flow's clean
  SwapCurrencyError, so an unsupported pair surfaced as "SimpleSwap
  returned error code 500". Both errors are now ranked and the more
  actionable one is thrown.
- denominationToNative results reached the core unrounded, so a provider
  amount carrying more decimals than the asset's denomination produced a
  fractional native amount. Minimums round up, maximums and receive
  amounts round down.
- The provider-echoed amountFrom drove the spend with no bound against the
  requested amount. It is now bounded, and the failure is fatal rather than
  retryable so the created order is never abandoned for a second one.
@j0ntz
j0ntz force-pushed the jon/simpleswap-swap-support branch from d0da4e9 to f3c5315 Compare August 19, 2026 23:44
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.

2 participants