Add Swapter swap provider - #475
Conversation
There was a problem hiding this comment.
💡 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".
59a7a92 to
1ad9e59
Compare
1ad9e59 to
2110233
Compare
2110233 to
5235c38
Compare
5235c38 to
3876435
Compare
3876435 to
8ed6ef8
Compare
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.
632629b to
d7a85e5
Compare
d7a85e5 to
4b409a5
Compare
4b409a5 to
d09e981
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d09e981. Configure here.
d09e981 to
441a45e
Compare
📸 Test evidence (review-round re-verification, HEAD 441a45e)swapter quote swap success txn details max skipchecks 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
Asana task
Adds the Swapter centralized swap provider.
Recreates partner PR #455 (by @markovo4, from
markovo4/edge-exchange-plugins) on anEdgeApp branch so it can run CI, rebased from its
v2.46.0merge-base onto currentmaster. The plugin design, mapping and test cases are the partner's work; this branchbrings 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/coinslist. Swapter has renamedseveral networks since the partner branch was cut in May, and nine of the 43 non-null
mappings no longer resolve:
arbitrumARBITRUMARBoptimismOPTIMISMOPpolygonPOLYGONPOLsolanaSOLANASOLethereumpowETHWETHEREUM POW(note the space)bitcoinsvBSVcoreumCOREfioFIOtelosTELOSTELOS(EVM); Edge'stelosis 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.", whilea token quote throws
SwapCurrencyErrorbecause/data/coinsreturns no ticker set forthe unknown network. Verified by driving the plugin against the live API —
polygonatPOLYGONfailsmin-amountwith HTTP 400, and atPOLreturns HTTP 200. Bitcoin SV isdropped rather than remapped to Swapter's
BCHSV:getChainAndTokenCodesderives themainnet ticker from
currencyInfo.currencyCode(BSV), so that pair needs aSPECIAL_MAINNET_CASESentry to work at all.Max swaps now resolve. Every other central plugin routes its quote through
getMaxSwappable; this one did not, so itsquoteFor !== 'from'guard threwSwapCurrencyErrorfor every max ("send all") swap and Swapter silently vanished from theprovider list. It now follows the
nympattern: a quote-only probe order prices feesagainst 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/estimatewas called and cleaned on everyquote, but its response was only logged —
toNativeAmountcomes from thecreateresponse. The probe needs only a
spendInfoto price fees, andcreatereturns theauthoritative amounts, so neither path needs it.
Regenerated
test/partnerJson/swapterMap.jsonfrom/data/coins. The partner fixtureheld 292 networks, 156 of which are not Swapter networks at all (
ERC20,ARBITRUMONE,AVAXCCHAIN, and a bareA), while omitting 72 real ones —POLwas absent entirely.Every other fixture in
test/partnerJson/is a subset of its provider's mapped codes; thisone 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.3upgrade, a newts-nodedevDependency, andtheir
yarn.lockfallout. This branch touches no dependency files.Also registered
swapteralphabetically insrc/index.tsand dropped the whitespace-onlyedits to
partnerJson.test.ts.Testing
verify-repo.shpasses (prepare, eslint,tsc, mocha — 18 partnerJson cases including 3new 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/createand
/v2/swap/estimatereturn HTTP 401 under every documented auth variant, and/data/coins401s whenever anX-API-KEYheader is present at all. (/v2/swap/min-amountis unauthenticated, which is what made the mapping verification above possible.)
Driving the real plugin against the live API with that key gives:
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
createresponse cleaner (asSwapterCreateResponse) hasnever seen a real payload and is trusted from the published docs alone.
Review-round changes (addressing @peachbits)
ceilfor the below-limit native minimum (never understate Swapter's floor) andfloorfor the payouttoNativeAmount(never over-promise the receive amount), replacinground.userSettings.swapTypeselection (the GUI never passes it, which pinned the plugin to float). Swapter 500stype: 'fixed'for pairs it cannot fix, so that create error is suppressed and the float fallback runs.SwapterCreateRejectedsentinel), so a create-then-parse-failure never spawns a second order; unsupported-pair errors propagate directly with no retry; rethrow the fixed error only when float was also cleanly rejected, else surface the float error so a live order is not masked.skipChecks: trueon the max-fee probe so EVM max swaps no longer fail withSpendToSelfError(matches the NYM fix).SPECIAL_MAINNET_CASESpins native TON to Swapter'sGRAMcoin (audited all mapped networks; TON was the only native-coin mismatch).eqwould false-reject 18-decimal orders, and no enforced max exists to guard.Follow-ups
asSwapterCreateResponsevalidated against livecreateresponses.mapctlsynchronizer for Swapter./data/coinsneeds no API key, so it is aclean fit, and it would catch renames like
POLYGON→POLautomatically instead ofsilently dropping a chain. Not done here:
mapctl update-mappingsconstructs everyprovider'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.
swapteris registered but is not yet enabled anywhere — the info server serves noSwapter init options, so the GUI needs
SWAPTER_INITbefore it can surface.Note
Medium Risk
New third-party swap path that creates real deposit orders and builds spends from API responses; behavior aligns with other CEX plugins but live
createpayloads were not verified with an authorized key, and stale network mappings can fail silently at quote time.Overview
Adds Swapter as a new centralized swap provider: plugin registration, Edge→Swapter chain mapping, and partner JSON tests backed by a regenerated
/data/coinsfixture.Quoting follows the same central-plugin pattern as peers:
getMaxSwappablewith a fee probe (user refund address,skipChecksso EVM max swaps don’t hit spend-to-self), then/v2/swap/min-amountand/v2/swap/createfor a single real order—no separate estimate call. It tries fixed rate first and falls back to float only when create is rejected without an order; unsupported pairs map toSwapCurrencyError. Supported assets are refreshed hourly from public/data/coins(no API key on that route). Network codes were corrected against the live API (e.g.POL,ARB,AVAX_C,ETHEREUM POW); native TON usesSPECIAL_MAINNET_CASES(GRAMon Swapter). Onlyfrom/ max (rewritten to from) quotes are supported.SWAPTER_INITis added for tests; the plugin is registered but not enabled in production until init options are served.Reviewed by Cursor Bugbot for commit 441a45e. Bugbot is set up for automated code reviews on this repo. Configure here.