Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
22c7b4d
feat: inital swap setup
passandscore Jan 19, 2026
778e456
fix: rebase conflict
passandscore Feb 12, 2026
98bdeaf
feat: local tokenlist and pricing api
passandscore Jan 20, 2026
8c6bdcd
feat: enhance swap interface with mock quote integration and dual tok…
passandscore Jan 20, 2026
e77225d
feat: create reusable TokenSelectButton component and improve swap in…
passandscore Jan 20, 2026
e16f704
chore: formatting
passandscore Jan 20, 2026
f307255
feat: uniswap quotes
passandscore Jan 20, 2026
74ec928
refactor: swap quote system with pulsing animation
passandscore Jan 20, 2026
6d34cf6
feat: multil-side quotes and switcher logoc
passandscore Jan 20, 2026
d02d771
feat: input odometer effect
passandscore Jan 20, 2026
b197f4d
Add swap interface focus mode with hidden hero sections and still bac…
passandscore Jan 21, 2026
e7509db
feat: slippage and review
passandscore Jan 21, 2026
3bf1db9
refactor: update review swap modal
passandscore Jan 21, 2026
0c52999
chore: clean up swap files
passandscore Jan 21, 2026
d1fbbe0
refactor SwapInterface: Extract components and reduce file size
passandscore Jan 21, 2026
797e28c
refactor: fix quotes
passandscore Jan 21, 2026
9c415ea
refactor: fix input cursor
passandscore Jan 21, 2026
c809b10
refactor: switch logic
passandscore Jan 22, 2026
d82235b
refactor: fix quote bugs
passandscore Jan 22, 2026
0a22d0d
refactor: adjust nav header
passandscore Jan 22, 2026
ea1b791
refactor: support 13in macbooks
passandscore Jan 22, 2026
955d472
refactor: action button bug when token switched
passandscore Jan 22, 2026
8a8a6e7
refactor: remove rounding from wallet balance
passandscore Jan 22, 2026
a41840c
feat: add ETH to token list
passandscore Jan 22, 2026
406da5e
fix: token selection scrolling
passandscore Jan 22, 2026
b10529a
refactor: fee teir checks use promise.race & no liquidity errot handling
passandscore Jan 22, 2026
31bd4f4
refactor: quote fee tiers
passandscore Jan 22, 2026
787ca29
feat: dynamic text input
passandscore Jan 22, 2026
3661a5a
refactor: precentage max bug
passandscore Jan 22, 2026
1346026
refactor: hide visible scroll bar
passandscore Jan 22, 2026
43f036d
refactor: redesign swap confirmation
passandscore Jan 22, 2026
7d9b33c
feat: weth handling
passandscore Jan 22, 2026
404b8d3
feat: migrate fast swaps
passandscore Jan 22, 2026
33203a5
chore: clean up landing
passandscore Jan 22, 2026
89f20d4
refactor: use token list
passandscore Jan 22, 2026
f43b131
refactor: quote logic
passandscore Jan 23, 2026
21657af
refactor: component restructuring
passandscore Jan 23, 2026
37b85b6
refactor: quote adjustments
passandscore Jan 23, 2026
8044666
refactor: overlay switch button
passandscore Jan 23, 2026
1297f4f
feat: gas estimate for wrapped operationd
passandscore Jan 26, 2026
f51b5b5
refactor: bypass quotes for weth
passandscore Jan 26, 2026
cf0dbe0
refactor: adjust contrast on modals
passandscore Jan 28, 2026
ecfd519
chore: remove unused files
passandscore Jan 28, 2026
1d10b54
feat: add fastRPC api endpoints
passandscore Jan 28, 2026
775c777
feat: dummy mode
passandscore Jan 28, 2026
e0357f8
feat: review details accordian
passandscore Jan 29, 2026
fa1ab49
feat: support minOut values
passandscore Jan 29, 2026
8928b71
fix: buikd error
passandscore Jan 29, 2026
c02771b
refactor: adjust slippage logic
passandscore Jan 29, 2026
686f04c
feat: useWaitForTxConfirmation hook
passandscore Jan 29, 2026
d66e47e
refactor: update SwapConfirmationModal label
passandscore Jan 29, 2026
aab0f8f
refactor: add useWaitForTxConfirmation to swap logic
passandscore Jan 29, 2026
f4f8360
refactor: remove dummy logic and cleanup useSwapConfirmation
passandscore Jan 29, 2026
3264ebb
refactor: handle deadline
passandscore Jan 29, 2026
88473bd
feat: isStablecoin logic
passandscore Jan 29, 2026
613f302
refactor: SwapConfirmation
passandscore Jan 30, 2026
4befb04
refactor: handle price impact confirmation
passandscore Jan 30, 2026
a9b6ff4
feat: reset swap state on wallet disconnect
passandscore Jan 30, 2026
880fabe
refactor: add odometer effect to secondary values
passandscore Jan 30, 2026
90b14b2
refactor: adjust swap interface padding
passandscore Jan 30, 2026
7830434
feat: fetch user points for Fuul
passandscore Jan 30, 2026
3324e6a
refactor: padding adjustments
passandscore Jan 30, 2026
460847f
testing: logs and gas
passandscore Jan 30, 2026
dc4e6eb
refactor: layout upgrades
passandscore Jan 30, 2026
710f5e4
testing: console.logs
passandscore Jan 30, 2026
af42ba0
feat: network check before swap
passandscore Feb 2, 2026
4a16a1f
refactor: update dashboard tab
passandscore Feb 2, 2026
3a4b2e7
refactor: hide auto slippage badge on confirmation when not set
passandscore Feb 2, 2026
7ea5c39
feat: centralize error handling
passandscore Feb 2, 2026
57f7024
refactor: fast/eth request format
passandscore Feb 2, 2026
33300a6
refactor: fix tx execution errors
passandscore Feb 3, 2026
c1b0da6
refactor: update error message handling
passandscore Feb 3, 2026
62e5e62
refactor: update gas values
passandscore Feb 4, 2026
e6a5925
refactor: cleanup
passandscore Feb 4, 2026
bdb1521
refactor: improve deadline errors
passandscore Feb 4, 2026
af57d2b
refactor: handle slippage display in settings
passandscore Feb 4, 2026
e0eea54
feat: add Barter api
passandscore Feb 4, 2026
0191144
fix: build errors
passandscore Feb 4, 2026
7ae4778
refactor: make auto slippage default onload
passandscore Feb 4, 2026
6a2e676
refactor: optimise for Vercel speed insights
passandscore Feb 4, 2026
058d7c3
refactor: format full errors
passandscore Feb 4, 2026
76e5d95
fix: remove weth as output token when eth is needed
passandscore Feb 5, 2026
bdbb4d7
refactor: add additional details to barter error log
passandscore Feb 5, 2026
f235e65
feat: permit2 approval
passandscore Feb 5, 2026
22c4844
feat: redesign tx processing toast
passandscore Feb 5, 2026
3e29bb7
feat: swap toast feature flag
passandscore Feb 5, 2026
0b95610
refactor: swap toast
passandscore Feb 6, 2026
e54df42
feat: use additional Barter response data
passandscore Feb 6, 2026
c27bbf8
refactor: change cta button text for intent
passandscore Feb 7, 2026
47eba85
refactor: add slippage to rpc request payload
passandscore Feb 9, 2026
2f26378
refactor: switch all quotes back to Uniswap
passandscore Feb 9, 2026
93a5439
refactor: clamp slippage to max 2%
passandscore Feb 9, 2026
beb61d2
refactor: show preconf on swapToast
passandscore Feb 9, 2026
94a2cb5
feat: handle failed DB reciepts
passandscore Feb 9, 2026
2d2f365
feat: add swap whilelist
passandscore Feb 10, 2026
e3583bc
feat: whitelist feature flag
passandscore Feb 10, 2026
3afe7b6
refactor: handle db errors
passandscore Feb 11, 2026
c3140d2
refactor: primary cta button
passandscore Feb 11, 2026
8051871
refactor: hide try again on errors after preconf
passandscore Feb 11, 2026
1ac9c4b
refactor: remove network modal from AppHeader
passandscore Feb 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/tx-confirmation-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Transaction confirmation flow (TL;DR)

