A token swap frontend is one of the few UIs where a small bug does not just annoy someone, it takes their money. I have built swap interfaces and I have watched users lose value not because the smart contract was wrong but because the frontend let them sign something they did not understand or showed them a number that was already stale by the time they clicked. These are the pitfalls that actually cost real money, and most of them are unglamorous frontend discipline rather than anything exotic.
Slippage is a promise, not a suggestion
Slippage tolerance is the gap you accept between the price you saw and the price you actually get when the transaction lands. Set it too tight and swaps fail constantly because the price moved a hair, which frustrates people. Set it too loose, or let the user set it loose without understanding, and you have opened the door to sandwich attacks where a bot moves the price against them inside that tolerance and pockets the difference.
The frontend's job is to make this legible. Show the minimum received, not just the expected amount, because the minimum is the real promise. Warn loudly when someone sets a high tolerance. And never silently raise slippage to make a swap go through. I have seen UIs do that to reduce failed transactions, and it is a quiet way to hand users to the bots. A failed swap is recoverable. A swap that executed at a terrible price is not.
Quotes go stale faster than users click
The price you fetched a few seconds ago may not be the price now. Markets move, and the gap between fetching a quote and the user signing can be long enough for the displayed number to be a lie. If you show a quote and let it sit there indefinitely, you are setting up a confusing experience at best and a costly one at worst when the user signs against a number that no longer holds.
Refresh quotes on a sensible interval, show the user when a quote is refreshing, and recompute the minimum received from the fresh number rather than the one the user first saw. The contract enforces the minimum, so the safety is real, but a swap that reverts because the quote went stale still reads to the user as the app being broken. Keeping the displayed number honest is how you avoid both the bad fill and the support ticket.
- Always display minimum received, not just the expected output
- Refresh quotes and show a clear refreshing state instead of a frozen price
- Recompute slippage bounds from the latest quote, never the first one shown
Approvals are where trust is won or lost
Before a contract can move a user's tokens they have to approve it, and the approval step is where a lot of damage happens. The convenient pattern is to request an unlimited approval so the user never has to approve again. The safe pattern is to approve only the amount being swapped. Unlimited approvals are a standing risk: if that contract is ever compromised, every token a user approved is reachable. Convenience here is borrowed against the user's security.
I lean toward exact-amount approvals by default and make the tradeoff visible rather than deciding it silently for people. At minimum, surface what is being approved and to whom in plain language. A user clicking approve on an opaque prompt has no idea they may be granting open-ended access, and the frontend is the only thing standing between them and that ignorance.
Decimals and the off-by-a-lot bug
Tokens have different decimal precisions, and getting this wrong does not produce a slightly off number. It produces a number off by orders of magnitude. Treat all token amounts as the smallest integer unit internally and only format for display at the edges. Doing math on the human-readable floating point value is how you end up swapping a thousand times more or less than the user intended, and floating point rounding in a financial UI is simply not acceptable.
This is boring and it is also one of the most common serious bugs I see in swap code. The fix is a hard rule: integers in the smallest unit everywhere, conversion to display only at render. No exceptions, no shortcuts, no doing one quick calculation in floats because it was easier in the moment.
The transaction is not done when the wallet pops up
A swap has a lifecycle: pending in the mempool, included in a block, and confirmed enough to be safe. A frontend that flips to success the instant the wallet returns is lying, because the transaction can still fail or get reorganized. Track the real state and reflect it. Show pending honestly, show failures clearly with a reason the user can act on, and do not claim success until the chain has actually confirmed it.
All of these pitfalls share a theme. The contract can be perfect and the user can still lose money because the frontend showed them the wrong thing, let them sign the wrong thing, or told them they were safe when they were not. In a swap UI the interface is part of the security surface, not a layer on top of it, and treating it that way is the difference between a tool people trust and one that quietly costs them.
Building something where this matters?
I am open to senior full-stack, Web3, or AI engineering roles, fully remote and any timezone. If the hard part of your product is fighting you, that is the work I like.
Get in touch →