Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
91cae78
feat: inital swap setup
passandscore Jan 19, 2026
fdbe6b7
fix: rebase conflict
passandscore Feb 12, 2026
f11ca1c
fix: rebase conflict
passandscore Feb 17, 2026
5203bf9
feat: enhance swap interface with mock quote integration and dual tok…
passandscore Jan 20, 2026
7b5c401
feat: create reusable TokenSelectButton component and improve swap in…
passandscore Jan 20, 2026
1531fae
chore: formatting
passandscore Jan 20, 2026
3459136
feat: uniswap quotes
passandscore Jan 20, 2026
38b8c04
refactor: swap quote system with pulsing animation
passandscore Jan 20, 2026
92bbf49
feat: multil-side quotes and switcher logoc
passandscore Jan 20, 2026
0b9f9f6
feat: input odometer effect
passandscore Jan 20, 2026
bcb23aa
Add swap interface focus mode with hidden hero sections and still bac…
passandscore Jan 21, 2026
59ea736
feat: slippage and review
passandscore Jan 21, 2026
fb4db05
refactor: update review swap modal
passandscore Jan 21, 2026
423a98d
chore: clean up swap files
passandscore Jan 21, 2026
f81ab91
refactor SwapInterface: Extract components and reduce file size
passandscore Jan 21, 2026
1babcba
refactor: fix quotes
passandscore Jan 21, 2026
8ee89ca
refactor: fix input cursor
passandscore Jan 21, 2026
02afacc
refactor: switch logic
passandscore Jan 22, 2026
7e5eb4c
refactor: fix quote bugs
passandscore Jan 22, 2026
52a05a1
refactor: adjust nav header
passandscore Jan 22, 2026
e0754e0
refactor: support 13in macbooks
passandscore Jan 22, 2026
37feca1
refactor: action button bug when token switched
passandscore Jan 22, 2026
c39b991
refactor: remove rounding from wallet balance
passandscore Jan 22, 2026
608f1f6
feat: add ETH to token list
passandscore Jan 22, 2026
2822795
fix: token selection scrolling
passandscore Jan 22, 2026
090d6ab
refactor: fee teir checks use promise.race & no liquidity errot handling
passandscore Jan 22, 2026
9207914
refactor: quote fee tiers
passandscore Jan 22, 2026
aec52f5
feat: dynamic text input
passandscore Jan 22, 2026
62aafd2
refactor: precentage max bug
passandscore Jan 22, 2026
6bfcd5e
refactor: hide visible scroll bar
passandscore Jan 22, 2026
7216f00
refactor: redesign swap confirmation
passandscore Jan 22, 2026
79ab774
feat: weth handling
passandscore Jan 22, 2026
bbf04fe
feat: migrate fast swaps
passandscore Jan 22, 2026
ce9e953
chore: clean up landing
passandscore Jan 22, 2026
65b848e
refactor: use token list
passandscore Jan 22, 2026
722da14
refactor: quote logic
passandscore Jan 23, 2026
53228a1
refactor: component restructuring
passandscore Jan 23, 2026
8660591
refactor: quote adjustments
passandscore Jan 23, 2026
12bd97c
refactor: overlay switch button
passandscore Jan 23, 2026
7022a42
feat: gas estimate for wrapped operationd
passandscore Jan 26, 2026
ad477aa
refactor: bypass quotes for weth
passandscore Jan 26, 2026
c6bd914
refactor: adjust contrast on modals
passandscore Jan 28, 2026
19e3642
chore: remove unused files
passandscore Jan 28, 2026
a4eed09
feat: add fastRPC api endpoints
passandscore Jan 28, 2026
621d371
feat: dummy mode
passandscore Jan 28, 2026
ce0a39e
feat: review details accordian
passandscore Jan 29, 2026
ec26950
feat: support minOut values
passandscore Jan 29, 2026
0e0aff8
fix: buikd error
passandscore Jan 29, 2026
a22cee0
refactor: adjust slippage logic
passandscore Jan 29, 2026
21de1e4
feat: useWaitForTxConfirmation hook
passandscore Jan 29, 2026
c316f47
refactor: update SwapConfirmationModal label
passandscore Jan 29, 2026
fb69232
refactor: add useWaitForTxConfirmation to swap logic
passandscore Jan 29, 2026
adebe92
refactor: remove dummy logic and cleanup useSwapConfirmation
passandscore Jan 29, 2026
6a237be
refactor: handle deadline
passandscore Jan 29, 2026
dde4871
feat: isStablecoin logic
passandscore Jan 29, 2026
d759e8a
refactor: SwapConfirmation
passandscore Jan 30, 2026
b1b0e2b
refactor: handle price impact confirmation
passandscore Jan 30, 2026
f05ecb8
feat: reset swap state on wallet disconnect
passandscore Jan 30, 2026
35d8758
refactor: add odometer effect to secondary values
passandscore Jan 30, 2026
9a2e1fc
refactor: adjust swap interface padding
passandscore Jan 30, 2026
49023eb
feat: fetch user points for Fuul
passandscore Jan 30, 2026
c68cc76
refactor: padding adjustments
passandscore Jan 30, 2026
04deada
testing: logs and gas
passandscore Jan 30, 2026
1100c1b
refactor: layout upgrades
passandscore Jan 30, 2026
12d77d2
testing: console.logs
passandscore Jan 30, 2026
a92c6d4
feat: network check before swap
passandscore Feb 2, 2026
056ddfb
refactor: update dashboard tab
passandscore Feb 2, 2026
2c8677d
refactor: hide auto slippage badge on confirmation when not set
passandscore Feb 2, 2026
a06d8c8
feat: centralize error handling
passandscore Feb 2, 2026
83fa482
refactor: fast/eth request format
passandscore Feb 2, 2026
2bb501f
refactor: fix tx execution errors
passandscore Feb 3, 2026
d189430
refactor: update error message handling
passandscore Feb 3, 2026
3486206
refactor: update gas values
passandscore Feb 4, 2026
2dcb498
refactor: cleanup
passandscore Feb 4, 2026
bf2e6e6
refactor: improve deadline errors
passandscore Feb 4, 2026
1768d33
refactor: handle slippage display in settings
passandscore Feb 4, 2026
d689292
feat: add Barter api
passandscore Feb 4, 2026
60dc10e
fix: build errors
passandscore Feb 4, 2026
67051ea
refactor: make auto slippage default onload
passandscore Feb 4, 2026
3ad36a3
refactor: optimise for Vercel speed insights
passandscore Feb 4, 2026
c23dbef
refactor: format full errors
passandscore Feb 4, 2026
8bfd158
fix: remove weth as output token when eth is needed
passandscore Feb 5, 2026
1d2b489
refactor: add additional details to barter error log
passandscore Feb 5, 2026
35cc3f0
feat: permit2 approval
passandscore Feb 5, 2026
26b1f8d
feat: redesign tx processing toast
passandscore Feb 5, 2026
085bb98
feat: swap toast feature flag
passandscore Feb 5, 2026
d5bf93c
refactor: swap toast
passandscore Feb 6, 2026
46cfc7b
feat: use additional Barter response data
passandscore Feb 6, 2026
1ea8a74
refactor: change cta button text for intent
passandscore Feb 7, 2026
458893b
refactor: add slippage to rpc request payload
passandscore Feb 9, 2026
2d03076
refactor: switch all quotes back to Uniswap
passandscore Feb 9, 2026
e06ae14
refactor: clamp slippage to max 2%
passandscore Feb 9, 2026
5eeb69a
refactor: show preconf on swapToast
passandscore Feb 9, 2026
1c34e34
feat: handle failed DB reciepts
passandscore Feb 9, 2026
54d6776
feat: add swap whilelist
passandscore Feb 10, 2026
8a3e4bc
feat: whitelist feature flag
passandscore Feb 10, 2026
b1cfe9a
refactor: handle db errors
passandscore Feb 11, 2026
f74fe85
refactor: primary cta button
passandscore Feb 11, 2026
0b756a5
refactor: hide try again on errors after preconf
passandscore Feb 11, 2026
d8441bd
refactor: remove network modal from AppHeader
passandscore Feb 12, 2026
8a5b306
chore: format
passandscore Feb 17, 2026
56a151e
feat: swap waitlist registration
passandscore Feb 18, 2026
c6aa347
feat: add notice on early access form
passandscore Feb 20, 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)
})
55 changes: 55 additions & 0 deletions src/app/(app)/SwapOrLandingGate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client"

