Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

- added: `AGENTS.md` and `.cursor/BUGBOT.md`, recording the swap plugin conventions that recent provider reviews settled.
- added: A test suite for the swap plugin template, covering the max-quote probe, limit handling, the trust boundary on provider amounts, and memo cleaning. A new integration can copy it alongside the template.
- added: HoudiniSwap swap plugin: privacy-routed CEX swaps with forward and reverse (receive-amount) quotes, swap-to-address destinations with destination-tag support for memo chains, and a cached-fixture acceptance suite. Honors `EdgeSwapRequest.privacy`, so a request that does not demand privacy may also take Houdini's standard routes, which are the only ones served between the 10 USD standard floor and the 25 USD private floor. Rate-limited calls back off behind the window the API reports and fail as a rate limit rather than as an unavailable route. Only the exact-out path reports a fixed rate, since that is the only path the provider serves fixed rates on. Token lookups are memoized including misses and expire after ten minutes, so a chain the provider lists but serves no native coin for costs one lookup per window rather than one per quote, and a chain the provider later lists is picked up without restarting the app; a lookup the provider failed to answer is not cached, since a rate limit says nothing about whether the chain is served.
- changed: The swap plugin template now models the max-quote probe, the trust boundary on provider-returned amounts, integer native-amount rounding, and funds-safe memo cleaning, so a new integration starts from them rather than rediscovering them in review.
- changed: `docs/CREATING_AN_EXCHANGE_PLUGIN.md` covers max quotes and amount rounding in depth, and ends with a pre-PR checklist drawn from findings on shipped integrations.
- changed: `checkInvalidTokenIds` takes an `allowSameAsset` option, for a privacy provider whose main flow is routing an asset to itself.

## 2.53.0 (2026-08-18)

Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { makeChangellyPlugin } from './swap/central/changelly'
import { makeChangeNowPlugin } from './swap/central/changenow'
import { makeExolixPlugin } from './swap/central/exolix'
import { makeGodexPlugin } from './swap/central/godex'
import { makeHoudiniPlugin } from './swap/central/houdini'
import { makeLetsExchangePlugin } from './swap/central/letsexchange'
import { makeNexchangePlugin } from './swap/central/nexchange'
import { makeNymPlugin } from './swap/central/nym'
Expand Down Expand Up @@ -40,6 +41,7 @@ const plugins = {
cosmosibc: makeCosmosIbcPlugin,
exolix: makeExolixPlugin,
godex: makeGodexPlugin,
houdini: makeHoudiniPlugin,
letsexchange: makeLetsExchangePlugin,
lifi: makeLifiPlugin,
nexchange: makeNexchangePlugin,
Expand Down
107 changes: 107 additions & 0 deletions src/mappings/houdini.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Maps Edge `EdgeCurrencyPluginId` -> HoudiniSwap chain `shortName` (v2 `GET /chains`),
* or `null` when Houdini has no compatible chain for that asset.
*
* Derived from the live `GET /chains` list intersected with Edge's currency
* pluginIds. Memo-required chains (`Chain.memoNeeded`, e.g. XRP/XLM) are
* supported: the plugin forwards a destination memo as `destinationTag` on
* order creation. The IBC-family chains (coreum/osmosis/axelar) stay `null`
* because Houdini reports no `memoNeeded` flag and a permissive `^.*$` address
* validation for them, so their payout semantics are not yet trustworthy.
*
* This table answers "what does Houdini call this chain", not "does Houdini
* serve it". Whether a chain has a tradable native coin is discovered at
* runtime by `resolveTokenId`, which declines with the same `SwapCurrencyError`
* the whitelist check throws, so a name that turns out to be unserved costs one
* cached lookup rather than a wrong answer. Keeping the two questions apart is
* what lets the table stay correct when Houdini adds or drops a native.
*/

import { EdgeCurrencyPluginId } from '../util/edgeCurrencyPluginIds'

export const houdini = new Map<EdgeCurrencyPluginId, string | null>()
houdini.set('abstract', null)
houdini.set('algorand', 'algorand')
houdini.set('amoy', null)
houdini.set('arbitrum', 'arbitrum')
houdini.set('avalanche', 'avalanche')
houdini.set('axelar', null)
houdini.set('badcoin', null)
houdini.set('base', 'base')
houdini.set('binance', null)
houdini.set('binancesmartchain', 'bsc')
houdini.set('bitcoin', 'bitcoin')
houdini.set('bitcoincash', 'bitcoincash')
houdini.set('bitcoincashtestnet', null)
houdini.set('bitcoingold', null)
houdini.set('bitcoingoldtestnet', null)
houdini.set('bitcoinsv', 'bsv')
houdini.set('bitcointestnet', null)
houdini.set('bitcointestnet4', null)
houdini.set('bobevm', null)
houdini.set('botanix', null)
houdini.set('calibration', null)
houdini.set('cardano', 'cardano')
houdini.set('cardanotestnet', null)
houdini.set('celo', 'celo')
houdini.set('coreum', null)
houdini.set('cosmoshub', 'cosmoshub-4')
houdini.set('dash', 'dash')
houdini.set('digibyte', null)
houdini.set('dogecoin', 'doge')
houdini.set('eboost', null)
houdini.set('ecash', 'eCash')
houdini.set('eos', null)
houdini.set('ethDev', null)
houdini.set('ethereum', 'ethereum')
houdini.set('ethereumclassic', null)
houdini.set('ethereumpow', null)
houdini.set('fantom', 'fantom')
houdini.set('feathercoin', null)
houdini.set('filecoin', null)
houdini.set('filecoinfevm', null)
houdini.set('filecoinfevmcalibration', null)
houdini.set('fio', null)
houdini.set('groestlcoin', null)
houdini.set('hedera', 'hedera')
houdini.set('holesky', null)
houdini.set('hyperevm', 'hyperevm')
houdini.set('liberland', null)
houdini.set('liberlandtestnet', null)
houdini.set('litecoin', 'litecoin')
houdini.set('mayachain', null)
houdini.set('monad', null)
houdini.set('monero', 'monero')
houdini.set('nym', null)
houdini.set('opbnb', 'opbnb')
houdini.set('optimism', 'optimism')
houdini.set('osmosis', null)
houdini.set('piratechain', null)
houdini.set('pivx', 'pivx')
houdini.set('polkadot', 'polkadot')
houdini.set('polygon', 'polygon')
houdini.set('pulsechain', 'pulsechain')
houdini.set('qtum', null)
houdini.set('ravencoin', null)
houdini.set('ripple', 'ripple')
houdini.set('rsk', 'rootstock')
houdini.set('sepolia', null)
houdini.set('smartcash', null)
houdini.set('solana', 'solana')
houdini.set('sonic', 'sonic')
houdini.set('stellar', 'xlm')
houdini.set('sui', 'sui')
houdini.set('suitestnet', null)
houdini.set('telos', 'telos')
houdini.set('tezos', null)
houdini.set('thorchainrune', 'thorchain')
houdini.set('thorchainrunestagenet', null)
houdini.set('ton', 'ton')
houdini.set('tron', 'tron')
houdini.set('ufo', null)
houdini.set('vertcoin', null)
houdini.set('wax', null)
houdini.set('zano', null)
houdini.set('zcash', 'Zcash')
houdini.set('zcoin', null)
houdini.set('zksync', 'zksync-era')
Loading
Loading