Skip to content

Add Swapter swap provider#475

Open
j0ntz wants to merge 1 commit into
masterfrom
jon/swapter-integration
Open

Add Swapter swap provider#475
j0ntz wants to merge 1 commit into
masterfrom
jon/swapter-integration

Conversation

@j0ntz

@j0ntz j0ntz commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Asana task

Adds the Swapter centralized swap provider.

Recreates partner PR #455 (by @markovo4, from markovo4/edge-exchange-plugins) on an
EdgeApp branch so it can run CI, rebased from its v2.46.0 merge-base onto current
master. The plugin design, mapping and test cases are the partner's work; this branch
brings the integration up to current conventions and fixes what had gone stale.

Asana: https://app.asana.com/1/9976422036640/project/1213880789473005/task/1216571782597915

Changes on top of the partner branch

Chain mappings corrected against the live /data/coins list. Swapter has renamed
several networks since the partner branch was cut in May, and nine of the 43 non-null
mappings no longer resolve:

Edge plugin Partner Live network
arbitrum ARBITRUM ARB
optimism OPTIMISM OP
polygon POLYGON POL
solana SOLANA SOL
ethereumpow ETHW ETHEREUM POW (note the space)
bitcoinsv BSV (none — see below)
coreum CORE (none)
fio FIO (none)
telos TELOS (only TELOS(EVM); Edge's telos is EOSIO)

A stale code here is caught by no type and no test: a mainnet quote still reaches the API
and fails with Swapter's "Deposit coin and network combination does not exists.", while
a token quote throws SwapCurrencyError because /data/coins returns no ticker set for
the unknown network. Verified by driving the plugin against the live API — polygon at
POLYGON fails min-amount with HTTP 400, and at POL returns HTTP 200. Bitcoin SV is
dropped rather than remapped to Swapter's BCHSV: getChainAndTokenCodes derives the
mainnet ticker from currencyInfo.currencyCode (BSV), so that pair needs a
SPECIAL_MAINNET_CASES entry to work at all.

Max swaps now resolve. Every other central plugin routes its quote through
getMaxSwappable; this one did not, so its quoteFor !== 'from' guard threw
SwapCurrencyError for every max ("send all") swap and Swapter silently vanished from the
provider list. It now follows the nym pattern: a quote-only probe order prices fees
against the user's own refund address, so a max swap does not create an order that is
immediately abandoned, and exactly one order is created per quote.

Dropped the estimate round trip. /v2/swap/estimate was called and cleaned on every
quote, but its response was only logged — toNativeAmount comes from the create
response. The probe needs only a spendInfo to price fees, and create returns the
authoritative amounts, so neither path needs it.

Regenerated test/partnerJson/swapterMap.json from /data/coins. The partner fixture
held 292 networks, 156 of which are not Swapter networks at all (ERC20, ARBITRUMONE,
AVAXCCHAIN, and a bare A), while omitting 72 real ones — POL was absent entirely.
Every other fixture in test/partnerJson/ is a subset of its provider's mapped codes; this
one now is too (38 networks, 729 tickers).

Dropped unrelated dependency churn: a self-referential "edge-exchange-plugins": "^2.46.0"
dependency, a typescript ~4.9.5^6.0.3 upgrade, a new ts-node devDependency, and
their yarn.lock fallout. This branch touches no dependency files.

Also registered swapter alphabetically in src/index.ts and dropped the whitespace-only
edits to partnerJson.test.ts.

Testing

verify-repo.sh passes (prepare, eslint, tsc, mocha — 18 partnerJson cases including 3
new Swapter ones, asserting against the regenerated live fixture).

An executed in-app swap is not part of this verification, and the plugin is unproven past
order creation.
The throwaway key on the Asana task is not authorized: /v2/swap/create
and /v2/swap/estimate return HTTP 401 under every documented auth variant, and
/data/coins 401s whenever an X-API-KEY header is present at all. (/v2/swap/min-amount
is unauthenticated, which is what made the mapping verification above possible.)

Driving the real plugin against the live API with that key gives:

GET  /data/coins            -> HTTP 401   (warn + empty ticker map, as designed)
POST /v2/swap/min-amount    -> HTTP 200   {"amount":"0.00116310"}
POST /v2/swap/create        -> HTTP 401   {"info":{"type":"float","refundAddress":"bc1q…"},
                                           "deposit":{"coin":"BTC","network":"BTC","amount":0.1},
                                           "withdraw":{"coin":"ETH","network":"ETH","address":"0x71C7…"}}
=> Error: Swapter returned error code 401

So request construction and the mapping are confirmed end-to-end, and auth is the only
thing between this and an executed swap. A valid Swapter partner key is needed to
sign this off
; until then the create response cleaner (asSwapterCreateResponse) has
never seen a real payload and is trusted from the published docs alone.

Follow-ups

  • Verify a real swap end-to-end once a working key exists, and re-check
    asSwapterCreateResponse against a live create response.
  • Consider a mapctl synchronizer for Swapter. /data/coins needs no API key, so it is a
    clean fit, and it would catch renames like POLYGONPOL automatically instead of
    silently dropping a chain. Not done here: mapctl update-mappings constructs every
    provider's synchronizer eagerly and aborts without all of their API keys, so the
    generated-mapping path could not be run or validated in this environment.
  • swapter is registered but is not yet enabled anywhere — the info server serves no
    Swapter init options, so the GUI needs SWAPTER_INIT before it can surface.

Note

Medium Risk
New third-party swap path that builds real deposit spends and orders; behavior follows existing central plugins but live create responses have not been verified with a production API key.

Overview
Adds Swapter as a new centralized swap provider and registers it in the plugin index.

The plugin quotes via Swapter’s /v2/swap/min-amount and /v2/swap/create APIs (API key required), hydrates supported tokens from public /data/coins without an X-API-KEY header, and maps Edge chains to Swapter network codes in src/mappings/swapter.ts. Quotes are deposit-amount only (quoteFor: 'from'); float vs fixed comes from user settings. Max (“send all”) swaps use the same getMaxSwappable + fee probe pattern as NYM so fee estimation does not create a stray order before the real create call. Unsupported pairs (API error codes 1/2) surface as SwapCurrencyError; deposit memos accept numeric or string tags.

Also adds SWAPTER_INIT in test config, regenerates test/partnerJson/swapterMap.json, extends partnerJson.test with Swapter cases, and notes the provider in CHANGELOG.

Reviewed by Cursor Bugbot for commit 5235c38. Bugbot is set up for automated code reviews on this repo. Configure here.

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59a7a92508

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/swap/central/swapter.ts Outdated
Comment thread src/swap/central/swapter.ts Outdated
@j0ntz
j0ntz force-pushed the jon/swapter-integration branch from 59a7a92 to 1ad9e59 Compare July 15, 2026 00:19
Comment thread src/swap/central/swapter.ts
Comment thread src/mappings/swapter.ts
@j0ntz
j0ntz force-pushed the jon/swapter-integration branch from 1ad9e59 to 2110233 Compare July 15, 2026 00:23

@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 default effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

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 2110233. Configure here.

Comment thread src/swap/central/swapter.ts
Comment thread src/swap/central/swapter.ts
Recreates partner PR #455 (author markovo4) on an EdgeApp branch, rebased
onto master, with the integration brought to current plugin conventions:

- Route quotes through getMaxSwappable so max swaps resolve, using a
  quote-only probe order that prices fees against the user's own refund
  address rather than creating an order that would be abandoned.
- Drop the estimate round trip. Its response was fetched and discarded;
  create returns the authoritative amounts and the probe only needs a
  spendInfo to price fees.
- Correct nine chain mappings against the live /data/coins network list.
  Swapter has renamed several networks since the partner branch was cut
  (ARBITRUM to ARB, OPTIMISM to OP, POLYGON to POL, SOLANA to SOL,
  ETHW to 'ETHEREUM POW'), which silently broke those chains. Bitcoin SV,
  Coreum, FIO and Telos are no longer offered in a form Edge can use, so
  they map to null.
- Regenerate the partnerJson fixture from /data/coins so it reflects
  Swapter's real network identifiers and stays a subset of the mapping.

The partner branch also added a self-referential edge-exchange-plugins
dependency and a TypeScript 6 upgrade; both are dropped as unrelated.
@j0ntz
j0ntz force-pushed the jon/swapter-integration branch from 2110233 to 5235c38 Compare July 15, 2026 00:28
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