Skip to content

Add HoudiniSwap swap plugin#469

Open
j0ntz wants to merge 1 commit into
masterfrom
jon/stealth-send-swap
Open

Add HoudiniSwap swap plugin#469
j0ntz wants to merge 1 commit into
masterfrom
jon/stealth-send-swap

Conversation

@j0ntz

@j0ntz j0ntz commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none (compiles against published edge-core-js; the synthetic-destination interplay is runtime-only and guarded)

Description

Asana task

HoudiniSwap swap plugin: privacy-routed CEX swaps via Houdini's v2 partner API (initOptions: { apiKey, apiSecret }, auth header apiKey:apiSecret, requests forced through Edge's CORS proxy because the partner API rejects browser-origin calls from the core WebView).

Behavior:

  • Forward quotes take private (multi-exchange) routes only. Judgement call: Houdini's standard single-CEX routes are served better by our direct provider integrations; privacy routing is this provider's identity in Edge. Alternate (also competing with standard routes on forward quotes) documented as rejected.
  • Reverse quotes (quoteFor: 'to') map to GET /quotes?amountType=receive&fixed=true. The API only prices exact-out on fixed-rate quotes, and its private routing does not serve exact-out today (verified live: exact-out returns standard routes only, across pairs). So reverse quotes fall back to standard fixed-rate routes, which still settle through Houdini (the recipient never sees the sender's address) but use a single exchange leg. Private routes remain preferred whenever the API offers them. Alternates considered: rejecting reverse quotes entirely (breaks the flip-input guarantee semantics the product spec requires), or emulating exact-out by inverting a forward quote (loses the receive-side guarantee).
  • Swap-to-address destinations. A core-built synthetic destination wallet (id prefix synthetic://) skips the typed-address lookup (addressTypeMap) since it holds exactly one pasted, caller-validated address, and may expose destination memos via a getMemos method (detected at runtime through a local guarded type, so this package keeps compiling against published edge-core-js). The memo is forwarded as destinationTag on order creation. Memo chains XRP/XLM/ATOM/HBAR/TON/RUNE are mapped; IBC-family chains (coreum/osmosis/axelar) stay unmapped because Houdini reports no memoNeeded flag and a permissive ^.*$ address validation for them.
  • Route fallback on held deposits. A fixed-rate route's static deposit address can be held by another live order (HTTP 409 STATIC_DEPOSIT_IN_USE, hit live during testing); order creation falls through to the next-best in-range route.
  • Limit handling. Forward limits (min/max) are from-side, reverse limits (minOut/maxOut) receive-side; a reverse quote must also clear the route's from-side bounds with its own priced amountIn (the API enforces this at order creation, verified live). API-level errors with a human-readable message (e.g. "Amount is too low, minimum is 25 USD") surface as that message instead of raw JSON.
  • Max quotes via getMaxSwappable; zcash destinations use transparent addresses (ChangeNow's addressTypeMap precedent).

Testing: mocha acceptance suite (test/houdini.test.ts) runs in the normal npm test (unlike the earlier out-of-suite harness) with disk-cached fixtures that replay offline within the partner API budget: forward BTC→ETH and ETH→USDC private swaps, a reverse BTC→ETH swap priced by the receive amount, and a synthetic memo-chain destination (ETH→XRP) asserting the entered destination tag reaches the create-exchange body. All fixtures recorded against the live API. tsc, eslint, and the full suite pass. In-app: live quotes through this plugin were exercised on the iOS sim via the Stealth Send UI (quote retrieval verified; order execution hit the shared dev key's free-tier 10-exchanges/day cap, with the tag-bearing order creation covered by the live-recorded fixture test).


Note

Medium Risk
New third-party swap path that resolves quotes, converts API float amounts to native units, and submits payout/refund addresses and memos; mistakes could mis-route funds, though behavior is covered by cached acceptance tests and mirrors other central plugins.

Overview
Adds a new HoudiniSwap central swap provider wired into the plugin registry (houdini), backed by Houdini’s v2 partner API with apiKey/apiSecret and always-on CORS proxy so calls work from the core WebView.

The plugin quotes and creates exchanges end-to-end: it resolves Houdini token IDs per chain, prefers private routes on forward swaps, and supports reverse pricing (quoteFor: 'to') via amountType=receive with fixed-rate routes when private exact-out isn’t available. It maps Edge assets through src/mappings/houdini.ts, handles min/max (including reverse minOut/maxOut and priced amountIn), retries order creation on 409 STATIC_DEPOSIT_IN_USE, and builds deposit spends with optional deposit memos.

Swap-to-address synthetic destinations (synthetic://) skip typed address lookup and forward the first destination memo as destinationTag on order creation (e.g. XRP). Zcash uses transparent payout addresses.

Coverage is added via test/houdini.test.ts with disk-cached test/houdiniFixtures/ (forward BTC→ETH, ETH→USDC, reverse BTC→ETH, ETH→XRP with destination tag), plus HOUDINI_INIT in test config and a CHANGELOG unreleased entry.

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

- Privacy-routed CEX swaps via Houdini's v2 partner API: forward quotes
  take private (multi-exchange) routes only; reverse quotes price by the
  receive amount (amountType=receive, fixed-rate), which Houdini's
  private routing does not serve today, so they fall back to standard
  fixed-rate routes that still settle through Houdini.
- Swap-to-address destinations: synthetic destination wallets skip the
  typed-address lookup and may carry destination memos, forwarded as
  destinationTag on order creation (memo chains XRP/XLM/ATOM/HBAR/TON/
  RUNE are mapped; IBC-family chains stay unmapped until Houdini's
  metadata firms up).
- Falls through to the next-best route when a fixed-rate route's static
  deposit address is held by another live order (409).
- Max quotes via getMaxSwappable; zcash destinations use transparent
  addresses; requests ride Edge's CORS proxy (the partner API rejects
  browser-origin calls).
- Mocha acceptance suite with disk-cached fixtures replays offline and
  stays inside the partner API budget.
@j0ntz

j0ntz commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence (live quotes through the plugin on the iOS sim)

agent proof 1216251688512498 01 stealth send toggle

agent proof 1216251688512498 01 stealth send toggle

agent proof 1216251688512498 06 stealth swap houdini only

agent proof 1216251688512498 06 stealth swap houdini only

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

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