import { useAccount } from "wagmi"
import { FEATURE_FLAGS } from "@/lib/feature-flags"
import { useWhitelist } from "@/hooks/use-whitelist"
import { useWaitlist } from "@/hooks/use-waitlist"
import { Hero } from "@/components/swap/HeroSection"
import { AnimatedBackgroundOrbs } from "@/components/swap/OrbAnimatedBackground"
import { SwapForm } from "@/components/swap/SwapForm"
import LandingPage from "@/components/landing/Page"
import { EarlyAccessForm } from "@/components/landing/EarlyAccessForm"
import { AlreadyOnWaitlistMessage } from "@/components/landing/AlreadyOnWaitlistMessage"

function SwapContent() {
return (
<div className="relative flex flex-col items-center justify-start px-4 xs:pt-6 pb-4">
<AnimatedBackgroundOrbs />
<Hero />
<SwapForm />
</div>
)
}

export function SwapOrLandingGate() {
const { swapPrivateMode } = FEATURE_FLAGS
const { isConnected, address } = useAccount()
const { isWhitelisted, isLoading: isWhitelistLoading } = useWhitelist()
const { onWaitlist, isLoading: isWaitlistLoading } = useWaitlist()

if (!swapPrivateMode) {
return <SwapContent />
}

if (isWhitelistLoading || isWaitlistLoading) {
return (
<div className="relative flex min-h-[60vh] items-center justify-center px-4">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
)
}

const canSwap = isWhitelisted && onWaitlist
if (canSwap) {
return <SwapContent />
}

if (isConnected && address) {
if (onWaitlist) {
return <AlreadyOnWaitlistMessage />
}
return <EarlyAccessForm initialWalletAddress={address} />
}

return <LandingPage />
}
26 changes: 25 additions & 1 deletion src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import { useConnectModal } from "@rainbow-me/rainbowkit"
import { toast } from "sonner"
import { Suspense } from "react"
import { AppHeader } from "@/components/shared/AppHeader"
import { AnimatedBackground } from "@/components/AnimatedBackground"
import { useRPCTest } from "@/hooks/use-rpc-test"
import { useWalletInfo } from "@/hooks/use-wallet-info"
import { useWhitelist } from "@/hooks/use-whitelist"
import { useWaitlist } from "@/hooks/use-waitlist"
import { isMetaMaskWallet, isRabbyWallet } from "@/lib/onboarding-utils"
import { NETWORK_CONFIG } from "@/lib/network-config"
import { FEATURE_FLAGS } from "@/lib/feature-flags"
import { DashboardTabProvider, useDashboardTab } from "./DashboardTabContext"

