diff --git a/src/components/swap/ActionButton.tsx b/src/components/swap/ActionButton.tsx
new file mode 100644
index 0000000..3032537
--- /dev/null
+++ b/src/components/swap/ActionButton.tsx
@@ -0,0 +1,99 @@
+"use client"
+
+import React from "react"
+// UI Components
+import { Button } from "@/components/ui/button"
+import { ConnectButton } from "@rainbow-me/rainbowkit"
+
+// Types
+import { Token } from "@/types/swap"
+
+/**
+ * Interface for the ActionButton component.
+ * Includes only the specific state flags required to determine
+ * which button variant to render.
+ */
+interface ActionButtonProps {
+ isConnected: boolean
+ toToken: Token | null
+ amount: string
+ insufficientBalance: boolean
+ isWrap: boolean
+ isUnwrap: boolean
+ handleSwapClick: () => void
+}
+
+export const ActionButton: React.FC = ({
+ isConnected,
+ toToken,
+ amount,
+ insufficientBalance,
+ isWrap,
+ isUnwrap,
+ handleSwapClick,
+}) => {
+ /**
+ * 1. WALLET CONNECTION CHECK
+ * The highest priority state. If the user isn't connected, we
+ * defer to the RainbowKit ConnectButton custom implementation.
+ */
+ if (!isConnected) {
+ return (
+
+
+ {({ openConnectModal }) => (
+
+ )}
+
+
+ )
+ }
+
+ /**
+ * 2. VALIDATION & SWAP LOGIC
+ * Once connected, we check for token selection, input amounts,
+ * and balance sufficiency before allowing the swap action.
+ */
+ return (
+
+ {!toToken ? (
+ // Case: No destination token selected
+
+ ) : !amount || amount === "0" ? (
+ // Case: Token is selected but no amount has been entered
+
+ ) : insufficientBalance ? (
+ // Case: Amount exceeds user's current balance
+
+ ) : (
+ // Case: All checks passed - Ready to Swap, Wrap, or Unwrap
+
+ )}
+
+ )
+}
diff --git a/src/components/swap/AmountInput.tsx b/src/components/swap/AmountInput.tsx
new file mode 100644
index 0000000..e2e61de
--- /dev/null
+++ b/src/components/swap/AmountInput.tsx
@@ -0,0 +1,161 @@
+"use client"
+
+import React, { useRef, useState, useLayoutEffect } from "react"
+import NumberFlow from "@number-flow/react"
+import { cn } from "@/lib/utils"
+
+/**
+ * Strictly pruned interface containing only active parameters.
+ * Removed pulse-related props and debug logs for production readiness.
+ */
+interface AmountInputProps {
+ value: string | null
+ onChange: (value: string) => void
+ onFocus: () => void
+ onBlur: () => void
+ isActive: boolean
+ isDisabled: boolean
+ showError: boolean
+ isQuoteLoading?: boolean
+ inputRef?: React.RefObject
+}
+
+export default function AmountInput({
+ value,
+ onChange,
+ onFocus,
+ onBlur,
+ isActive,
+ isDisabled,
+ showError,
+ inputRef,
+}: AmountInputProps) {
+ // 1. VALUE CALCULATIONS
+ // Sanitizing commas and handling the edge case of "No liquidity" text
+ const isSpecialValue = value === "No liquidity"
+ const cleanValue = value && !isSpecialValue ? value.replace(/,/g, "") : ""
+
+ // Logic for NumberFlow formatting to match input precision
+ const numericValue = value && !isNaN(parseFloat(cleanValue)) ? parseFloat(cleanValue) : null
+ const decimalPlaces = cleanValue.includes(".") ? cleanValue.split(".")[1]?.length || 0 : 0
+ const minFractionDigits = Math.min(decimalPlaces, 6)
+
+ // 2. DYNAMIC FONT SCALING STATE & REFS
+ const [fontPx, setFontPx] = useState(36)
+ const containerRef = useRef(null)
+ const mirrorRef = useRef(null)
+
+ /**
+ * useLayoutEffect handles the "Mirror Measurement" strategy.
+ * It calculates the width of the text in a hidden span before the browser paints,
+ * shrinking the font size if the text exceeds the container width.
+ */
+ useLayoutEffect(() => {
+ const container = containerRef.current
+ const mirror = mirrorRef.current
+ if (!container || !mirror) return
+
+ const MAX_FONT_SIZE = 36
+ const MIN_FONT_SIZE = 14
+ const RIGHT_GUTTER = 4 // Safety buffer to prevent character clipping at the edge
+
+ const adjustSize = () => {
+ const availableWidth = container.clientWidth - RIGHT_GUTTER
+
+ // Reset mirror to max to measure the "natural" width of the current string
+ mirror.style.fontSize = `${MAX_FONT_SIZE}px`
+ const textWidth = mirror.scrollWidth
+
+ if (textWidth > availableWidth && availableWidth > 0) {
+ const ratio = availableWidth / textWidth
+ // 0.97 multiplier adds a tiny safety margin for tight letter tracking
+ const calculatedSize = Math.max(MAX_FONT_SIZE * ratio * 0.97, MIN_FONT_SIZE)
+ setFontPx(calculatedSize)
+ } else {
+ setFontPx(MAX_FONT_SIZE)
+ }
+ }
+
+ const observer = new ResizeObserver(adjustSize)
+ observer.observe(container)
+ adjustSize()
+
+ return () => observer.disconnect()
+ }, [value, isActive])
+
+ // 3. RENDER LOGIC
+ return (
+
+
+ {/* HIDDEN MIRROR:
+ Used solely for measuring text width. Must share the exact
+ font-weight and tracking classes as the visible input.
+ */}
+
+ {value || "0"}
+
+
+
+ )
+}
diff --git a/src/components/swap/ExchangeRate.tsx b/src/components/swap/ExchangeRate.tsx
new file mode 100644
index 0000000..5f7e0bb
--- /dev/null
+++ b/src/components/swap/ExchangeRate.tsx
@@ -0,0 +1,194 @@
+"use client"
+
+import React, { useMemo } from "react"
+import NumberFlow from "@number-flow/react"
+// UI Components & Icons
+import { AlertTriangle } from "lucide-react"
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@radix-ui/react-tooltip"
+
+// Utils & Hooks
+import { cn } from "@/lib/utils"
+import { QuoteResult, getPriceImpactSeverity, formatPriceImpact } from "@/hooks/use-swap-quote"
+
+/**
+ * Prop interface focused on essential quote data.
+ * Internalized the severity calculation to reduce parent complexity.
+ */
+interface ExchangeRateProps {
+ // The pre-formatted string or element showing "1 ABC = 0.5 XYZ" (fallback when no numeric rate)
+ exchangeRateContent: React.ReactNode
+
+ // Numeric rate for NumberFlow (subtle animation on refetch; no "Fetching rate..." text)
+ exchangeRateValue: number | null
+ exchangeRateFromSymbol: string
+ exchangeRateToSymbol: string
+ exchangeRateToStable: boolean
+ isQuoteLoading?: boolean
+
+ // The raw quote data from the API
+ activeQuote: QuoteResult | null
+
+ // Logical flags to determine UI behavior
+ isWrapUnwrap: boolean
+ isManualInversion: boolean
+
+ // The countdown until the current quote expires
+ timeLeft: number
+}
+
+export const ExchangeRate: React.FC = ({
+ exchangeRateContent,
+ exchangeRateValue,
+ exchangeRateFromSymbol,
+ exchangeRateToSymbol,
+ exchangeRateToStable,
+ isQuoteLoading = false,
+ activeQuote,
+ isWrapUnwrap,
+ isManualInversion,
+ timeLeft,
+}) => {
+ /**
+ * 1. DERIVED LOCAL STATE
+ * We use useMemo here to ensure that severity calculations only
+ * run when the actual quote data changes, keeping the component performant.
+ */
+ const { priceImpact, severity, formattedImpact } = useMemo(() => {
+ // If we are in manual inversion, we usually want to hide or freeze the impact
+ // because the impact of an inverted quote isn't 100% accurate until re-fetched.
+ if (isManualInversion && !activeQuote) {
+ return {
+ priceImpact: 0,
+ severity: "low" as const,
+ formattedImpact: "...", // Shows a placeholder while switching
+ }
+ }
+
+ const impact = activeQuote?.priceImpact ?? 0
+ return {
+ priceImpact: impact,
+ severity: getPriceImpactSeverity(impact),
+ formattedImpact: activeQuote ? formatPriceImpact(impact) : "-",
+ }
+ }, [activeQuote, isManualInversion])
+
+ /**
+ * 2. CONDITIONAL RENDER CHECK
+ * If we are performing a 1:1 wrap/unwrap, the price impact logic is
+ * irrelevant. We return a simplified view or handle the exclusion internally.
+ */
+ const showExtendedInfo = activeQuote && !isWrapUnwrap
+
+ return (
+
+
+ {/* LEFT SECTION: EXCHANGE RATE & LIVE STATUS */}
+
+
+ {exchangeRateValue != null ? (
+ <>
+ 1 {exchangeRateFromSymbol} ={" "}
+ {" "}
+ {exchangeRateToSymbol}
+ >
+ ) : (
+ exchangeRateContent
+ )}
+
+
+ {/* STATUS INDICATOR:
+ A pulsing dot indicates the quote is 'live'.
+ We switch to yellow if 'isManualInversion' is true, signaling
+ to the user they are looking at a flipped rate view.
+ */}
+ {showExtendedInfo && (
+
+ {/* 1. HEADER & SETTINGS
+ Handles the "Swap" title and the configuration popover.
+ */}
+
+
+ {/* 2. CORE SWAP CARDS
+ SellCard and BuyCard with a SwitchButton overlay.
+ */}
+
+
+
+
+
+
+
+
+
+
+ {/* 3. INFORMATION & EXECUTION
+ Only show price impact and rate when both tokens are present.
+ The ActionButton handles all connection and validation states.
+ */}
+ {fromToken && toToken && (
+
+ )}
+
+
+
+ {/* 4. REWARDS
+ Persistent badge for user engagement.
+ */}
+
+