I built a cross-network payment link app on the SDK in a weekend

by

I built over a weekend using the SODAX SDK. It's a payment link generator. You pick a destination network and token, set an amount, add a memo if you want, and you get a link. Whoever opens that link pays with whatever crypto they hold on whatever network they're on. The recipient gets exactly what they asked for. No signups, fully non-custodial, SodaxPay never touches funds at any point.

Three surfaces make up the app. A creation page where you build the link (desktop-shaped, deliberate). A payment page the payer opens cold (often mobile, one shot at credibility). And a landing page for both audiences.

The build experience is probably more useful than the app itself for anyone here, so here's what it was actually like.

What saved the most time

@sodax/wallet-sdk-react was the biggest one. EVM, Solana, Sui, Stellar, and ICON wallets behind one component and one hook shape. If you've ever wired up two of these wallet families by hand you know what that saves. Five is not linear. This is most of the reason SodaxPay exists. Without it the app would have been a month of adapter plumbing before I could start on actual logic.

One thing to watch on server-rendered frameworks: the adapters touch window and localStorage at module load. Gate the provider behind a client mount or Next.js will blow up at build time.

Then there's the live registry. Chain and token data comes from sodax.config.initialize() at page load, nothing hardcoded. 21 networks, 220+ tokens across all of them, 12 networks wired in SodaxPay today with 164 tokens on those 12. If a new network gets added tomorrow, the landing page picks it up without me redeploying. An earlier version of my copy claimed "18 networks." That was wrong in both directions. It got caught precisely because everything else on the page is live-read. That kind of design keeps you honest.

Interesting problem: pricing backwards

Worth calling out because it's not obvious until you hit it.

Normal flow is "here's $100 of X, what do I get?" Input fixed, output solved. Invoices are the opposite. The recipient needs exactly 250 USDC, so the output is fixed and the input is what you solve for. The SDK quotes exact_input only, so SodaxPay has to guess an input, ask what that produces, adjust, and converge. Not a rounding thing either. If someone pays a $100 invoice in ETH, a naive scaling attempt would ask the pricing engine to quote a hundred ETH, a trade three orders of magnitude bigger than the real one. So it converges iteratively instead of guessing once.

No complaints there. Just something worth knowing upfront if you're building anything invoice-shaped. The API's design decides what work lands in your app.

Recovery that actually works

"What happens when it goes wrong" is the first question anyone serious asks about payments. If the relay times out or the solver can't fill the order, funds don't vanish and they don't get stuck with SodaxPay because SodaxPay never has them. They sit at the hub under the payer's control. The SDK gives you recovery tools to check what's sitting there and let them pull it back themselves. No support ticket, no trusting an operator to refund you.

Honest DX notes

What was great: the wallet layer (biggest time saver by a wide margin), the live registry (design that doesn't age), the Result<T, E> pattern (nothing throws, error handling is hard to accidentally get wrong).

Where to pay attention: exact_input-only quoting means invoice-style flows need convergence logic. And a real one worth generalizing: the SDK's types were right and my casts were wrong. Three separate bugs in SodaxPay came from reaching for as to make TypeScript errors go away. If the SDK returns Result<T, E> and you cast it to T, you haven't fixed anything. You've moved the failure from compile time to production.

Design decision

I designed the payment UI like a departures board. A payment is basically an itinerary. Origin, transfer through a hub, destination, and a wait in the middle you can't shorten. Settlement takes tens of seconds and I made a deliberate choice to render the wait as the product rather than hide it behind a spinner. If you're going to ask someone to wait, make the waiting feel like something is happening.

Stack: Next.js 16, /sdk 2.0.0, /wallet-sdk-react 2.0.0, Neon Postgres, anime.js, Selawik


Try it at . Happy to answer questions about the DX or go deeper on any of this.

2 views

Add a comment

Replies

Be the first to comment