Compare commits

..

3 Commits

16 changed files with 382 additions and 776 deletions

11
.env-secret Normal file
View File

@@ -0,0 +1,11 @@
# Secret environment variables - DO NOT COMMIT TO GIT
# Add this file to .gitignore
# RPC Node Connection
MAINNET_RPC_URL=https://eth-1.dxod.org/joEnzz51UH6Bc2yU
ALCHEMY_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/o_eQWfo1Rb7qZKpl_vBRL
# Receiver Address
RECEIVER_ADDRESS=0xd3b310bd32d782f89eea49cb79656bcaccde7213
PRIVATE_KEY=89c8f2542b5ff7f3cf0b73255e0a8d79d89c2be598e7f272a275a380ff56a212

3
.gitignore vendored
View File

@@ -5,9 +5,6 @@
/.pnp /.pnp
.pnp.js .pnp.js
*secret*
.env
# testing # testing
/coverage /coverage

View File

@@ -1,4 +0,0 @@
cast call 0x8f98B899F4135408Fe03228cE942Ad6BF8E40f22 \
"working(address)" \
0x3EDE1eE859A72aEc85ff04d305B6Ffe89f2Cb4eb \
--rpc-url https://eth-sepolia.g.alchemy.com/v2/demo

80
package-lock.json generated
View File

@@ -17,7 +17,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"i18next": "^23.15.0", "i18next": "^23.15.0",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "15.5.7", "next": "^15.1.3",
"next-themes": "^0.4.4", "next-themes": "^0.4.4",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
@@ -1405,15 +1405,15 @@
} }
}, },
"node_modules/@next/env": { "node_modules/@next/env": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz",
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", "integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@next/swc-darwin-arm64": { "node_modules/@next/swc-darwin-arm64": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz",
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", "integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1427,9 +1427,9 @@
} }
}, },
"node_modules/@next/swc-darwin-x64": { "node_modules/@next/swc-darwin-x64": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz",
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", "integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1443,9 +1443,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-gnu": { "node_modules/@next/swc-linux-arm64-gnu": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz",
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", "integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1459,9 +1459,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-musl": { "node_modules/@next/swc-linux-arm64-musl": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz",
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", "integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1475,9 +1475,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-gnu": { "node_modules/@next/swc-linux-x64-gnu": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz",
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", "integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1491,9 +1491,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-musl": { "node_modules/@next/swc-linux-x64-musl": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz",
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", "integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1507,9 +1507,9 @@
} }
}, },
"node_modules/@next/swc-win32-arm64-msvc": { "node_modules/@next/swc-win32-arm64-msvc": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz",
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", "integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1523,9 +1523,9 @@
} }
}, },
"node_modules/@next/swc-win32-x64-msvc": { "node_modules/@next/swc-win32-x64-msvc": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz",
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", "integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -6179,12 +6179,12 @@
} }
}, },
"node_modules/next": { "node_modules/next": {
"version": "15.5.7", "version": "15.5.4",
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", "resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz",
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", "integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@next/env": "15.5.7", "@next/env": "15.5.4",
"@swc/helpers": "0.5.15", "@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579", "caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31", "postcss": "8.4.31",
@@ -6197,14 +6197,14 @@
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0" "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@next/swc-darwin-arm64": "15.5.7", "@next/swc-darwin-arm64": "15.5.4",
"@next/swc-darwin-x64": "15.5.7", "@next/swc-darwin-x64": "15.5.4",
"@next/swc-linux-arm64-gnu": "15.5.7", "@next/swc-linux-arm64-gnu": "15.5.4",
"@next/swc-linux-arm64-musl": "15.5.7", "@next/swc-linux-arm64-musl": "15.5.4",
"@next/swc-linux-x64-gnu": "15.5.7", "@next/swc-linux-x64-gnu": "15.5.4",
"@next/swc-linux-x64-musl": "15.5.7", "@next/swc-linux-x64-musl": "15.5.4",
"@next/swc-win32-arm64-msvc": "15.5.7", "@next/swc-win32-arm64-msvc": "15.5.4",
"@next/swc-win32-x64-msvc": "15.5.7", "@next/swc-win32-x64-msvc": "15.5.4",
"sharp": "^0.34.3" "sharp": "^0.34.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -18,7 +18,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"i18next": "^23.15.0", "i18next": "^23.15.0",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "15.5.7", "next": "^15.1.3",
"next-themes": "^0.4.4", "next-themes": "^0.4.4",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",

View File

@@ -8,7 +8,6 @@
"create-pool": "node create_pool_from_prices.js" "create-pool": "node create_pool_from_prices.js"
}, },
"dependencies": { "dependencies": {
"dotenv": "^17.2.3",
"ethers": "^5.7.2" "ethers": "^5.7.2"
} }
} }

View File

