If your product touches crypto — a wallet, a portfolio tracker, a game with an in-app token — you will eventually get asked for swaps. Users hold asset A and want asset B, and every time you send them to an external site to do it, you lose them for the rest of the session.
The obvious implementation is the one you should not build: take deposits, hold balances, execute conversions internally. That turns your app into a custodian, which brings licensing exposure, an attack surface holding other people's money, and an operational burden that has nothing to do with your product.
The alternative is integrating a non-custodial instant exchange. Funds never touch your infrastructure. Here's how that model actually works and what you need to handle in your code.
The execution model
An instant exchange is not an order book. There's no matching, no bids and asks, no depth. The flow is:
Request a quote for a pair and amount
Create an order, submitting the user's destination address
Receive a one-time deposit address for that specific order
The user sends funds to that address from their own wallet
On deposit confirmation, the swap executes and output goes directly to the destination address
Your application is a coordinator. It never holds a key, never holds a balance, and never becomes a party to custody. The user's wallet sends, the exchange routes, the user's wallet receives.
The design decisions that matter
Rate type is a risk-allocation choice, not a UX preference
Most exchange APIs expose fixed and floating rates. This is not a cosmetic toggle and you should not pick a default without understanding it.
Floating calculates the rate at deposit confirmation. The market moves between order creation and confirmation, so the output amount is an estimate. Spread is thinner because the exchange carries no price risk.
Fixed locks the output at order creation for a bounded window. Your user sees a guaranteed amount. The exchange absorbs the price movement and charges for it via a wider spread.
If you're building a checkout flow where a precise amount must arrive, you need fixed — a floating rate that lands two percent short breaks your business logic downstream. If you're building a general-purpose swap UI, expose both and explain the difference in one line. Silently defaulting to floating and showing an estimate as though it's a promise is how you generate support tickets.
Order state is asynchronous and long-lived
A swap is not a request/response operation. It spans a user action in another application and one or more blockchain confirmations. Elapsed time is dominated by network confirmation, which varies by chain and congestion — a Bitcoin deposit and a Tron deposit are not comparable operations.
Design for this:
Persist the order ID on creation, before the user leaves to send funds. If you lose it, you cannot reconcile.
Prefer webhooks over polling for status transitions, with polling as a fallback. Confirmation timing is unpredictable enough that a fixed polling interval is either wasteful or slow.
Handle the terminal states explicitly: completed, expired (fixed-rate window elapsed before deposit), underpaid/overpaid, and held-for-review. That last one is real — deposits are screened against blockchain analytics and flagged ones pause pending manual review. If your UI only knows "pending" and "done", a held order looks like a hang.
Treat expiry as a normal path. Users open a swap, get distracted, and come back after the rate window closed. Build the retry.
Address validation belongs in your UI
The most common failure in this entire flow is a user-supplied destination address that's wrong for the target network — a BEP20 address for an ERC20 payout, or a missing memo/tag on a chain that requires one.
This is unrecoverable by design. No custodial party is holding the funds, so no custodial party can reverse it. Validate format and network client-side before you let the order be created, and surface the memo requirement as a blocking field rather than a hint, on the chains where it applies.
Minimums are per-asset, and they move
Minimum swap amounts are driven by network fees, so they shift with congestion. Don't hardcode them. Fetch the current minimum with the quote and validate against the live value, or you will ship a form that rejects valid amounts and accepts invalid ones.
Two integration surfaces
Most providers offer both:
An embeddable widget. An iframe with your styling. Fast — a day of work — but you don't control the UX and you get limited state visibility.
A REST API. You build the interface, you own the flow, you handle the state machine described above. More work, but it's the only option if swaps are part of a larger transaction in your product rather than a standalone feature.
Start with the widget to validate that users actually want this. Move to the API once you know they do.
Top comments (0)