How to Build a Swap App with Venum API
This guide shows how to build a simple non-custodial Solana swap app on top of the Venum API.
It is inspired by the public Venum Swap flow, but keeps the architecture generic and avoids product-specific internals. The goal is to help you build your own swap UI, wallet flow, and transaction lifecycle without rebuilding DEX routing and transaction assembly from scratch.
What Your Swap App Needs
At a minimum, a swap app needs five pieces:
- Token selection
- Amount input and quote refresh
- Wallet connection
- Transaction build, sign, and submit flow
- Transaction status feedback
Venum handles the routing and swap transaction construction. Your app handles the user experience.
Recommended Architecture
Use a thin backend in front of the Venum API.
Browser UI
-> Your backend
-> Venum API
-> Solana DEX routing + transaction buildThis gives you three practical benefits:
- Your API key stays server-side
- You can add your own auth, logging, and rate limiting
- You can reshape responses for your frontend without coupling the UI to raw API payloads
Core Flow
The app flow is straightforward:
Select tokens -> Request quote -> Connect wallet -> Build unsigned swap -> Sign -> Submit -> Track statusThe main Venum endpoints involved are:
POST /v1/quotePOST /v1/swap/buildPOST /v1/swapGET /v1/tx/:signatureor the transaction stream for status updates
Step 1: Create a Quote Endpoint in Your Backend
Your frontend should call your server, not Venum directly.
// Example: /api/quote
export async function postQuote(req: Request) {
const body = await req.json();
const response = await fetch('https://api.venum.dev/v1/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.VENUM_API_KEY!,
},
body: JSON.stringify({
inputMint: body.inputMint,
outputMint: body.outputMint,
amount: body.amount,
slippageBps: body.slippageBps,
}),
});
return Response.json(await response.json(), { status: response.status });
}On the frontend, refresh the quote whenever the user changes:
- input token
- output token
- amount
- slippage
Add a short debounce so you do not fire a request on every keystroke.
Step 2: Build the Swap UI Around Quotes
Your quote-driven UI should usually show:
- estimated output amount
- best route or DEX label
- price impact
- slippage setting
- loading and no-route states
A good pattern is:
- pre-wallet: show quotes immediately so the page feels usable on first paint
- post-wallet: build the unsigned transaction only when the user is ready to swap
That keeps the UI fast while still using POST /v1/swap/build as the source of truth before signing.
Step 3: Connect a Wallet
Keep the swap non-custodial:
- your app never sees the user's private key
- your backend never signs on behalf of the user
- the wallet signs the unsigned transaction locally
All you need from the wallet for build is the public key.
const userPublicKey = wallet.publicKey.toBase58();Step 4: Build the Unsigned Transaction
When the user presses Swap, call your backend build route.
// Example: /api/swap/build
export async function postSwapBuild(req: Request) {
const body = await req.json();
const response = await fetch('https://api.venum.dev/v1/swap/build', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.VENUM_API_KEY!,
},
body: JSON.stringify({
inputMint: body.inputMint,
outputMint: body.outputMint,
amount: body.amount,
slippageBps: body.slippageBps,
userPublicKey: body.userPublicKey,
}),
});
return Response.json(await response.json(), { status: response.status });
}The response gives you an unsigned transaction plus the quoteId needed for submission.
Before you show the wallet prompt, surface useful state in the UI:
- building transaction
- simulation or validation failure
- insufficient balance
- stale quote
Step 5: Sign Client-Side
Once you receive the unsigned transaction, deserialize it and ask the wallet to sign.
import { VersionedTransaction } from '@solana/web3.js';
const tx = VersionedTransaction.deserialize(
Buffer.from(build.transaction, 'base64')
);
const signed = await wallet.signTransaction(tx);
const signedTransaction = Buffer.from(signed.serialize()).toString('base64');This is also the point where you can let advanced users inspect the route and transaction details before signing.
Step 6: Submit the Signed Transaction
Send the signed transaction back through your backend.
// Example: /api/swap/submit
export async function postSwapSubmit(req: Request) {
const body = await req.json();
const response = await fetch('https://api.venum.dev/v1/swap', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.VENUM_API_KEY!,
},
body: JSON.stringify({
signedTransaction: body.signedTransaction,
quoteId: body.quoteId,
}),
});
return Response.json(await response.json(), { status: response.status });
}Your UI should move through clear states:
- Quoting
- Building
- Signing
- Sending
- Confirming
- Confirmed or failed
That state machine matters more than most teams think. It is the difference between a swap app that feels trustworthy and one that feels broken during wallet and network delays.
Step 7: Show Status After Submission
After submission, track transaction status with either:
GET /v1/tx/:signature- the transaction status stream if you want live updates
Polling example:
async function waitForStatus(signature: string) {
for (;;) {
const response = await fetch(`/api/tx/${signature}`);
const status = await response.json();
if (status.confirmed || status.failed) return status;
await new Promise((resolve) => setTimeout(resolve, 500));
}
}At minimum, show:
- submitted signature
- linking out to an explorer
- success state
- readable failure state
Frontend Patterns Worth Copying
The best swap apps tend to share the same UX patterns:
- Quote before wallet connect so the app feels instant
- Pause quote refresh while the wallet popup is open
- Lock the CTA while a transaction is in flight
- Preserve token pair and amount in the URL for shareable links
- Show route, pool, or venue information in a compact details row
- Let users adjust slippage without forcing them into advanced settings screens
You do not need a complicated architecture to get these right. One swap card with a clear async lifecycle is usually enough.
Keep the API Key Off the Client
Do not embed your Venum API key in browser code.
Bad:
fetch('https://api.venum.dev/v1/quote', {
headers: { 'X-API-Key': 'public-key-in-client-code' },
});Good:
fetch('/api/quote', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});This keeps the browser simple and your credentials private.
Production Checklist
- Keep your API key server-side only
- Debounce quotes during input typing
- Disable swaps for zero amount and no-route states
- Handle quote expiry by re-quoting and rebuilding
- Surface wallet rejection separately from on-chain failure
- Show transaction signatures and explorer links
- Log build and submit errors on your backend
- Test on mobile wallets as well as desktop extensions
Minimal App Structure
You can ship a clean first version with just these pieces:
src/
api/
quote.ts
swap-build.ts
swap-submit.ts
tx-status.ts
components/
SwapCard.tsx
TokenSelector.tsx
WalletButton.tsx
lib/
tokens.ts
format.ts
wallet.tsStart with one swap card. Add watchlists, charts, portfolio views, and routing analytics later.
Next Steps
- Quick Start for the first quote/build/submit flow
- Swap Building for unsigned transaction details
- Composable Instructions if you want to extend swaps with your own instructions
- API Reference for request and response fields