Two sources race to confirm a swap tx: **Fast RPC** (polled via `eth_getTransactionReceipt`) and **Wagmi** (on-chain receipt). The flow is robust to status flipping from success to failed before finality.

## Flow

- **RPC polling** (every 500ms): Single fetch per iteration. If receipt is **success** (0x1) → call `onPreConfirmed` once, show “pre-confirmed” UI, **keep polling**. If receipt is **reverted** (0x0) at any time → treat as final failure: stop polling, set `hasConfirmedRef` so Wagmi won’t override, call `onError` → UI shows error modal.
- **Wagmi**: When on-chain receipt arrives, if we already “confirmed” (including by RPC failure) we do nothing. Else: if reverted → `onError`; if success → `onConfirmed`, abort RPC polling.

So: pre-confirm is optimistic (0x1 from RPC); we keep re-checking until either Wagmi confirms or we see 0x0 and show error.

## Key pieces

- **Hook:** `useWaitForTxConfirmation` in `src/hooks/use-wait-for-tx-confirmation.ts` — runs the race, uses `fetchTransactionReceiptFromDb` (single request per loop), `preConfirmedFiredRef` so `onPreConfirmed` fires once per hash.
- **Swap UI:** `SwapToast` uses the hook only; `onPreConfirmed` → set status to `"pre-confirmed"`; `onError` → `setFailed` → `lastTxError` → SwapConfirmationModal shows the error.
- **Status:** RPC returns 0x1/0x0; `transaction-receipt-utils` normalizes to `"success"` / `"reverted"`. No raw 0x0/0x1 checks in UI.

