Skip to content

Commit 3767f2a

Browse files
committed
Add Houdini destination-chain metadata and plugin registration
HOUDINI_CHAINS snapshots Houdini's GET /chains intersected with Edge currency pluginIds (per-chain address validation regex + memoNeeded), mirroring the edge-exchange-plugins chain mapping. Register the houdini swap plugin through HOUDINI_INIT env config like other providers.
1 parent 1b38595 commit 3767f2a

3 files changed

Lines changed: 283 additions & 0 deletions

File tree

src/envConfig.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,12 @@ export const asEnvConfig = asObject({
322322
),
323323
HOLESKY_INIT: asCorePluginInit(asEvmApiKeys),
324324
HEDERA_INIT: asOptional(asBoolean, true),
325+
HOUDINI_INIT: asCorePluginInit(
326+
asObject({
327+
apiKey: asOptional(asString, ''),
328+
apiSecret: asOptional(asString, '')
329+
}).withRest
330+
),
325331
HYPEREVM_INIT: asCorePluginInit(asEvmApiKeys),
326332
LIBERLAND_INIT: asOptional(asBoolean, true),
327333
LIFI_INIT: asCorePluginInit(

src/util/corePlugins.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export const swapPlugins = {
9494
changenow: ENV.CHANGE_NOW_INIT,
9595
exolix: ENV.EXOLIX_INIT,
9696
godex: ENV.GODEX_INIT,
97+
houdini: ENV.HOUDINI_INIT,
9798
lifi: ENV.LIFI_INIT,
9899
letsexchange: ENV.LETSEXCHANGE_INIT,
99100
nexchange: ENV.NEXCHANGE_INIT,

src/util/houdiniChains.ts

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import type { EdgeTokenId } from 'edge-core-js'
2+
3+
/**
4+
* A destination chain HoudiniSwap can pay out to, keyed by the Edge currency
5+
* pluginId. `addressValidation` is Houdini's own per-chain regex, reused for
6+
* client-side validation of pasted destination addresses. `memoNeeded` chains
7+
* show a destination-tag row whose value rides `toAddressInfo.toMemos` to the
8+
* plugin and onward as `destinationTag` on order creation.
9+
*/
10+
export interface HoudiniChain {
11+
pluginId: string
12+
houdiniShortName: string
13+
memoNeeded: boolean
14+
addressValidation: RegExp
15+
}
16+
17+
/**
18+
* Snapshot of Houdini's `GET /chains` (v2 partner API, fetched 2026-07-02)
19+
* intersected with Edge's currency pluginIds, mirroring the
20+
* edge-exchange-plugins Houdini chain mapping. IBC-family chains are excluded
21+
* there (no trustworthy memo metadata), so they are absent here too. A
22+
* follow-up can source this dynamically from the API once chain metadata is
23+
* exposed through the swap plugin.
24+
*/
25+
export const HOUDINI_CHAINS: HoudiniChain[] = [
26+
{
27+
pluginId: 'algorand',
28+
houdiniShortName: 'algorand',
29+
memoNeeded: false,
30+
addressValidation: /^[A-Z0-9]{58,58}$/
31+
},
32+
{
33+
pluginId: 'arbitrum',
34+
houdiniShortName: 'arbitrum',
35+
memoNeeded: false,
36+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
37+
},
38+
{
39+
pluginId: 'avalanche',
40+
houdiniShortName: 'avalanche',
41+
memoNeeded: false,
42+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
43+
},
44+
{
45+
pluginId: 'base',
46+
houdiniShortName: 'base',
47+
memoNeeded: false,
48+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
49+
},
50+
{
51+
pluginId: 'binancesmartchain',
52+
houdiniShortName: 'bsc',
53+
memoNeeded: false,
54+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
55+
},
56+
{
57+
pluginId: 'bitcoin',
58+
houdiniShortName: 'bitcoin',
59+
memoNeeded: false,
60+
addressValidation:
61+
/^([13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{39}|bc1[a-z0-9]{59})$/
62+
},
63+
{
64+
pluginId: 'bitcoincash',
65+
houdiniShortName: 'bitcoincash',
66+
memoNeeded: false,
67+
addressValidation:
68+
/^([13][a-km-zA-HJ-NP-Z1-9]{25,34})$|^((bitcoincash:)?(q|p)[a-z0-9]{41})$|^((BITCOINCASH:)?(Q|P)[A-Z0-9]{41})$/
69+
},
70+
{
71+
pluginId: 'bitcoinsv',
72+
houdiniShortName: 'bsv',
73+
memoNeeded: false,
74+
addressValidation: /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/
75+
},
76+
{
77+
pluginId: 'cardano',
78+
houdiniShortName: 'cardano',
79+
memoNeeded: false,
80+
addressValidation:
81+
/^(([1-9A-HJ-NP-Za-km-z]{59})|([0-9A-Za-z]{100,104})|([0-9a-fA-F]{64}))$|^(addr)[0-9A-Za-z]{45,65}$|^[a-zA-z0-9]*|[0-9A-Za-z]{45,65}$/
82+
},
83+
{
84+
pluginId: 'celo',
85+
houdiniShortName: 'celo',
86+
memoNeeded: false,
87+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
88+
},
89+
{
90+
pluginId: 'cosmoshub',
91+
houdiniShortName: 'cosmoshub-4',
92+
memoNeeded: true,
93+
addressValidation: /^(cosmos1)[0-9a-z]{38}$/
94+
},
95+
{
96+
pluginId: 'dash',
97+
houdiniShortName: 'dash',
98+
memoNeeded: false,
99+
addressValidation: /^[X|7][0-9A-Za-z]{33}$/
100+
},
101+
{
102+
pluginId: 'dogecoin',
103+
houdiniShortName: 'doge',
104+
memoNeeded: false,
105+
addressValidation: /^(D|A|9)[a-km-zA-HJ-NP-Z1-9]{33,34}$/
106+
},
107+
{
108+
pluginId: 'ecash',
109+
houdiniShortName: 'eCash',
110+
memoNeeded: false,
111+
addressValidation:
112+
/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$|^[0-9A-Za-z]{42,42}$|^(ecash:)[0-9A-Za-z]{30,70}$/
113+
},
114+
{
115+
pluginId: 'ethereum',
116+
houdiniShortName: 'ethereum',
117+
memoNeeded: false,
118+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
119+
},
120+
{
121+
pluginId: 'fantom',
122+
houdiniShortName: 'fantom',
123+
memoNeeded: false,
124+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
125+
},
126+
{
127+
pluginId: 'hedera',
128+
houdiniShortName: 'hedera',
129+
memoNeeded: true,
130+
addressValidation: /^(0.0.)[0-9]{4,40}$/
131+
},
132+
{
133+
pluginId: 'hyperevm',
134+
houdiniShortName: 'hyperevm',
135+
memoNeeded: false,
136+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
137+
},
138+
{
139+
pluginId: 'litecoin',
140+
houdiniShortName: 'litecoin',
141+
memoNeeded: false,
142+
addressValidation: /^(L|M|3)[A-Za-z0-9]{33}$|^(ltc1)[0-9A-Za-z]{39}$/
143+
},
144+
{
145+
pluginId: 'monero',
146+
houdiniShortName: 'monero',
147+
memoNeeded: false,
148+
addressValidation: /^[48][a-zA-Z|\d]{94}([a-zA-Z|\d]{11})?$/
149+
},
150+
{
151+
pluginId: 'opbnb',
152+
houdiniShortName: 'opbnb',
153+
memoNeeded: false,
154+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
155+
},
156+
{
157+
pluginId: 'optimism',
158+
houdiniShortName: 'optimism',
159+
memoNeeded: false,
160+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
161+
},
162+
{
163+
pluginId: 'pivx',
164+
houdiniShortName: 'pivx',
165+
memoNeeded: false,
166+
addressValidation: /^(D)[0-9A-za-z]{33}$/
167+
},
168+
{
169+
pluginId: 'polkadot',
170+
houdiniShortName: 'polkadot',
171+
memoNeeded: false,
172+
addressValidation: /^1[1-9A-HJ-NP-Za-km-z]{46,47}$/
173+
},
174+
{
175+
pluginId: 'polygon',
176+
houdiniShortName: 'polygon',
177+
memoNeeded: false,
178+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
179+
},
180+
{
181+
pluginId: 'pulsechain',
182+
houdiniShortName: 'pulsechain',
183+
memoNeeded: false,
184+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
185+
},
186+
{
187+
pluginId: 'ripple',
188+
houdiniShortName: 'ripple',
189+
memoNeeded: true,
190+
addressValidation: /^r[1-9A-HJ-NP-Za-km-z]{25,34}$/
191+
},
192+
{
193+
pluginId: 'rsk',
194+
houdiniShortName: 'rootstock',
195+
memoNeeded: false,
196+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
197+
},
198+
{
199+
pluginId: 'solana',
200+
houdiniShortName: 'solana',
201+
memoNeeded: false,
202+
addressValidation: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/
203+
},
204+
{
205+
pluginId: 'sonic',
206+
houdiniShortName: 'sonic',
207+
memoNeeded: false,
208+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
209+
},
210+
{
211+
pluginId: 'stellar',
212+
houdiniShortName: 'xlm',
213+
memoNeeded: true,
214+
addressValidation: /^G[A-D]{1}[A-Z2-7]{54}$/
215+
},
216+
{
217+
pluginId: 'sui',
218+
houdiniShortName: 'sui',
219+
memoNeeded: false,
220+
addressValidation: /^(0x)[0-9A-Za-z]{64}$/
221+
},
222+
{
223+
pluginId: 'telos',
224+
houdiniShortName: 'telos',
225+
memoNeeded: false,
226+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
227+
},
228+
{
229+
pluginId: 'thorchainrune',
230+
houdiniShortName: 'thorchain',
231+
memoNeeded: true,
232+
addressValidation: /^(thor1)[0-9a-z]{38}$/
233+
},
234+
{
235+
pluginId: 'ton',
236+
houdiniShortName: 'ton',
237+
memoNeeded: true,
238+
addressValidation: /^(EQ|UQ)[A-Za-z0-9-_]{46}$/
239+
},
240+
{
241+
pluginId: 'tron',
242+
houdiniShortName: 'tron',
243+
memoNeeded: false,
244+
addressValidation: /^T[1-9A-HJ-NP-Za-km-z]{33}$/
245+
},
246+
{
247+
pluginId: 'zcash',
248+
houdiniShortName: 'Zcash',
249+
memoNeeded: false,
250+
addressValidation: /^t1[1-9A-HJ-NP-Za-km-z]{33}$/
251+
},
252+
{
253+
pluginId: 'zksync',
254+
houdiniShortName: 'zksync-era',
255+
memoNeeded: false,
256+
addressValidation: /^(0x)[0-9A-Za-z]{40}$/
257+
}
258+
]
259+
260+
/** Look up the Houdini destination chain for an Edge asset, if served. */
261+
export function getHoudiniChain(
262+
pluginId: string,
263+
tokenId: EdgeTokenId
264+
): HoudiniChain | undefined {
265+
// Only native (chain) assets are offered as destinations for now:
266+
if (tokenId != null) return undefined
267+
return HOUDINI_CHAINS.find(chain => chain.pluginId === pluginId)
268+
}
269+
270+
/** Validate a pasted destination address against the chain's own regex. */
271+
export function isValidHoudiniAddress(
272+
chain: HoudiniChain,
273+
address: string
274+
): boolean {
275+
return chain.addressValidation.test(address.trim())
276+
}

0 commit comments

Comments
 (0)