@nonnux/world-swap
v0.4.0
Published
Drop-in token swap card + quote API for World App mini apps (Uniswap V2/V3 on World Chain).
Maintainers
Readme
@nonnux/world-swap
A drop-in token swap for World App mini apps — a ready-made swap card plus the quote API behind it. Routes through Uniswap V2, V3 and cross-version pools on World Chain, with a 1% platform fee.
It's the same swap that powers the nonnux mini apps (UFO PIZZA SLICE, PAU$, REI$ Slots, …), packaged so you can add it to your own mini app in a couple of lines.
┌──────────────────────────┐ ┌──────────────────────────────┐
│ <SwapCard /> (client) │ ─────▶ │ /api/swap/quote (server) │
│ wallet · UI · tx via │ POST │ reads Uniswap V2/V3 via RPC │
│ MiniKit.sendTransaction │ ◀───── │ (ethers, no DB, no secrets) │
└──────────────────────────┘ └──────────────────────────────┘Requirements
- A Next.js app (App Router) — it provides the API route.
- Tailwind CSS v4 — the card is styled with Tailwind utility classes.
- Peer deps (you almost certainly already have these):
react,react-dom,ethers@^6,@worldcoin/minikit-js@>=1.9.
Install
npm install @nonnux/world-swapThe package ships pre-bundled (minified ESM, with React/ethers/MiniKit kept
external), so no extra bundler config is needed. If your setup ever fails to
resolve it, you can add transpilePackages: ['@nonnux/world-swap'] to
next.config.js.
Usage
1. Mount the quote API
Create app/api/swap/quote/route.js:
// Batteries-included: nonnux token list + World Chain contracts.
export { POST, GET } from '@nonnux/world-swap/server';2. Render the card
'use client';
import { SwapCard } from '@nonnux/world-swap';
import { Button } from '@/components/ui/Button'; // your own button
export default function SwapTab({ user }) {
return <SwapCard walletUser={user} Button={Button} />;
}That's it. walletUser is your connected user object ({ address }); pass
null to show the "connect your wallet" state. Button is your own button
component so the CTA matches your app's look.
Token icons are loaded from
/<icon>(e.g./tokens/slice.gif) in yourpublic/folder. Copy the icons you need, or pointtokens[].iconat a URL.
World App Developer Portal setup (required)
World App rejects any contract or token your mini app hasn't whitelisted — skip
this and swaps fail with invalid_contract. In the
Developer Portal → your app → Configuration
→ Advanced, paste these (comma-separated).
Contract Entrypoints — the 3 swap contracts + every tradeable token:
0x4479A14811274D0eEB8Eb8932fA5A49b51F37852,0x4Cf23b3C9BeB48b85bc8F01E3dAB674Ce94E180a,0x380a940524C4cC1829f654B7790B338f676C31b8,0x513530649BAC862c38b65e957E6EA38d3f86F3dc,0x10377A2e9CC81A8822885cd847e577F7F39B5574,0x2e5f6BB8C993e64B3bC1fC9473C8B2CDEd772D06,0xE67Fb81ca7d20eB90bA3a53220018a9A7f870de8,0x9d59Bb9d85Ae4622116Ad11055B5be4349b67dc2,0x3Cb880f7ac84950c369e700deE2778d023b0C52d,0xE977de70dd1F571Aa563E41525C28b4F1eDB69ba,0x03995cE5Ad612a2cC3E4DFBEEb0EeD7BC165749b,0x06B4C6830bb19BF07A88ff96758003074EaE9c52,0x2cFc85d8E48F8EAB294be644d9E25C3030863003,0x4200000000000000000000000000000000000006,0xab09A728E53d3d6BC438BE95eeD46Da0Bbe7FB38Permit2 Tokens — every tradeable token (same list, minus the 3 contracts):
0x513530649BAC862c38b65e957E6EA38d3f86F3dc,0x10377A2e9CC81A8822885cd847e577F7F39B5574,0x2e5f6BB8C993e64B3bC1fC9473C8B2CDEd772D06,0xE67Fb81ca7d20eB90bA3a53220018a9A7f870de8,0x9d59Bb9d85Ae4622116Ad11055B5be4349b67dc2,0x3Cb880f7ac84950c369e700deE2778d023b0C52d,0xE977de70dd1F571Aa563E41525C28b4F1eDB69ba,0x03995cE5Ad612a2cC3E4DFBEEb0EeD7BC165749b,0x06B4C6830bb19BF07A88ff96758003074EaE9c52,0x2cFc85d8E48F8EAB294be644d9E25C3030863003,0x4200000000000000000000000000000000000006,0xab09A728E53d3d6BC438BE95eeD46Da0Bbe7FB38The first three entrypoints are the swap contracts (V2 0x4479…, V3 0x4Cf2…,
cross-version router 0x380a…); the other twelve are the token contracts. Using
a custom tokens list? Whitelist your own token addresses (the 3 swap contracts
stay the same).
Customising
Everything is optional and falls back to the nonnux / World Chain defaults.
Your own token list
app/api/swap/quote/route.js:
import { createSwapQuoteHandler } from '@nonnux/world-swap/server';
import { MY_TOKENS } from '@/config/tokens';
export const { POST, GET } = createSwapQuoteHandler({ tokens: MY_TOKENS });Component:
import { SwapCard } from '@nonnux/world-swap';
import { MY_TOKENS } from '@/config/tokens';
<SwapCard walletUser={user} Button={Button} tokens={MY_TOKENS} />A token looks like:
{
symbol: 'SLICE',
name: 'UFO PIZZA',
address: '0x...',
icon: '/tokens/slice.gif', // path in /public or a URL
gradient: 'from-orange-500 to-yellow-500', // Tailwind gradient
decimals: 18,
emoji: '🍕', // fallback if icon fails
externalTaxPercent: 0, // token transfer tax, if any
poolVersion: 'v2', // 'v2' | 'v3'
v3FeeTier: 10000, // only for v3 tokens
}<SwapCard /> props
| Prop | Default | Description |
| ---------------- | ------------------------ | ------------------------------------------------------ |
| walletUser | — | Connected user { address }. null → connect state. |
| Button | — | Your button component for the CTA. |
| tokens | nonnux list | Token list to swap between. |
| contracts | World Chain | Override any contract address (merged with defaults). |
| rpcUrl | Alchemy public | JSON-RPC URL for balance reads. |
| apiBasePath | /api/swap | Base path of your quote route (${base}/quote). |
| explorerUrl | https://worldscan.org | Block explorer for the "view tx" link. |
| ImageComponent | <img> | Pass next/image (or a wrapper) to optimise icons. |
| storagePrefix | world_swap | localStorage key prefix (avoid cross-app collisions). |
| title | Swap | Header title. |
createSwapQuoteHandler(config) options
tokens, contracts, rpcUrl, directV3Pairs, platformFeeBps, v3FeeTiers.
How it works
- Quote (
/api/swap/quote, server): given a token pair + amount, it reads Uniswap pools on World Chain viaethersand returns the best route (direct V2, V2 multi-hop through WLD, direct V3, or V3↔V2 cross-version), plus price impact and the min-received after fee. - Swap (client): builds a 2-step transaction (ERC-20
transferto the swap contract, then the swap call) and submits it withMiniKit.commandsAsync.sendTransaction. The contract enforcesminAmountOut.
No database, no API keys, no env vars required — the quote route only needs a public World Chain RPC.
Add this swap to your own mini app
Building a World App mini app and want a swap tab without writing one? Install
@nonnux/world-swap, mount the quote route, and drop in <SwapCard /> — about
two lines each (see Usage above). Bring your own token list or use the
defaults.
Need a hand? Read the guide: https://paus.nonnux.com/world-swap.html
License
MIT © nonnux