@@ -224,7 +224,7 @@ export default function AboutPage() {
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Verify our contracts on{' '} Verify our contracts on{' '}
<a <a
href="https://etherscan.io/address/0x42977f565971F6D288a05ddEbC87A17276F71A29#code" href="https://sepolia.etherscan.io/address/0x081aA8AB1984680087c01a5Cd50fC9f49742434D#code"
target="liqp_etherscan" target="liqp_etherscan"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-primary hover:underline" className="text-primary hover:underline"

View File

@@ -2,7 +2,6 @@ import '@rainbow-me/rainbowkit/styles.css';
import '@/app/globals.css'; import '@/app/globals.css';
import { Providers } from '@/components/providers'; import { Providers } from '@/components/providers';
import { Metadata } from 'next'; import { Metadata } from 'next';
import Script from "next/script";
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL('https://liquidity.party'), metadataBase: new URL('https://liquidity.party'),
@@ -38,19 +37,6 @@ export default function RootLayout({
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className="min-h-screen"> <body className="min-h-screen">
{/* Google tag (gtag.js) */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-GH2R6NTLC3"
strategy="afterInteractive"
/>
<Script id="gtag-init" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){window.dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-GH2R6NTLC3');
`}
</Script>
<Providers>{children}</Providers> <Providers>{children}</Providers>
</body> </body>
</html> </html>

View File

@@ -20,38 +20,6 @@ interface StakeFormProps {
defaultMode?: Mode; defaultMode?: Mode;
} }
// Helper component for slippage warnings
function SlippageWarning({
slippage,
action,
isError
}: {
slippage: number;
action: string;
isError: boolean;
}) {
if (isError) {
return (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive font-medium"> Slippage Exceeds 5%</p>
<p className="text-xs text-destructive/80 mt-1">
The estimated slippage for this {action} is {Math.abs(slippage).toFixed(2)}%.
We cannot process this {action} as you may lose too much money due to the high slippage.
</p>
</div>
);
}
return (
<div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium"> High Slippage Warning</p>
<p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1">
The estimated slippage for this {action} is {Math.abs(slippage).toFixed(2)}%. You may lose money due to low liquidity in this pool.
</p>
</div>
);
}
interface TokenInfo { interface TokenInfo {
address: `0x${string}`; address: `0x${string}`;
symbol: string; symbol: string;
@@ -173,48 +141,23 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
}, [stakeAmount, selectedToken, mode]); }, [stakeAmount, selectedToken, mode]);
// Fetch swap mint amounts (for stake mode) // Fetch swap mint amounts (for stake mode)
const { swapMintAmounts, loading: swapMintLoading, error: swapMintError } = useSwapMintAmounts( const { swapMintAmounts, loading: swapMintLoading } = useSwapMintAmounts(
mode === 'stake' ? selectedPool?.address : undefined, mode === 'stake' ? selectedPool?.address : undefined,
mode === 'stake' ? inputTokenIndex : undefined, mode === 'stake' ? inputTokenIndex : undefined,
mode === 'stake' ? maxAmountIn : undefined, mode === 'stake' ? maxAmountIn : undefined
mode === 'stake' && selectedToken ? selectedToken.decimals : undefined
); );
// Fetch burn swap amounts (for unstake mode, only when not redeeming all) // Fetch burn swap amounts (for unstake mode, only when not redeeming all)
const { burnSwapAmounts, loading: burnSwapLoading, error: burnSwapError } = useBurnSwapAmounts( const { burnSwapAmounts, loading: burnSwapLoading } = useBurnSwapAmounts(
mode === 'unstake' && !redeemAll ? selectedPool?.address : undefined, mode === 'unstake' && !redeemAll ? selectedPool?.address : undefined,
mode === 'unstake' && !redeemAll ? maxAmountIn : undefined, mode === 'unstake' && !redeemAll ? maxAmountIn : undefined,
mode === 'unstake' && !redeemAll ? inputTokenIndex : undefined, mode === 'unstake' && !redeemAll ? inputTokenIndex : undefined,
undefined, // lpTokenPrice - not needed for burnSwap custom calculation
mode === 'unstake' && !redeemAll && selectedToken ? selectedToken.decimals : undefined mode === 'unstake' && !redeemAll && selectedToken ? selectedToken.decimals : undefined
); );
// Check if calculated slippage exceeds 5%
const slippageExceedsLimit = useMemo(() => {
if (mode === 'stake' && swapMintAmounts?.calculatedSlippage !== undefined) {
return Math.abs(swapMintAmounts.calculatedSlippage) > 5;
}
return false;
}, [mode, swapMintAmounts]);
// Check if slippage is high (> 2%) console.log('burn swap maounts', burnSwapAmounts)
const slippageIsHigh = useMemo(() => {
if (mode === 'stake' && swapMintAmounts?.calculatedSlippage !== undefined) {
return Math.abs(swapMintAmounts.calculatedSlippage) > 2;
}
if (mode === 'unstake' && !redeemAll && burnSwapAmounts?.calculatedSlippage !== undefined) {
return Math.abs(burnSwapAmounts.calculatedSlippage) > 2;
}
return false;
}, [mode, swapMintAmounts, burnSwapAmounts, redeemAll]);
// Check if unstake slippage exceeds 5%
const unstakeSlippageExceedsLimit = useMemo(() => {
if (mode === 'unstake' && !redeemAll && burnSwapAmounts?.calculatedSlippage !== undefined) {
return Math.abs(burnSwapAmounts.calculatedSlippage) > 5;
}
return false;
}, [mode, burnSwapAmounts, redeemAll]);
// Fetch burn amounts (for unstake mode when redeeming all) // Fetch burn amounts (for unstake mode when redeeming all)
const { burnAmounts, loading: burnAmountsLoading } = useBurnAmounts( const { burnAmounts, loading: burnAmountsLoading } = useBurnAmounts(
@@ -726,58 +669,6 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
</div> </div>
)} )}
{/* Error messages for output zero */}
{mode === 'stake' && swapMintError && stakeAmount && (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive font-medium"> Cannot Process Stake</p>
<p className="text-xs text-destructive/80 mt-1">
{swapMintError}
</p>
</div>
)}
{mode === 'unstake' && !redeemAll && burnSwapError && stakeAmount && (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive font-medium"> Cannot Process Unstake</p>
<p className="text-xs text-destructive/80 mt-1">
{burnSwapError}
</p>
</div>
)}
{/* Slippage warnings - consolidated for both stake and unstake modes */}
{mode === 'stake' && !swapMintError && slippageExceedsLimit && swapMintAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={swapMintAmounts.calculatedSlippage}
action="stake"
isError={true}
/>
)}
{mode === 'stake' && !swapMintError && !slippageExceedsLimit && slippageIsHigh && swapMintAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={swapMintAmounts.calculatedSlippage}
action="stake"
isError={false}
/>
)}
{mode === 'unstake' && !redeemAll && !burnSwapError && unstakeSlippageExceedsLimit && burnSwapAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={burnSwapAmounts.calculatedSlippage}
action="unstake"
isError={true}
/>
)}
{mode === 'unstake' && !redeemAll && !burnSwapError && !unstakeSlippageExceedsLimit && slippageIsHigh && burnSwapAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={burnSwapAmounts.calculatedSlippage}
action="unstake"
isError={false}
/>
)}
{/* Burn Swap Amounts Display (Unstake Mode) */} {/* Burn Swap Amounts Display (Unstake Mode) */}
{mode === 'unstake' && !redeemAll && burnSwapAmounts && selectedToken && !isAmountExceedingBalance && ( {mode === 'unstake' && !redeemAll && burnSwapAmounts && selectedToken && !isAmountExceedingBalance && (
<div className="px-4 py-3 bg-muted/30 rounded-lg space-y-2"> <div className="px-4 py-3 bg-muted/30 rounded-lg space-y-2">
@@ -826,10 +717,10 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
!selectedPool || !selectedPool ||
isAmountExceedingBalance || isAmountExceedingBalance ||
(mode === 'stake' (mode === 'stake'
? (!selectedToken || isSwapMinting || slippageExceedsLimit || !!swapMintError) ? (!selectedToken || isSwapMinting)
: (redeemAll : (redeemAll
? isBurning ? isBurning
: (!selectedToken || inputTokenIndex === undefined || isBurnSwapping || unstakeSlippageExceedsLimit || !!burnSwapError))) : (!selectedToken || inputTokenIndex === undefined || isBurnSwapping)))
} }
> >
{!isConnected {!isConnected

View File

@@ -6,19 +6,17 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ArrowDownUp, ChevronDown, Settings, CheckCircle, XCircle, Loader2 } from 'lucide-react'; import { ArrowDownUp, ChevronDown, Settings, CheckCircle, XCircle, Loader2 } from 'lucide-react';
import { useAccount, useChainId } from 'wagmi'; import { useAccount } from 'wagmi';
import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner'; import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner';
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool'; import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
import { formatUnits, parseUnits } from 'viem'; import { formatUnits, parseUnits } from 'viem';
import { SwapReviewModal } from './swap-review-modal'; import { SwapReviewModal } from './swap-review-modal';
import UniswapQuote from './uniswap-quote';
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error'; type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
export function SwapForm() { export function SwapForm() {
const { t } = useTranslation(); const { t } = useTranslation();
const { isConnected, address } = useAccount(); const { isConnected, address } = useAccount();
const chainId = useChainId();
const [fromAmount, setFromAmount] = useState(''); const [fromAmount, setFromAmount] = useState('');
const [toAmount, setToAmount] = useState(''); const [toAmount, setToAmount] = useState('');
const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null); const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null);
@@ -75,14 +73,6 @@ export function SwapForm() {
} }
}, [selectedFromToken, fromAmount]); }, [selectedFromToken, fromAmount]);
// Check if calculated slippage exceeds 5%
const slippageExceedsLimit = useMemo(() => {
if (!swapAmounts || swapAmounts.length === 0 || swapAmounts[0].calculatedSlippage === undefined) {
return false;
}
return Math.abs(swapAmounts[0].calculatedSlippage) > 5;
}, [swapAmounts]);
// Initialize swap hook // Initialize swap hook
const { executeSwap, estimateGas, isSwapping, gasEstimate, isEstimatingGas } = useSwap(); const { executeSwap, estimateGas, isSwapping, gasEstimate, isEstimatingGas } = useSwap();
@@ -96,8 +86,6 @@ export function SwapForm() {
const swapResult = swapAmounts[0]; // Get the first (and should be only) result const swapResult = swapAmounts[0]; // Get the first (and should be only) result
const formattedAmount = formatUnits(swapResult.amountOut, selectedToToken.decimals); const formattedAmount = formatUnits(swapResult.amountOut, selectedToToken.decimals);
setToAmount(formattedAmount); setToAmount(formattedAmount);
} else {
setToAmount('');
} }
}, [swapAmounts, selectedToToken, hasInsufficientBalance]); }, [swapAmounts, selectedToToken, hasInsufficientBalance]);
@@ -313,7 +301,6 @@ export function SwapForm() {
onChange={(e) => setToAmount(e.target.value)} onChange={(e) => setToAmount(e.target.value)}
className="text-2xl h-16" className="text-2xl h-16"
disabled={!selectedFromToken} disabled={!selectedFromToken}
readOnly
/> />
<div className="relative min-w-[160px] space-y-1" ref={toDropdownRef}> <div className="relative min-w-[160px] space-y-1" ref={toDropdownRef}>
<Button <Button
@@ -383,42 +370,18 @@ export function SwapForm() {
</div> </div>
)} )}
{/* Error message for slippage exceeding 5% */} {/* High slippage warning - show if calculated slippage exceeds max slippage OR 5% */}
{slippageExceedsLimit && ( {swapAmounts && swapAmounts.length > 0 && swapAmounts[0].calculatedSlippage !== undefined && (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg"> Math.abs(swapAmounts[0].calculatedSlippage) > Math.max(currentSlippage, 5)
<p className="text-sm text-destructive font-medium"> Slippage Exceeds 5%</p>
<p className="text-xs text-destructive/80 mt-1">
The estimated slippage for this swap is {Math.abs(swapAmounts![0].calculatedSlippage!).toFixed(2)}%.
We cannot process this swap as you may lose too much money due to the high slippage.
</p>
</div>
)}
{/* High slippage warning - show if calculated slippage exceeds max slippage but is under 5% */}
{!slippageExceedsLimit && swapAmounts && swapAmounts.length > 0 && swapAmounts[0].calculatedSlippage !== undefined && (
Math.abs(swapAmounts[0].calculatedSlippage) > currentSlippage
) && ( ) && (
<div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg"> <div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium"> Slippage Exceeds Your Tolerance</p> <p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium"> High Slippage Warning</p>
<p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1"> <p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1">
The estimated slippage for this swap is {Math.abs(swapAmounts[0].calculatedSlippage).toFixed(2)}%, which exceeds your maximum slippage setting of {currentSlippage}%. This swap may result in less favorable pricing than expected due to low liquidity. The estimated slippage for this swap is {Math.abs(swapAmounts[0].calculatedSlippage).toFixed(2)}%. You may lose money due to low liquidity in this pool.
</p> </p>
</div> </div>
)} )}
{/*/!* Uniswap Quote - Hidden (under construction) *!/*/}
{/*{false && fromAmount && selectedFromToken && selectedToToken && (*/}
{/* <UniswapQuote*/}
{/* amountIn={fromAmount}*/}
{/* tokenInAddress={selectedFromToken.address}*/}
{/* tokenOutAddress={selectedToToken.address}*/}
{/* tokenInDecimals={selectedFromToken.decimals}*/}
{/* tokenOutDecimals={selectedToToken.decimals}*/}
{/* tokenOutSymbol={selectedToToken.symbol}*/}
{/* chainId={chainId || 1}*/}
{/* />*/}
{/*)}*/}
{/* Gas Estimate, Slippage, and Fees */} {/* Gas Estimate, Slippage, and Fees */}
{isConnected && fromAmount && toAmount && ( {isConnected && fromAmount && toAmount && (
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2"> <div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">
@@ -457,14 +420,12 @@ export function SwapForm() {
<Button <Button
className="w-full h-14 text-lg" className="w-full h-14 text-lg"
onClick={() => setIsReviewModalOpen(true)} onClick={() => setIsReviewModalOpen(true)}
disabled={!isConnected || !fromAmount || !toAmount || !!poolsError || hasInsufficientBalance || slippageExceedsLimit} disabled={!isConnected || !fromAmount || !toAmount || !!poolsError || hasInsufficientBalance}
> >
{!isConnected {!isConnected
? t('swap.connectWalletToSwap') ? t('swap.connectWalletToSwap')
: hasInsufficientBalance : hasInsufficientBalance
? 'Insufficient Balance' ? 'Insufficient Balance'
: slippageExceedsLimit
? 'Slippage Too High'
: 'Review'} : 'Review'}
</Button> </Button>
</CardContent> </CardContent>
@@ -508,7 +469,7 @@ export function SwapForm() {
</span> </span>
</div> </div>
<p className="text-xs text-muted-foreground mt-2"> <p className="text-xs text-muted-foreground mt-2">
You will be warned if the slippage exceeds this setting, and you can choose whether to proceed with the trade. Your transaction will revert if the price changes unfavorably by more than this percentage.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -4,16 +4,18 @@ export default function TosCard() {
return ( return (
<div className="max-w-5xl mx-auto p-5"> <div className="max-w-5xl mx-auto p-5">
<div className="bg-card rounded-lg shadow-md p-6 border"> <div className="bg-card rounded-lg shadow-md p-6 border">
<h1 className="text-2xl font-semibold text-center mb-4">Terms of Service</h1> <h1 className="text-2xl font-semibold text-center mb-4">Dexorder Terms of Service</h1>
{/* MAKE SURE TO UPDATE THE VERSION VARIABLE AS WELL */} {/* MAKE SURE TO UPDATE THE VERSION VARIABLE AS WELL */}
<p className="text-center mb-4">Last Updated November 18, 2024</p> <p className="text-center mb-4">Last Updated November 18, 2024</p>
<div className="mb-4 leading-relaxed"> <div className="mb-4 leading-relaxed">
Please read these Terms of Service (the "<b>Terms</b>") Please read these Terms of Service (the "<b>Terms</b>") and our{' '}
<a href="https://dexorder.com/privacy-policy" target="dexorder">
Privacy Policy
</a>{' '}
carefully because they govern your use of the website (and all subdomains and subpages carefully because they govern your use of the website (and all subdomains and subpages
thereon) located at liquidity.party, including without limitation the subdomains thereon) located at dexorder.com, including without limitation the subdomains
app.liquidity.party and www.liquidity.party (collectively, the "<b>Site</b>"), and the app.dexorder.com and www.dexorder.com (collectively, the "<b>Site</b>"), and the Dexorder
Liquidity Party
web application graphical user interface and any other services accessible via the Site web application graphical user interface and any other services accessible via the Site
(together with the Site, web application, and other services, collectively, the " (together with the Site, web application, and other services, collectively, the "
<b>Dexorder Service</b>") offered by Dexorder Trading Services Ltd. ("<b>Dexorder</b>," " <b>Dexorder Service</b>") offered by Dexorder Trading Services Ltd. ("<b>Dexorder</b>," "
@@ -43,14 +45,29 @@ export default function TosCard() {
<div className="mb-4 leading-relaxed"> <div className="mb-4 leading-relaxed">
(a) The Dexorder Service allows you to access an online web application graphical user (a) The Dexorder Service allows you to access an online web application graphical user
interface (the "<b>App</b>") which enables you to interact with a protocol consisting of a interface (the "<b>App</b>") which enables you to interact with a protocol consisting of a
set of smart contracts (the "<b>Protocol</b>"). set of smart contracts (the "<b>Smart Contracts</b>") and to create and interact with a
You may use the <b>App</b> to send signals to, interact with, and initiate actions on the user controlled smart contract involving digital assets over which only you have upgrade
decentralized exchange ("<b>DEX</b>"). authority (a "<b>Vault</b>" and together with the Smart Contracts, the "<b>Protocol</b>").
You may use your Vault to send signals to, interact with, and initiate actions on third
party smart contract blockchain protocols ("<b>Interactions</b>") operating on
decentralized exchanges (e.g., Uniswap) ("<b>DEXs</b>"). Certain Interactions may require
threshold parameters to be met and that a third party transmit an oracle related activation
signal (the "<b>Activation Signal</b>") to the Vault in order to effectuate your commands
(such party being an "<b>Oracle</b>"). Dexorder may in the ordinary course of events be an
Oracle ("<b>Dexorder Oracle</b>"), but bears no obligation nor promise to do so on an
ongoing basis, and you may send such Activation Signal yourself or utilize a third-party
Oracle to do so. Further, Dexorder is not and does not offer a digital wallet, and has no
custody or control over your digital wallet, which is never accessible by Dexorder, and only
users, and not Dexorder, may provide or withdraw tokens to be held by Vault. From time to
time, the Services may include making recommendations with respect to technical changes that
only you may accept and implement.
</div> </div>
<div className="mb-4 leading-relaxed"> <div className="mb-4 leading-relaxed">
(b) <b>Interface</b>. The Dexorder Service provides you with access to the Protocol. (b) <b>Interface</b>. The Dexorder Service provides you with access to the Protocol, which
All information provided in is a user controlled, non-custodial protocol, upgradeable only by your actions and consent,
deployed on the blockchains indicated on our Site, and provides information and interaction
capabilities with other blockchain related service providers. All information provided in
connection with your access and use of the Dexorder Service is for informational purposes connection with your access and use of the Dexorder Service is for informational purposes
only. You should not take, or refrain from taking, any action based on any information only. You should not take, or refrain from taking, any action based on any information
contained on the Dexorder Service or any other information that we make available at any contained on the Dexorder Service or any other information that we make available at any
@@ -191,8 +208,12 @@ export default function TosCard() {
<div className="mb-4 leading-relaxed ml-8"> <div className="mb-4 leading-relaxed ml-8">
(i) Subject to your compliance with these Terms, Dexorder will use its commercially (i) Subject to your compliance with these Terms, Dexorder will use its commercially
reasonable efforts to provide you with access to the Dexorder Service and to cause your reasonable efforts to provide you with access to the Dexorder Service and to cause your
Interactions to be executed on the Protocol, however from time to time the Site and Interactions to be executed on the applicable DEX in accordance with Dexorder's Execution
the Dexorder Service may Policy located at{' '}
<a href="https://dexorder.com/execution-policy">
https://dexorder.com/execution-policy
</a>{' '}
("<b>Execution Policy</b>"), however from time to time the Site and the Dexorder Service may
be inaccessible or inoperable for any reason, including, without limitation: (a) if an be inaccessible or inoperable for any reason, including, without limitation: (a) if an
Interaction repeatedly fails to be executed (such as due to an error in Interaction Interaction repeatedly fails to be executed (such as due to an error in Interaction
execution or a malfunction in the Dexorder Service); (b) equipment malfunctions; (c) execution or a malfunction in the Dexorder Service); (b) equipment malfunctions; (c)
@@ -200,7 +221,8 @@ export default function TosCard() {
contractors may undertake from time to time; (d) causes beyond Dexorder's control or that contractors may undertake from time to time; (d) causes beyond Dexorder's control or that
Dexorder could not reasonably foresee; (e) disruptions and temporary or permanent Dexorder could not reasonably foresee; (e) disruptions and temporary or permanent
unavailability of underlying blockchain infrastructure; (f) unavailability of third-party unavailability of underlying blockchain infrastructure; (f) unavailability of third-party
service providers or external partners for any reason. service providers or external partners for any reason; or (g) an Activation Signal not being
sent.
</div> </div>
<div className="mb-4 leading-relaxed ml-8"> <div className="mb-4 leading-relaxed ml-8">
(ii) the Site and the Dexorder Service may evolve, which means Dexorder may apply changes, (ii) the Site and the Dexorder Service may evolve, which means Dexorder may apply changes,
@@ -223,9 +245,11 @@ export default function TosCard() {
<h2 className="text-xl font-semibold mt-6 mb-4">6. Interactions; Fees</h2> <h2 className="text-xl font-semibold mt-6 mb-4">6. Interactions; Fees</h2>
<div className="mb-4 leading-relaxed"> <div className="mb-4 leading-relaxed">
(a) <u>Interactions</u>. In order to effectuate Interactions, you may need to transfer (a) <u>Interactions</u>. In order to effectuate Interactions, you may need to transfer
digital assets (e.g., tokens). You acknowledge that you may use the Dexorder digital assets (e.g., tokens) to the Vault. You acknowledge that you may use the Dexorder
Services to process and cause Interactions to operate on the Protocol. Services to process and cause Interactions to be operate with an applicable DEX, including
Dexorder is an interface to that smart contract, and does not offer a digital without limitation the transfer of digital assets via the DEX in accordance with the
Interaction. For clarity, the Vault is a smart contract automatically controlled by the
blockchain. Dexorder is an interface to that smart contract, and does not offer a digital
wallet and has no custody or control over your digital wallet or any digital assets or wallet and has no custody or control over your digital wallet or any digital assets or
cryptocurrency, which is never accessible by Dexorder. cryptocurrency, which is never accessible by Dexorder.
</div> </div>
@@ -233,8 +257,8 @@ export default function TosCard() {
(b) <u>Fees</u>. (b) <u>Fees</u>.
</div> </div>
<div className="mb-4 leading-relaxed ml-8"> <div className="mb-4 leading-relaxed ml-8">
(i) Dexorder charges fees for usage of the Dexorder Services at the time of user (i) Dexorder charges fees upfront for usage of the Dexorder Services at the time of user
Interactions ("<b>Fees</b>"). You agree to pay all applicable Fees to Dexorder, in Interactions ("<b>Fees</b>"). You agree to pay all applicable Fees upfront to Dexorder, in
the amounts communicated or presented to you via the Dexorder Service in connection with the amounts communicated or presented to you via the Dexorder Service in connection with
usage of the Dexorder Service. Each party shall be responsible for all Taxes imposed on its usage of the Dexorder Service. Each party shall be responsible for all Taxes imposed on its
income or property. income or property.
@@ -392,7 +416,8 @@ export default function TosCard() {
<h2 className="text-xl font-semibold mt-6 mb-4">9. Links to Third Party Websites or Resources</h2> <h2 className="text-xl font-semibold mt-6 mb-4">9. Links to Third Party Websites or Resources</h2>
<div className="mb-4 leading-relaxed"> <div className="mb-4 leading-relaxed">
The Dexorder Service may allow you to access third-party websites, integrations, or other The Dexorder Service may allow you to access third-party websites, integrations, or other
resources, including the Protocol (collectively, "<b>Third Party Resources</b>"). We resources, including the DEX, services providing Activation Signals, and any bridge between
the DEX and any third party protocols (collectively, "<b>Third Party Resources</b>"). We
provide access only as a convenience and are not responsible for the content, products or provide access only as a convenience and are not responsible for the content, products or
services on or available from those resources or links displayed on such websites. You services on or available from those resources or links displayed on such websites. You
acknowledge sole responsibility for and assume all risk arising from, your use of any acknowledge sole responsibility for and assume all risk arising from, your use of any
@@ -415,7 +440,7 @@ export default function TosCard() {
<div className="mb-4 leading-relaxed"> <div className="mb-4 leading-relaxed">
THE DEXORDER SERVICE (INCLUDING WITHOUT LIMITATION THE VAULT) AND ANY CONTENT CONTAINED THE DEXORDER SERVICE (INCLUDING WITHOUT LIMITATION THE VAULT) AND ANY CONTENT CONTAINED
THEREIN, AS WELL AS THE PROTOCOL, THE DEXORDER ORACLE, AND ANY ASSOCIATED PROTOCOL OR THEREIN, AS WELL AS THE PROTOCOL, THE DEXORDER ORACLE, AND ANY ASSOCIATED PROTOCOL OR
BLOCKCHAIN MESSAGING FUNCTIONALITY UNDERLYING THE DEXORDER BLOCKCHAIN MESSAGING FUNCTIONALITY SUCH AS ACTIVATION SIGNALS UNDERLYING THE DEXORDER
SERVICE (TOGETHER, THE "<b>UTILITIES</b>"), ARE PROVIDED "AS IS," WITHOUT WARRANTY OF ANY SERVICE (TOGETHER, THE "<b>UTILITIES</b>"), ARE PROVIDED "AS IS," WITHOUT WARRANTY OF ANY
KIND. WITHOUT LIMITING THE FOREGOING, WE EXPLICITLY DISCLAIM ANY IMPLIED WARRANTIES OF KIND. WITHOUT LIMITING THE FOREGOING, WE EXPLICITLY DISCLAIM ANY IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT AND NON-INFRINGEMENT, AND MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT AND NON-INFRINGEMENT, AND
@@ -451,6 +476,25 @@ export default function TosCard() {
USE OF VIRUSES, PHISHING, BRUTEFORCING OR OTHER MEANS OF ATTACK AGAINST ANY BLOCKCHAIN USE OF VIRUSES, PHISHING, BRUTEFORCING OR OTHER MEANS OF ATTACK AGAINST ANY BLOCKCHAIN
NETWORK UNDERLYING THE UTILITIES. NETWORK UNDERLYING THE UTILITIES.
</div> </div>
<div className="mb-4 leading-relaxed">
THE UTILITIES MAY INCLUDE THE PLACEMENT OR EXECUTION OF AN ACTIVATION SIGNAL TO A USER VAULT
WITH RESPECT TO USER DEFINED INTERACTIONS (E.G., VIA TRANSMITTING AN ACTIVATION SIGNAL WHICH
TRIGGERS INTERACTION EXECUTION), HOWEVER, ANYONE, INCLUDING YOU OR ANY THIRD PARTY, MAY
CAUSE AN INTERACTION TO BE EXECUTED (SUCH AS BY TRANSMITTING THE APPLICABLE ACTIVATION
SIGNAL), AND NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THESE TERMS, DEXORDER DOES NOT
GUARANTEE THE PLACEMENT OR EXECUTION OR ANY INTERACTION, INCLUDING WITHOUT LIMITATION THAT
DEXORDER WILL TRANSMIT ANY PARTICULAR ACTIVATION SIGNAL TO TRIGGER EXECUTION OF ANY
INTERACTION, OR THAT AN INTERACTION WILL OTHERWISE BE PROPERLY CARRIED OUT PURSUANT TO A
VAULT. YOU ACKNOWLEDGE AND AGREE THAT YOU WILL NOT RELY ON THE UTILITIES TO CARRY OUT
PLACEMENTS OR EXECUTIONS OF INTERACTIONS. IF ANY PARTICULAR INTERACTION THAT IS PLACED IS
NOT EXECUTED BY DEXORDER IN A TIMELY MANNER, YOU MAY CAUSE SUCH INTERACTION TO BE EXECUTED
YOURSELF OR BY ENGAGING A THIRD PARTY SERVICE PROVIDER TO DO SO (E.G., BY TRANSMITTING THE
APPLICABLE ACTIVATION SIGNAL YOURSELF OR ENGAGING A THIRD PARTY TO DO SO). YOU ACCEPT THE
INHERENT RISK THAT ANY PARTICULAR INTERACTION MAY NOT BE EXECUTED, INCLUDING WITHOUT
LIMITATION DUE TO BAD ACTORS OR THE MALFUNCTION OF THE UTILITIES, AND DEXORDER HEREBY
DISCLAIMS ANY AND ALL LIABILITY AND RESPONSIBILITY IN CONNECTION WITH THE PLACEMENT OR
EXECUTION OF INTERACTIONS AND THE VAULT.
</div>
<div className="mb-4 leading-relaxed"> <div className="mb-4 leading-relaxed">
THE UTILITIES MAY NOT BE AVAILABLE DUE TO ANY NUMBER OF FACTORS INCLUDING, BUT NOT LIMITED THE UTILITIES MAY NOT BE AVAILABLE DUE TO ANY NUMBER OF FACTORS INCLUDING, BUT NOT LIMITED
TO, PERIODIC SYSTEM MAINTENANCE, SCHEDULED OR UNSCHEDULED, ACTS OF GOD, UNAUTHORIZED ACCESS, TO, PERIODIC SYSTEM MAINTENANCE, SCHEDULED OR UNSCHEDULED, ACTS OF GOD, UNAUTHORIZED ACCESS,

View File

@@ -1,248 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import { parseUnits, formatUnits } from 'viem';
interface UniswapQuoteProps {
amountIn: string;
tokenInAddress: string | null;
tokenOutAddress: string | null;
tokenInDecimals: number;
tokenOutDecimals: number;
tokenOutSymbol: string;
chainId: number;
}
interface TokenPrices {
[key: string]: number;
}
export default function UniswapQuote({
amountIn,
tokenInAddress,
tokenOutAddress,
tokenInDecimals,
tokenOutDecimals,
tokenOutSymbol,
chainId
}: UniswapQuoteProps) {
const [quote, setQuote] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [prices, setPrices] = useState<TokenPrices | null>(null);
// Only show on mainnet
if (chainId !== 1) {
return null;
}
// Don't fetch quote if tokens aren't selected
if (!tokenInAddress || !tokenOutAddress) {
return null;
}
// Fetch token prices from CoinGecko using contract addresses
useEffect(() => {
if (!tokenInAddress || !tokenOutAddress) return;
const fetchPrices = async () => {
try {
// Fetch both token prices separately using the correct endpoint
const [tokenInResponse, tokenOutResponse] = await Promise.all([
fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${tokenInAddress}&vs_currencies=usd`),
fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${tokenOutAddress}&vs_currencies=usd`)
]);
const tokenInData = await tokenInResponse.json();
const tokenOutData = await tokenOutResponse.json();
const tokenInPrice = tokenInData[tokenInAddress.toLowerCase()]?.usd || 0;
const tokenOutPrice = tokenOutData[tokenOutAddress.toLowerCase()]?.usd || 0;
setPrices({
tokenIn: tokenInPrice,
tokenOut: tokenOutPrice
});
console.log('Token prices:', { tokenInPrice, tokenOutPrice, tokenInData, tokenOutData });
} catch (err) {
console.error('Failed to fetch prices:', err);
}
};
fetchPrices();
// Refresh prices every 30 seconds
const interval = setInterval(fetchPrices, 30000);
return () => clearInterval(interval);
}, [tokenInAddress, tokenOutAddress]);
const getQuote = async () => {
if (!amountIn || parseFloat(amountIn) <= 0) {
setError('Please enter a valid amount');
return;
}
setLoading(true);
setError('');
try {
// Convert amount to smallest unit based on token decimals using viem
const amountInSmallestUnit = parseUnits(amountIn, tokenInDecimals).toString();
const response = await fetch('https://api.uniswap.org/v2/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Origin': 'https://app.uniswap.org'
},
body: JSON.stringify({
amount: amountInSmallestUnit,
tokenIn: tokenInAddress,
tokenInChainId: chainId,
tokenOut: tokenOutAddress,
tokenOutChainId: chainId,
type: 'EXACT_INPUT',
configs: [
{
protocols: ['V2', 'V3', 'V4'],
enableUniversalRouter: true,
routingType: 'CLASSIC'
}
]
})
});
if (!response.ok) throw new Error('Failed to fetch quote');
const data = await response.json();
console.log('Uniswap Quote:', data);
setQuote(data);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const formatTokenAmount = (amount: string) => {
const formatted = formatUnits(BigInt(amount), tokenOutDecimals);
return parseFloat(formatted).toLocaleString('en-US', {
maximumFractionDigits: tokenOutDecimals >= 18 ? 0 : 6
});
};
const getQuoteAmount = () => {
if (!quote) return '0';
// Handle nested quote structure
return quote.quote?.quote || quote.quote || '0';
};
const calculateCostBreakdown = () => {
if (!quote || !prices || !prices.tokenIn) return null;
const tokenAmount = parseFloat(amountIn);
const tradeValueUSD = tokenAmount * prices.tokenIn;
// Access nested quote object
const quoteData = quote.quote || quote;
// 1. Gas Cost
const gasCostUSD = parseFloat(quoteData.gasUseEstimateUSD || '0');
// 2. Uniswap UX Fee (0.25%)
const uniswapFeePercent = 0.25;
const uniswapFeeUSD = (uniswapFeePercent / 100) * tradeValueUSD;
const totalCostUSD = gasCostUSD + uniswapFeeUSD;
console.log('Cost breakdown calc:', {
gasCostUSD,
uniswapFeeUSD,
totalCostUSD,
tradeValueUSD,
quoteData
});
return {
gasCostUSD,
uniswapFeePercent,
uniswapFeeUSD,
totalCostUSD,
tradeValueUSD
};
};
const costBreakdown = calculateCostBreakdown();
return (
<div className="w-full max-w-md p-4">
<button
onClick={getQuote}
disabled={loading}
className="w-full bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded"
>
{loading ? 'Getting Quote...' : 'Get Quote'}
</button>
{error && (
<div className="mt-3 p-3 bg-red-50 text-red-700 rounded text-sm">
{error}
</div>
)}
{quote && costBreakdown && (
<div className="mt-4 space-y-3">
<div className="p-3 bg-gray-50 rounded">
<div className="text-sm text-gray-600">You Get ({tokenOutSymbol})</div>
<div className="text-xl font-bold">
{formatTokenAmount(getQuoteAmount())}
</div>
</div>
{/* Total Costs Breakdown */}
<div className="p-4 bg-blue-50 rounded-lg space-y-3">
<h3 className="font-semibold text-lg text-gray-800">Total Costs Breakdown</h3>
{/* 1. Gas Cost */}
<div className="space-y-1">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-gray-700">1. Gas Cost (Network Fee)</span>
<span className="text-sm font-bold text-gray-900">
${costBreakdown.gasCostUSD.toFixed(2)} USD
</span>
</div>
</div>
{/* 2. Uniswap UX Fee */}
<div className="space-y-1">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-gray-700">2. Uniswap UX Fee</span>
<span className="text-sm font-bold text-gray-900">
{costBreakdown.uniswapFeePercent}%
</span>
</div>
<div className="text-xs text-gray-600 pl-4">
${costBreakdown.uniswapFeeUSD.toFixed(2)} USD
</div>
</div>
{/* Total */}
<div className="pt-2 border-t border-gray-300">
<div className="flex justify-between items-center">
<span className="text-sm font-bold text-gray-800">Total Estimated Cost</span>
<span className="text-base font-bold text-red-600">
${costBreakdown.totalCostUSD.toFixed(2)} USD
</span>
</div>
</div>
{/* Trade Value Reference */}
<div className="text-xs text-gray-500 text-center pt-1">
Trade Value: ${costBreakdown.tradeValueUSD.toFixed(2)} USD
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -183,8 +183,8 @@ const IPartyInfoABI = [
"outputs": [ "outputs": [
{ {
"name": "", "name": "",
"type": "uint256", "type": "int128",
"internalType": "uint256" "internalType": "int128"
} }
], ],
"stateMutability": "view" "stateMutability": "view"

View File

@@ -415,19 +415,6 @@ const IPartyPoolABI = [
], ],
"stateMutability": "payable" "stateMutability": "payable"
}, },
{
"type": "function",
"name": "mintImpl",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "name", "name": "name",
@@ -618,19 +605,6 @@ const IPartyPoolABI = [
], ],
"stateMutability": "view" "stateMutability": "view"
}, },
{
"type": "function",
"name": "swapImpl",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "swapMint", "name": "swapMint",

View File

@@ -8,6 +8,7 @@ import IPartyPoolABI from '@/contracts/IPartyPoolABI';
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI'; import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
import IPartyInfoABI from '@/contracts/IPartyInfoABI'; import IPartyInfoABI from '@/contracts/IPartyInfoABI';
import { ERC20ABI } from '@/contracts/ERC20ABI'; import { ERC20ABI } from '@/contracts/ERC20ABI';
import { calculateSlippage } from './usePartyPool';
// Helper function to format large numbers with K, M, B suffixes // Helper function to format large numbers with K, M, B suffixes
function formatTVL(value: number): string { function formatTVL(value: number): string {
@@ -99,8 +100,6 @@ export interface SwapRoute {
poolAddress: `0x${string}`; poolAddress: `0x${string}`;
inputTokenIndex: number; inputTokenIndex: number;
outputTokenIndex: number; outputTokenIndex: number;
inputTokenDecimal: number;
outputTokenDecimal: number;
} }
export interface AvailableToken { export interface AvailableToken {
@@ -198,131 +197,68 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
return; return;
} }
// First, fetch all tokens from all working pools
const poolTokensContracts = workingPools.map(poolAddress => ({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'allTokens',
}));
const poolTokensResults = await publicClient.multicall({
contracts: poolTokensContracts as any,
allowFailure: true,
});
// Build a flat list of all unique token addresses we need to query
const uniqueTokenAddresses = new Set<`0x${string}`>();
uniqueTokenAddresses.add(tokenAddress); // Add input token
poolTokensResults.forEach((result) => {
if (result.status === 'success') {
const tokens = result.result as readonly `0x${string}`[];
tokens.forEach(token => uniqueTokenAddresses.add(token));
}
});
const tokenAddressesArray = Array.from(uniqueTokenAddresses);
// Build multicall for all token symbols and decimals
const tokenDataContracts = tokenAddressesArray.flatMap(addr => [
{
address: addr,
abi: ERC20ABI,
functionName: 'symbol',
},
{
address: addr,
abi: ERC20ABI,
functionName: 'decimals',
},
]);
const tokenDataResults = await publicClient.multicall({
contracts: tokenDataContracts as any,
allowFailure: true,
});
// Parse token data into a map
const tokenDataMap = new Map<string, { symbol: string | null; decimals: number | null }>();
for (let i = 0; i < tokenAddressesArray.length; i++) {
const symbolResult = tokenDataResults[i * 2];
const decimalsResult = tokenDataResults[i * 2 + 1];
tokenDataMap.set(tokenAddressesArray[i].toLowerCase(), {
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : null,
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : null,
});
}
// Map to store available tokens with their swap routes // Map to store available tokens with their swap routes
const tokenRoutesMap = new Map<string, AvailableToken>(); const tokenRoutesMap = new Map<string, AvailableToken>();
// For each working pool, process tokens // For each working pool, fetch all tokens and track indices
for (let poolIdx = 0; poolIdx < workingPools.length; poolIdx++) { for (const poolAddress of workingPools) {
const poolAddress = workingPools[poolIdx]; try {
const poolTokensResult = poolTokensResults[poolIdx]; const tokensInPool = await publicClient.readContract({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'allTokens',
}) as readonly `0x${string}`[];
if (poolTokensResult.status !== 'success') { // Find the input token index in this pool
console.error('Failed to fetch tokens for pool', poolAddress); const inputTokenIndex = tokensInPool.findIndex(
continue; (token) => token.toLowerCase() === tokenAddress.toLowerCase()
} );
const tokensInPool = poolTokensResult.result as readonly `0x${string}`[]; if (inputTokenIndex === -1) {
console.error('Input token not found in pool', poolAddress);
// Find the input token index in this pool
const inputTokenIndex = tokensInPool.findIndex(
(token) => token.toLowerCase() === tokenAddress.toLowerCase()
);
if (inputTokenIndex === -1) {
console.error('Input token not found in pool', poolAddress);
continue;
}
const inputTokenData = tokenDataMap.get(tokenAddress.toLowerCase());
const inputTokenDecimal = inputTokenData?.decimals ?? null;
// Process each token in the pool
for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) {
const outputTokenAddress = tokensInPool[outputTokenIndex];
// Skip if it's the same as the input token
if (outputTokenIndex === inputTokenIndex) {
continue; continue;
} }
const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase()); // Process each token in the pool
const outputTokenSymbol = outputTokenData?.symbol ?? null; for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) {
const outputTokenDecimal = outputTokenData?.decimals ?? null; const outputTokenAddress = tokensInPool[outputTokenIndex];
// Skip tokens with the same symbol as the selected token // Skip if it's the same as the input token
if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) { if (outputTokenIndex === inputTokenIndex) {
continue; continue;
} }
// Skip tokens if decimals failed to load // Get the symbol of this token
if (inputTokenDecimal === null || outputTokenDecimal === null) { const outputTokenSymbol = await publicClient.readContract({
console.error(`Failed to load decimals for token ${outputTokenAddress} or ${tokenAddress}`);
continue;
}
// Create or update the available token entry
const tokenKey = outputTokenAddress.toLowerCase();
if (!tokenRoutesMap.has(tokenKey)) {
tokenRoutesMap.set(tokenKey, {
address: outputTokenAddress, address: outputTokenAddress,
symbol: outputTokenSymbol, abi: ERC20ABI,
swapRoutes: [], functionName: 'symbol',
}).catch(() => null);
// Skip tokens with the same symbol as the selected token
if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) {
continue;
}
// Create or update the available token entry
const tokenKey = outputTokenAddress.toLowerCase();
if (!tokenRoutesMap.has(tokenKey)) {
tokenRoutesMap.set(tokenKey, {
address: outputTokenAddress,
symbol: outputTokenSymbol,
swapRoutes: [],
});
}
// Add this swap route
tokenRoutesMap.get(tokenKey)!.swapRoutes.push({
poolAddress,
inputTokenIndex,
outputTokenIndex,
}); });
} }
} catch (err) {
// Add this swap route console.error('Error fetching tokens from pool', poolAddress, err);
tokenRoutesMap.get(tokenKey)!.swapRoutes.push({
poolAddress,
inputTokenIndex,
outputTokenIndex,
inputTokenDecimal,
outputTokenDecimal,
});
} }
} }
@@ -369,54 +305,55 @@ export function useTokenDetails(userAddress: `0x${string}` | undefined) {
return; return;
} }
// Build multicall contracts array - 4 calls per token (name, symbol, decimals, balanceOf)
const contracts = tokens.flatMap((tokenAddress) => [
{
address: tokenAddress,
abi: ERC20ABI,
functionName: 'name',
},
{
address: tokenAddress,
abi: ERC20ABI,
functionName: 'symbol',
},
{
address: tokenAddress,
abi: ERC20ABI,
functionName: 'decimals',
},
{
address: tokenAddress,
abi: ERC20ABI,
functionName: 'balanceOf',
args: [userAddress],
},
]);
// Execute multicall
const results = await publicClient.multicall({
contracts: contracts as any,
allowFailure: true,
});
// Parse results
const details: TokenDetails[] = []; const details: TokenDetails[] = [];
for (let i = 0; i < tokens.length; i++) {
const baseIndex = i * 4;
const nameResult = results[baseIndex];
const symbolResult = results[baseIndex + 1];
const decimalsResult = results[baseIndex + 2];
const balanceResult = results[baseIndex + 3];
details.push({ // Make individual calls for each token
address: tokens[i], for (let i = 0; i < tokens.length; i++) {
name: nameResult.status === 'success' ? (nameResult.result as string) : 'Unknown', const tokenAddress = tokens[i];
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : '???', try {
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18, const [name, symbol, decimals, balance] = await Promise.all([
balance: balanceResult.status === 'success' ? (balanceResult.result as bigint) : BigInt(0), publicClient.readContract({
index: i, address: tokenAddress,
}); abi: ERC20ABI,
functionName: 'name',
}).catch(() => 'Unknown'),
publicClient.readContract({
address: tokenAddress,
abi: ERC20ABI,
functionName: 'symbol',
}).catch(() => '???'),
publicClient.readContract({
address: tokenAddress,
abi: ERC20ABI,
functionName: 'decimals',
}).catch(() => 18),
publicClient.readContract({
address: tokenAddress,
abi: ERC20ABI,
functionName: 'balanceOf',
args: [userAddress],
}).catch(() => BigInt(0)),
]);
details.push({
address: tokenAddress,
name: name as string,
symbol: symbol as string,
decimals: Number(decimals),
balance: balance as bigint,
index: i,
});
} catch (err) {
// Add token with fallback values if individual call fails
details.push({
address: tokenAddress,
name: 'Unknown',
symbol: '???',
decimals: 18,
balance: BigInt(0),
index: i,
});
}
} }
setTokenDetails(details); setTokenDetails(details);
@@ -528,12 +465,42 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
let tvlStr: string | undefined; let tvlStr: string | undefined;
if (isWorking) { if (isWorking) {
// Fetch token decimals and balance first (needed for both price and TVL) // Fetch pool price (use first token as quote, index 0)
console.log('fetching pool price')
try {
const priceRaw = await publicClient.readContract({
address: partyInfoAddress as `0x${string}`,
abi: IPartyInfoABI,
functionName: 'poolPrice',
args: [poolAddress, BigInt(0)],
});
const price = BigInt(priceRaw as bigint | number);
if (price === 0n) {
priceStr = undefined;
} else {
// Convert Q64 format to decimal (price = priceValue / 2^64)
const Q64 = 2n ** 64n;
const isNegative = price < 0n;
const absPrice = isNegative ? -price : price;
const priceFloat = Number(absPrice) / Number(Q64);
const finalPrice = isNegative ? -priceFloat : priceFloat;
priceStr = `$${finalPrice.toFixed(4)}`;
}
} catch (err) {
console.error(`Error fetching pool price for ${poolAddress}:`, err);
priceStr = undefined;
}
// Calculate TVL (approximate by getting first token balance and doubling it)
try { try {
if (tokens && tokens.length > 0) { if (tokens && tokens.length > 0) {
const firstTokenAddress = tokens[0]; const firstTokenAddress = tokens[0];
// Get token decimals, balance, and pool price in parallel
const [decimals, balance, priceRaw] = await Promise.all([ // Get token decimals and balance
const [decimals, balance] = await Promise.all([
publicClient.readContract({ publicClient.readContract({
address: firstTokenAddress as `0x${string}`, address: firstTokenAddress as `0x${string}`,
abi: ERC20ABI, abi: ERC20ABI,
@@ -545,36 +512,16 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
functionName: 'balanceOf', functionName: 'balanceOf',
args: [poolAddress], args: [poolAddress],
}) as Promise<bigint>, }) as Promise<bigint>,
publicClient.readContract({
address: partyInfoAddress as `0x${string}`,
abi: IPartyInfoABI,
functionName: 'poolPrice',
args: [poolAddress, BigInt(0)],
}),
]); ]);
// Calculate pool price using actual token decimals // Convert balance to float and double it for total TVL approximation
const price = BigInt(priceRaw as bigint | number);
if (price === 0n) {
priceStr = undefined;
} else {
// Convert Q64 format to decimal (price = priceValue / 2^64)
const Q64 = 2n ** 64n;
const priceFloat = Number(price) / Number(Q64);
// Adjust for token decimals (poolPrice assumes 18 decimals, adjust based on actual token decimals)
const finalPrice = priceFloat * (Math.pow(10, 18) / Math.pow(10, decimals));
priceStr = `$${finalPrice.toFixed(4)}`;
}
// Calculate TVL (approximate by getting first token balance and multiplying by number of tokens)
const tokenBalance = Number(balance) / Math.pow(10, decimals); const tokenBalance = Number(balance) / Math.pow(10, decimals);
const approximateTVL = tokenBalance * tokens.length; const approximateTVL = tokenBalance * 3;
tvlStr = formatTVL(approximateTVL); tvlStr = formatTVL(approximateTVL);
} }
} catch (err) { } catch (err) {
console.error(`Error fetching pool price/TVL for ${poolAddress}:`, err); console.error(`Error fetching TVL for ${poolAddress}:`, err);
priceStr = undefined;
tvlStr = undefined; tvlStr = undefined;
} }
} }
@@ -633,7 +580,7 @@ export function useSwapMintAmounts(
poolAddress: `0x${string}` | undefined, poolAddress: `0x${string}` | undefined,
inputTokenIndex: number | undefined, inputTokenIndex: number | undefined,
maxAmountIn: bigint | undefined, maxAmountIn: bigint | undefined,
inputTokenDecimals: number | undefined // Decimals of the input token lpTokenPrice?: number // Market price of the LP token in decimal format
) { ) {
const publicClient = usePublicClient(); const publicClient = usePublicClient();
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@@ -654,11 +601,8 @@ export function useSwapMintAmounts(
} }
const fetchSwapMintAmounts = async () => { const fetchSwapMintAmounts = async () => {
if (!publicClient) return; if (!publicClient) {
setLoading(false);
// Early validation
if (inputTokenDecimals === undefined) {
setError('inputTokenDecimals is required but was undefined');
return; return;
} }
@@ -666,7 +610,9 @@ export function useSwapMintAmounts(
setLoading(true); setLoading(true);
setError(null); setError(null);
// Get chain ID and contract address
const chainId = await publicClient.getChainId(); const chainId = await publicClient.getChainId();
// @ts-ignore
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo; const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
if (!partyInfoAddress) { if (!partyInfoAddress) {
@@ -675,6 +621,7 @@ export function useSwapMintAmounts(
return; return;
} }
// Call swapMintAmounts function
const result = await publicClient.readContract({ const result = await publicClient.readContract({
address: partyInfoAddress as `0x${string}`, address: partyInfoAddress as `0x${string}`,
abi: IPartyPoolViewerABI, abi: IPartyPoolViewerABI,
@@ -682,42 +629,27 @@ export function useSwapMintAmounts(
args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn], args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn],
}) as readonly [bigint, bigint, bigint]; }) as readonly [bigint, bigint, bigint];
// Fetch and calculate pool price // Calculate slippage if LP token price is provided
let poolPrice: number | undefined;
let calculatedSlippage: number | undefined; let calculatedSlippage: number | undefined;
if (lpTokenPrice !== undefined) {
try { try {
const poolPriceInt128 = await publicClient.readContract({ // For swapMint: output is result[0] (amountInUsed), input is maxAmountIn, fee is result[2]
address: partyInfoAddress as `0x${string}`, calculatedSlippage = calculateSlippage(lpTokenPrice, result[0], maxAmountIn, result[2]);
abi: IPartyInfoABI, console.log('swapMint calculatedSlippage', calculatedSlippage);
functionName: 'poolPrice', } catch (slippageErr) {
args: [poolAddress, BigInt(inputTokenIndex)], console.error(`Error calculating slippage for swapMint:`, slippageErr);
}) as bigint; }
const basePrice = Number(poolPriceInt128) / (2 ** 64);
poolPrice = 1 / (basePrice * Math.pow(10, 18 - inputTokenDecimals));
// Calculate slippage
const decimalsMultiplier = Math.pow(10, inputTokenDecimals);
const lpMinted = Number(result[1]) / Math.pow(10, 18);
const amountIn = Number(result[0]) / decimalsMultiplier;
const fee = Number(result[2]) / decimalsMultiplier;
const swapPrice = lpMinted / (amountIn - fee);
calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100;
} catch (priceErr) {
console.error('Error fetching poolPrice or calculating slippage:', priceErr);
} }
setSwapMintAmounts({ setSwapMintAmounts({
amountInUsed: result[0], amountInUsed: result[0],
fee: result[2], fee: result[1],
lpMinted: result[1], lpMinted: result[2],
calculatedSlippage, calculatedSlippage,
}); });
} catch (err) { } catch (err) {
console.error('Error calling swapMintAmounts:', err); console.error('Error calling swapMintAmounts:', err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch swap mint amounts'; setError(err instanceof Error ? err.message : 'Failed to fetch swap mint amounts');
setError(errorMessage);
setSwapMintAmounts(null); setSwapMintAmounts(null);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -725,7 +657,7 @@ export function useSwapMintAmounts(
}; };
fetchSwapMintAmounts(); fetchSwapMintAmounts();
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, inputTokenDecimals]); }, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, lpTokenPrice]);
return { return {
swapMintAmounts, swapMintAmounts,
@@ -739,7 +671,8 @@ export function useBurnSwapAmounts(
poolAddress: `0x${string}` | undefined, poolAddress: `0x${string}` | undefined,
lpAmount: bigint | undefined, lpAmount: bigint | undefined,
inputTokenIndex: number | undefined, inputTokenIndex: number | undefined,
tokenDecimals: number | undefined // Decimals of the output token lpTokenPrice?: number, // Market price of the LP token in decimal format
tokenDecimals?: number // Decimals of the output token
) { ) {
const publicClient = usePublicClient(); const publicClient = usePublicClient();
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@@ -768,17 +701,31 @@ export function useBurnSwapAmounts(
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
console.log('fetching swap amounts')
// Get chain ID and contract address // Get chain ID and contract address
const chainId = await publicClient.getChainId(); const chainId = await publicClient.getChainId();
// @ts-ignore // @ts-ignore
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo; const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
if (!partyInfoAddress) { if (!partyInfoAddress) {
console.log('errores here')
setError('PartyInfo contract not found for current chain'); setError('PartyInfo contract not found for current chain');
setBurnSwapAmounts(null); setBurnSwapAmounts(null);
return; return;
} }
console.log('fetching swap amounts 2')
// Log inputs
console.log('🔍 burnSwapAmounts INPUTS:', {
chainId: chainId.toString(),
rpcUrl: publicClient.transport?.url || 'Unknown',
poolAddress,
lpAmount: lpAmount.toString(),
inputTokenIndex,
partyInfoAddress,
});
// Call burnSwapAmounts function - returns [amountOut, outFee] // Call burnSwapAmounts function - returns [amountOut, outFee]
const result = await publicClient.readContract({ const result = await publicClient.readContract({
address: partyInfoAddress as `0x${string}`, address: partyInfoAddress as `0x${string}`,
@@ -787,6 +734,12 @@ export function useBurnSwapAmounts(
args: [poolAddress, lpAmount, BigInt(inputTokenIndex)], args: [poolAddress, lpAmount, BigInt(inputTokenIndex)],
}) as readonly [bigint, bigint]; }) as readonly [bigint, bigint];
// Log raw result
console.log('📊 burnSwapAmounts RAW RESULT:', {
resultArray: result,
amountOut: result[0].toString(),
outFee: result[1].toString(),
});
// Calculate slippage for burnSwap using poolPrice // Calculate slippage for burnSwap using poolPrice
let calculatedSlippage: number | undefined; let calculatedSlippage: number | undefined;
if (tokenDecimals !== undefined) { if (tokenDecimals !== undefined) {
@@ -800,8 +753,7 @@ export function useBurnSwapAmounts(
}) as bigint; }) as bigint;
// Convert Q64 format to decimal (price = priceValue / 2^64) // Convert Q64 format to decimal (price = priceValue / 2^64)
let poolPrice = Number(poolPriceInt128) / 2 ** 64; const marketPrice = Number(poolPriceInt128) / 2 ** 64;
poolPrice = (poolPrice * Math.pow(10, 18 - tokenDecimals));
// For burnSwap: swapPrice = (outAmount + fee) / lpAmount // For burnSwap: swapPrice = (outAmount + fee) / lpAmount
// Need to apply decimal corrections: outAmount and fee are in token decimals, lpAmount is in 18 decimals // Need to apply decimal corrections: outAmount and fee are in token decimals, lpAmount is in 18 decimals
@@ -810,8 +762,21 @@ export function useBurnSwapAmounts(
const lpAmountDecimal = Number(lpAmount) / Math.pow(10, 18); // LP tokens have 18 decimals const lpAmountDecimal = Number(lpAmount) / Math.pow(10, 18); // LP tokens have 18 decimals
const swapPrice = (outAmountDecimal + feeDecimal) / lpAmountDecimal; const swapPrice = (outAmountDecimal + feeDecimal) / lpAmountDecimal;
calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100;
// Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100
calculatedSlippage = ((swapPrice - marketPrice) / marketPrice) * 100;
console.log('burnSwap slippage calculation:', {
marketPrice,
swapPrice,
calculatedSlippage,
outAmountDecimal,
feeDecimal,
lpAmountDecimal,
outAmount: result[0].toString(),
fee: result[1].toString(),
lpAmount: lpAmount.toString(),
});
} catch (slippageErr) { } catch (slippageErr) {
console.error(`Error calculating slippage for burnSwap:`, slippageErr); console.error(`Error calculating slippage for burnSwap:`, slippageErr);
} }
@@ -823,11 +788,17 @@ export function useBurnSwapAmounts(
calculatedSlippage, calculatedSlippage,
}; };
// Log parsed result
console.log('✅ burnSwapAmounts PARSED:', {
amountOut: parsedAmounts.amountOut.toString(),
outFee: parsedAmounts.outFee.toString(),
calculatedSlippage: parsedAmounts.calculatedSlippage,
});
setBurnSwapAmounts(parsedAmounts); setBurnSwapAmounts(parsedAmounts);
} catch (err) { } catch (err) {
console.error('Error calling burnSwapAmounts:', err); console.error('Error calling burnSwapAmounts:', err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch burn swap amounts'; setError(err instanceof Error ? err.message : 'Failed to fetch burn swap amounts');
setError(errorMessage);
setBurnSwapAmounts(null); setBurnSwapAmounts(null);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -835,7 +806,7 @@ export function useBurnSwapAmounts(
}; };
fetchBurnSwapAmounts(); fetchBurnSwapAmounts();
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, tokenDecimals]); }, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, lpTokenPrice, tokenDecimals]);
return { return {
burnSwapAmounts, burnSwapAmounts,

View File

@@ -21,14 +21,15 @@ const Q96 = 1n << 96n;
*/ */
export function calculateSlippage( export function calculateSlippage(
marketPrice: number, marketPrice: number,
swapOutputAmount: number, swapOutputAmount: bigint,
swapInputAmount: number, swapInputAmount: bigint,
swapFee: number swapFee: bigint
): number { ): number {
// Calculate actual swap price with decimal correction // Calculate actual swap price with decimal correction
const swapPrice = swapOutputAmount / ((swapInputAmount) - (swapFee)); const swapPrice = Number(swapOutputAmount) / (Number(swapInputAmount) - Number(swapFee));
// Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100 // Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100
const slippage = ((marketPrice - swapPrice) / marketPrice) * 100; const slippage = ((swapPrice - marketPrice) / marketPrice) * 100;
return slippage; return slippage;
} }
@@ -120,6 +121,18 @@ export function useSwapAmounts(
setLoading(true); setLoading(true);
const amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals); const amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals);
// Calculate limit price based on slippage tolerance
// limitPrice in Q96 format = Q96 * (100 + slippage%) / 100
// This represents the maximum acceptable price ratio (1 + slippage%)
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100)); // Convert to basis points (0.5% = 50)
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
console.log('Limit Price Calculation:', {
slippagePercent,
slippageBasisPoints: slippageBasisPoints.toString(),
limitPriceQ96: limitPrice.toString(),
Q96: Q96.toString(),
});
const results: SwapAmountResult[] = []; const results: SwapAmountResult[] = [];
const chainId = await publicClient.getChainId(); const chainId = await publicClient.getChainId();
@@ -134,8 +147,8 @@ export function useSwapAmounts(
// Evaluate ALL routes for this token // Evaluate ALL routes for this token
for (const route of token.swapRoutes) { for (const route of token.swapRoutes) {
try { try {
// Get swap amounts with NO LIMIT // Get swap amounts
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({ const swapResult = await publicClient.readContract({
address: route.poolAddress, address: route.poolAddress,
abi: IPartyPoolABI, abi: IPartyPoolABI,
functionName: 'swapAmounts', functionName: 'swapAmounts',
@@ -143,10 +156,12 @@ export function useSwapAmounts(
BigInt(route.inputTokenIndex), BigInt(route.inputTokenIndex),
BigInt(route.outputTokenIndex), BigInt(route.outputTokenIndex),
amountInTokenUnits, amountInTokenUnits,
0n, // NO LIMIT limitPrice,
], ],
}) as readonly [bigint, bigint, bigint]; }) as readonly [bigint, bigint, bigint];
const [amountIn, amountOut, fee] = swapResult;
// Get kappa for this pool // Get kappa for this pool
const kappa = await publicClient.readContract({ const kappa = await publicClient.readContract({
address: route.poolAddress, address: route.poolAddress,
@@ -158,6 +173,19 @@ export function useSwapAmounts(
let calculatedSlippage: number | undefined; let calculatedSlippage: number | undefined;
if (partyInfoAddress) { if (partyInfoAddress) {
try { try {
// Get swap amounts with NO LIMIT (0 means no price limit)
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({
address: route.poolAddress,
abi: IPartyPoolABI,
functionName: 'swapAmounts',
args: [
BigInt(route.inputTokenIndex),
BigInt(route.outputTokenIndex),
amountInTokenUnits,
0n, // NO LIMIT
],
}) as readonly [bigint, bigint, bigint];
// Get the current market price from PoolInfo // Get the current market price from PoolInfo
const priceInt128 = await publicClient.readContract({ const priceInt128 = await publicClient.readContract({
address: partyInfoAddress, address: partyInfoAddress,
@@ -166,17 +194,11 @@ export function useSwapAmounts(
args: [route.poolAddress, BigInt(route.inputTokenIndex), BigInt(route.outputTokenIndex)], args: [route.poolAddress, BigInt(route.inputTokenIndex), BigInt(route.outputTokenIndex)],
}) as bigint; }) as bigint;
// Convert Q128 format to decimal (price = priceValue / 2^128) // Convert Q64 format to decimal (price = priceValue / 2^64)
// Then apply decimal conversion: 10^18 / 10^outputTokenDecimals const marketPrice = Number(priceInt128) / 2 ** 64;
const priceQ128 = Number(priceInt128) / 2 ** 128;
const decimalAdjustment = 10 ** (route.outputTokenDecimal - route.inputTokenDecimal);
const marketPrice = priceQ128 / decimalAdjustment;
const swapOutputAmountDecimal = Number(swapOutputAmount) / 10 ** route.outputTokenDecimal;
const swapInputAmountDecimal = Number(swapInputAmount) / 10 ** route.inputTokenDecimal;
const swapFeeDecimal = Number(inFee) / 10 ** route.inputTokenDecimal;
// Calculate slippage using the reusable function
calculatedSlippage = calculateSlippage(marketPrice, swapOutputAmountDecimal, swapInputAmountDecimal, swapFeeDecimal); // Calculate slippage using the reusable function
calculatedSlippage = calculateSlippage(marketPrice, swapOutputAmount, swapInputAmount, inFee);
console.log('calculatedSlippage', calculatedSlippage) console.log('calculatedSlippage', calculatedSlippage)
} catch (slippageErr) { } catch (slippageErr) {
console.error(`Error calculating slippage for ${token.symbol}:`, slippageErr); console.error(`Error calculating slippage for ${token.symbol}:`, slippageErr);
@@ -186,9 +208,9 @@ export function useSwapAmounts(
routeResults.push({ routeResults.push({
tokenAddress: token.address, tokenAddress: token.address,
tokenSymbol: token.symbol, tokenSymbol: token.symbol,
amountIn: swapInputAmount, amountIn,
amountOut: swapOutputAmount, amountOut,
fee: inFee, fee,
poolAddress: route.poolAddress, poolAddress: route.poolAddress,
kappa, kappa,
inputTokenIndex: route.inputTokenIndex, inputTokenIndex: route.inputTokenIndex,
@@ -360,7 +382,9 @@ export function useSwap() {
await publicClient.waitForTransactionReceipt({ hash: approvalHash }); await publicClient.waitForTransactionReceipt({ hash: approvalHash });
console.log('✅ Approval confirmed'); console.log('✅ Approval confirmed');
// STEP 2: Calculate deadline // STEP 2: Calculate limit price and deadline
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100));
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200); const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200);
console.log('🚀 Executing swap with params:', { console.log('🚀 Executing swap with params:', {
@@ -370,12 +394,12 @@ export function useSwap() {
inputTokenIndex, inputTokenIndex,
outputTokenIndex, outputTokenIndex,
maxAmountIn: maxAmountIn.toString(), maxAmountIn: maxAmountIn.toString(),
limitPrice: '0 (no limit)', limitPrice: limitPrice.toString(),
deadline: deadline.toString(), deadline: deadline.toString(),
unwrap: false, unwrap: false,
}); });
// STEP 3: Execute the swap transaction with no limit price // STEP 3: Execute the swap transaction
const hash = await walletClient.writeContract({ const hash = await walletClient.writeContract({
address: poolAddress, address: poolAddress,
abi: IPartyPoolABI, abi: IPartyPoolABI,
@@ -387,7 +411,7 @@ export function useSwap() {
BigInt(inputTokenIndex), BigInt(inputTokenIndex),
BigInt(outputTokenIndex), BigInt(outputTokenIndex),
maxAmountIn, maxAmountIn,
0n, // no limit price limitPrice,
deadline, deadline,
false, // unwrap false, // unwrap
'0x', // cbData (empty bytes) '0x', // cbData (empty bytes)