## Outcome matrix

| RPC first | Then | Result |
|----------------|------------|---------------------------|
| 0x1 (success) | keep polling | Pre-confirm shown |
| 0x1 then 0x0 | — | Error; Wagmi ignored |
| 0x0 | — | Error |
| Wagmi receipt | — | Success → `onConfirmed`; reverted → `onError` |
15 changes: 15 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,26 @@ const nextConfig = {
hostname: 'assets.coingecko.com',
pathname: '/coins/images/**',
},
{
protocol: 'https',
hostname: 'coin-images.coingecko.com',
pathname: '/coins/images/**',
},
{
protocol: 'https',
hostname: 'cryptologos.cc',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'token-icons.s3.amazonaws.com',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'raw.githubusercontent.com',
pathname: '/**',
},
],
},
};
Expand Down
60 changes: 59 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\""
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"stablecoins": "tsx scripts/getStablecoins.ts"
},
"dependencies": {
"@fuul/sdk": "^7.7.1",
"@hookform/resolvers": "^3.10.0",
"@next/eslint-plugin-next": "^15.4.2",
"@number-flow/react": "^0.5.10",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-aspect-ratio": "^1.1.7",
Expand Down Expand Up @@ -60,6 +62,7 @@
"ethers": "^6.16.0",
"googleapis": "^169.0.0",
"input-otp": "^1.4.2",
"lenis": "^1.3.17",
"lucide-react": "^0.462.0",
"motion": "^12.23.26",
"next": "^15.5.7",
Expand All @@ -81,6 +84,7 @@
"vaul": "^0.9.9",
"viem": "^2.40.4",
"wagmi": "^2.19.5",
"zustand": "^5.0.3",
"zod": "^3.25.76"
},
"devDependencies": {
Expand Down
35 changes: 35 additions & 0 deletions scripts/getStablecoins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Fetches stablecoin symbols from CoinGecko and writes a deduplicated list to src/lib/stablecoin-list.json.
* Run: npx tsx scripts/getStablecoins.ts
*/

const COINGECKO_URL =
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&category=stablecoins"
const OUT_PATH = new URL("../src/lib/stablecoin-list.json", import.meta.url)

interface CoinGeckoItem {
symbol: string
[key: string]: unknown
}

async function main() {
const res = await fetch(COINGECKO_URL)
if (!res.ok) {
throw new Error(`CoinGecko API error: ${res.status} ${res.statusText}`)
}
const data = (await res.json()) as CoinGeckoItem[]
const symbols = [...new Set(data.map((item) => item.symbol.toUpperCase()))].sort()
const json = JSON.stringify(symbols, null, 2)
const { writeFileSync, mkdirSync } = await import("fs")
const { dirname } = await import("path")
const { fileURLToPath } = await import("url")
const outPath = fileURLToPath(OUT_PATH)
mkdirSync(dirname(outPath), { recursive: true })
writeFileSync(outPath, json + "\n", "utf8")
console.log(`Wrote ${symbols.length} stablecoin symbols to ${outPath}`)
}

main().catch((err) => {
console.error(err)
process.exit(1)
})
4 changes: 3 additions & 1 deletion src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ function AppLayoutContent({ children }: { children: React.ReactNode }) {
!hasCheckedStatus &&
status !== "connecting" &&
status !== "reconnecting" &&
(pathname?.startsWith("/dashboard") || pathname?.startsWith("/leaderboard"))
(pathname?.startsWith("/dashboard") ||
pathname?.startsWith("/leaderboard") ||
pathname?.startsWith("/swap"))
) {
// Small tick to ensure RainbowKit's internal state is also ready
const timer = setTimeout(() => {
Expand Down
13 changes: 13 additions & 0 deletions src/app/(app)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Hero } from "@/components/swap/HeroSection"
import { AnimatedBackgroundOrbs } from "@/components/swap/OrbAnimatedBackground"
import { SwapForm } from "@/components/swap/SwapForm"

export default function IndexPage() {
return (
<div className="relative flex flex-col items-center justify-start px-4 xs:pt-6 pb-4">
<AnimatedBackgroundOrbs />
<Hero />
<SwapForm />
</div>
)
}
73 changes: 73 additions & 0 deletions src/app/api/barter/route/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { NextRequest, NextResponse } from "next/server"

const BARTER_API_BASE = "https://api2.eth.barterswap.xyz"

export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { source, target, sellAmount } = body

if (!source || !target || !sellAmount) {
return NextResponse.json(
{ error: "Missing required fields: source, target, sellAmount" },
{ status: 400 }
)
}

const apiKey = process.env.BARTER_API_KEY
if (!apiKey) {
return NextResponse.json({ error: "Barter API key invalid or missing." }, { status: 500 })
}

const requestId = crypto.randomUUID?.() ?? `route-${Date.now()}`

const resp = await fetch(`${BARTER_API_BASE}/route`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Request-Id": requestId,
},
body: JSON.stringify({
source: String(source).toLowerCase(),
target: String(target).toLowerCase(),
sellAmount: String(sellAmount),
}),
})

const data = await resp.json()
console.log("barter data", data)

if (!resp.ok) {
const msg = data?.error ?? data?.message ?? `Barter API error (${resp.status})`
return NextResponse.json({ error: msg }, { status: resp.status })
}

const outputAmount = data?.outputWithGasAmount
const gasEstimation = data?.gasEstimation
const transactionFee = data?.transactionFee
const gasPrice = data?.gasPrice

if (outputAmount == null || gasEstimation == null) {
return NextResponse.json(
{ error: "Barter API error. Invalid route response." },
{ status: 500 }
)
}

const response: Record<string, unknown> = {
outputAmount: String(outputAmount),
gasEstimation: Number(gasEstimation),
}
if (transactionFee != null) response.transactionFee = String(transactionFee)
if (gasPrice != null) response.gasPrice = String(gasPrice)

return NextResponse.json(response)
} catch (error) {
console.error("Barter route API error:", error)
return NextResponse.json(
{ error: "Barter API error. Please check your connection." },
{ status: 500 }
)
}
}
Loading
Loading