// Modal Components
Expand All @@ -32,10 +36,19 @@ function AppLayoutContent({ children }: { children: React.ReactNode }) {
const { isConnected, status, connector, address } = useAccount()
const { connectors } = useConnect()
const { walletName, walletIcon } = useWalletInfo(connector, isConnected)
const { isWhitelisted, isLoading: isWhitelistLoading } = useWhitelist()
const { onWaitlist, isLoading: isWaitlistLoading } = useWaitlist()
const rpcTest = useRPCTest()
const { openConnectModal } = useConnectModal()
const [hasCheckedStatus, setHasCheckedStatus] = useState(false)

const canSwap = isWhitelisted && onWaitlist
const isGateRoute = pathname === "/"
const hideLayout =
isGateRoute &&
FEATURE_FLAGS.swapPrivateMode &&
(isWhitelistLoading || isWaitlistLoading || !canSwap)

// Wallet detection
const isMetaMask = isMetaMaskWallet(connector)
const isRabby = isRabbyWallet(connector)
Expand Down Expand Up @@ -68,7 +81,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 Expand Up @@ -152,6 +167,15 @@ function AppLayoutContent({ children }: { children: React.ReactNode }) {
}
}

if (hideLayout) {
return (
<div className="relative min-h-screen">
<AnimatedBackground />
<div className="relative z-10">{children}</div>
</div>
)
}

return (
<div className="bg-background min-h-screen">
<div
Expand Down
5 changes: 5 additions & 0 deletions src/app/(app)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SwapOrLandingGate } from "./SwapOrLandingGate"

export default function IndexPage() {
return <SwapOrLandingGate />
}
Loading