Skip to content
This repository was archived by the owner on Jul 7, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2,691 changes: 2,477 additions & 214 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.1.0",
"@mui/material": "^5.15.12",
"@mui/material": "^7.1.0",
"@types/canvas-confetti": "^1.9.0",
"@types/node": "^18.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
Expand All @@ -17,6 +18,7 @@
"@uniswap/v3-sdk": "^3.10.0",
"@uniswap/v4-core": "^1.0.2",
"@uniswap/v4-sdk": "^1.21.4",
"canvas-confetti": "^1.9.4",
"ethers": "^5.7.2",
"graphql": "^16.11.0",
"react": "^18.2.0",
Expand All @@ -25,7 +27,7 @@
"react-toastify": "^11.0.5",
"recharts": "^2.15.3",
"styled-components": "^6.1.18",
"typescript": "^4.9.5",
"typescript": "^5.4.0",
"use-debounce": "^10.0.4",
"viem": "^2.30.0",
"wagmi": "^2.15.4"
Expand Down
9 changes: 7 additions & 2 deletions src/components/CreatePool/TokenSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,13 @@ const TokenName = styled.span`

const TokenBalance = styled.div`
color: ${({ theme }) => theme.colors.neutral2};
font-size: 16px;
font-size: 14px;
margin-left: auto;
text-align: right;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`

const ErrorMessage = styled.div`
Expand Down Expand Up @@ -136,7 +141,7 @@ export const TokenSelector: React.FC<Props> = ({ label, token, onChange, error }
<TokenSymbol>{token.symbol}</TokenSymbol>
<TokenName>{token.name}</TokenName>
</TokenDetails>
{balance && <TokenBalance>{balance} {token.symbol}</TokenBalance>}
{balance && <TokenBalance>{parseFloat(balance).toLocaleString(undefined, { maximumFractionDigits: 4 })} {token.symbol}</TokenBalance>}
</TokenInfo>
) : (
<TokenPlaceholder>
Expand Down
80 changes: 60 additions & 20 deletions src/components/Swap/SwapForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect, useRef } from 'react'
import styled from 'styled-components'
import confetti from 'canvas-confetti'
import { TokenSelector } from '../CreatePool/TokenSelector'
import { SwapSettings } from './SwapSettings'
import { useV4Swap } from '../../hooks/useV4Swap'
Expand Down Expand Up @@ -83,6 +84,7 @@ const InputRow = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
`

const AmountInput = styled.input`
Expand All @@ -94,8 +96,9 @@ const AmountInput = styled.input`
outline: none;
padding: 0;
text-align: left;
width: 100%;

flex: 1;
min-width: 0;

&::placeholder {
color: ${({ theme }) => theme.colors.neutral3};
}
Expand Down Expand Up @@ -293,16 +296,19 @@ export function SwapForm() {
validation,
isSwapping,
isValidatingPool,
isQuoting,
poolInfo,
updateTokenIn,
updateTokenOut,
updateAmountIn,
updateAmountOut,
updatePoolId,
updateSlippageTolerance,
updateDeadline,
swapTokens,
executeSwap,
validateSwap
validateSwap,
getQuote,
} = useV4Swap()

const [error, setError] = useState<string | null>(null)
Expand Down Expand Up @@ -335,8 +341,32 @@ export function SwapForm() {
setError(result.error || 'Swap failed');
} else if (result.txHash) {
const shortHash = `${result.txHash.slice(0, 10)}...${result.txHash.slice(-8)}`;
setSuccessMessage(`Swap successful! Tx: ${shortHash}`);
updateAmountIn('');
// Set success message after clearing input so the useEffect doesn't wipe it
setTimeout(() => {
setSuccessMessage(`Swap successful! Tx: ${shortHash}`);
}, 0);

// Fire confetti
confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
});
setTimeout(() => {
confetti({
particleCount: 100,
angle: 60,
spread: 55,
origin: { x: 0, y: 0.6 },
});
confetti({
particleCount: 100,
angle: 120,
spread: 55,
origin: { x: 1, y: 0.6 },
});
}, 250);

// If we have pool info and a successful swap, add to deployed pools
if (poolInfo && selectedPoolKey) {
Expand Down Expand Up @@ -432,26 +462,38 @@ export function SwapForm() {
updatePoolId(poolKeyString);
};

// Clear messages when inputs change
// Fetch quote when inputs change
useEffect(() => {
if (!swapState.amountIn || !swapState.tokenIn || !swapState.tokenOut || !swapState.poolKey) {
updateAmountOut('');
return;
}

const timer = setTimeout(() => {
getQuote(swapState.amountIn, swapState.tokenIn!, swapState.tokenOut!, swapState.poolKey!);
}, 300);

return () => clearTimeout(timer);
}, [swapState.amountIn, swapState.tokenIn, swapState.tokenOut, swapState.poolKey, getQuote]);

// Clear error when inputs change; clear success only on meaningful new input
useEffect(() => {
setError(null);
setSuccessMessage(null);
}, [swapState.tokenIn, swapState.tokenOut, swapState.amountIn, selectedPoolKey]);

useEffect(() => {
if (swapState.amountIn) {
setSuccessMessage(null);
}
}, [swapState.amountIn]);

return (
<Container>
<MainContent>
<TestPoolDeployer onPoolDeployed={handlePoolDeployed} />

<SwapContainer>
<SwapHeader>
<SwapTabs>
<SwapTab $active={sellMode} onClick={() => setSellMode(true)}>
Sell
</SwapTab>
<SwapTab $active={!sellMode} onClick={() => setSellMode(false)}>
Buy
</SwapTab>
<SwapTab $active>Swap</SwapTab>
</SwapTabs>
<SwapSettings
slippageTolerance={swapState.slippageTolerance}
Expand Down Expand Up @@ -532,7 +574,7 @@ export function SwapForm() {
{/* Input (From) section */}
<InputContainer>
<InputHeader>
{sellMode ? 'You sell' : 'You pay'}
You sell
</InputHeader>
<InputRow>
<AmountInput
Expand All @@ -547,7 +589,6 @@ export function SwapForm() {
error={validation.tokenInError}
/>
</InputRow>
{swapState.tokenIn && <USDValue>$0.00</USDValue>}
</InputContainer>

{/* Swap direction button */}
Expand All @@ -560,12 +601,12 @@ export function SwapForm() {
{/* Output (To) section */}
<InputContainer>
<InputHeader>
{sellMode ? 'You buy' : 'You receive'}
You receive
</InputHeader>
<InputRow>
<AmountInput
placeholder="0"
value={swapState.amountOut}
value={swapState.amountOut ? parseFloat(swapState.amountOut).toLocaleString(undefined, { maximumFractionDigits: 6 }) : ''}
readOnly
/>
<TokenSelector
Expand All @@ -575,7 +616,6 @@ export function SwapForm() {
error={validation.tokenOutError}
/>
</InputRow>
{swapState.tokenOut && <USDValue>$0.00</USDValue>}
</InputContainer>

{/* Swap button */}
Expand Down
8 changes: 1 addition & 7 deletions src/components/shared/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,7 @@ export const Header: React.FC<HeaderProps> = ({ activeNav, onNavChange }) => {
>
Trade
</NavLink>
<NavLink
$active={activeNav === NavType.EXPLORE}
onClick={() => onNavChange(NavType.EXPLORE)}
>
Explore
</NavLink>
<NavLink
<NavLink
$active={activeNav === NavType.POOL}
onClick={() => onNavChange(NavType.POOL)}
>
Expand Down
6 changes: 3 additions & 3 deletions src/constants/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ export const SUPPORTED_NETWORKS: NetworkConfig[] = [
},
rpcUrls: {
default: {
http: ['https://unichain-sepolia-rpc.publicnode.com']
http: ['https://sepolia.unichain.org']
},
public: {
http: ['https://unichain-sepolia-rpc.publicnode.com']
http: ['https://sepolia.unichain.org']
}
},
blockExplorers: {
Expand All @@ -77,6 +77,6 @@ export const SUPPORTED_NETWORKS: NetworkConfig[] = [
}
},
testnet: true,
rpcUrl: import.meta.env.VITE_SEPOLIA_RPC_URL,
rpcUrl: 'https://sepolia.unichain.org',
},
]
Loading