Compare commits
52 Commits
4ecda7de14
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d207ef6dca | |||
| 0e5921255e | |||
| 1ac26aeec0 | |||
| a2a036818d | |||
|
|
1c2e267136 | ||
|
|
b2abf2073e | ||
| 9835d67e54 | |||
| b0b050f4be | |||
| f43db3391b | |||
| 334e9f4f53 | |||
|
|
9d2ee39d1a | ||
| d4e41821a6 | |||
| d07ff55c13 | |||
| 4523195b78 | |||
| 068286919a | |||
| 7b8114267e | |||
| f3277b45ab | |||
| fe02d94d11 | |||
| ff9a718522 | |||
| e36f6011c9 | |||
| 99929d8db8 | |||
| 85a7b58d55 | |||
|
|
882e271040 | ||
|
|
a3053d1f7b | ||
| c8c23a4f54 | |||
| 8cc9d00521 | |||
|
|
519a22847b | ||
| aeb90f2e0f | |||
| d47f3d566a | |||
| 4214f94ab8 | |||
| ea44e0d941 | |||
|
|
5b450ab303 | ||
|
|
2585873d8a | ||
| 107d2ae5c0 | |||
| 66d854fb75 | |||
| d209742127 | |||
|
|
ce4e2eaef0 | ||
|
|
5fe5563871 | ||
| bd430411bd | |||
|
|
d5c40b9667 | ||
| c905217004 | |||
| 1cb6c4852f | |||
| 1fa6d686f4 | |||
| 2fbebed014 | |||
| fea441b4e7 | |||
| 732dfd7780 | |||
| c69f0a47de | |||
| dab02db690 | |||
| e5bcc632e5 | |||
| 0586a09161 | |||
| 6e5eca7543 | |||
| 9795d03493 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,6 +5,9 @@
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
*secret*
|
||||
.env
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
|
||||
4
EthereumTransactionCall.sh
Normal file
4
EthereumTransactionCall.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
cast call 0x8f98B899F4135408Fe03228cE942Ad6BF8E40f22 \
|
||||
"working(address)" \
|
||||
0x3EDE1eE859A72aEc85ff04d305B6Ffe89f2Cb4eb \
|
||||
--rpc-url https://eth-sepolia.g.alchemy.com/v2/demo
|
||||
BIN
LP Test Plan.docx
Normal file
BIN
LP Test Plan.docx
Normal file
Binary file not shown.
@@ -7,15 +7,9 @@ else
|
||||
DEV=0
|
||||
fi
|
||||
|
||||
CHAIN_ID=${1:-11155111} # Defaults to Sepolia
|
||||
PROJECT=liquidity-party
|
||||
REMOTE=git.dxod.org/dexorder/dexorder
|
||||
|
||||
if [ "$BUILD" != "1" ] && [ "$CHAIN_ID" = "31337" ]; then
|
||||
echo bin/deploy must specify a production chain ID
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$BUILD" != "1" ]; then
|
||||
if [ "$DEV" = "1" ]; then
|
||||
TAG="dev$(date +%Y%m%d%H%M%S)"
|
||||
@@ -32,12 +26,11 @@ else
|
||||
echo Building
|
||||
fi
|
||||
|
||||
bin/generate-contracts "$CHAIN_ID" || exit 1
|
||||
npm run build || exit 1
|
||||
|
||||
if [ "$BUILD" != "1" ]; then
|
||||
echo Deploying with tag $TAG
|
||||
docker build --no-cache -f deploy/Dockerfile -t dexorder/$PROJECT:latest . || exit 1
|
||||
docker buildx build --platform linux/amd64 --no-cache -f deploy/Dockerfile -t dexorder/$PROJECT:latest . || exit 1
|
||||
docker tag dexorder/$PROJECT:latest dexorder/$PROJECT:$TAG
|
||||
docker tag dexorder/$PROJECT:$TAG $REMOTE/$PROJECT:$TAG
|
||||
docker tag $REMOTE/$PROJECT:$TAG $REMOTE/$PROJECT:latest
|
||||
|
||||
@@ -6,6 +6,9 @@ case "$CHAIN_ID" in
|
||||
"sepolia")
|
||||
CHAIN_ID=11155111
|
||||
;;
|
||||
"mockchain")
|
||||
CHAIN_ID=31337
|
||||
;;
|
||||
"mainnet")
|
||||
CHAIN_ID=1
|
||||
;;
|
||||
@@ -34,7 +37,7 @@ generate() {
|
||||
|
||||
generate IPartyPlanner
|
||||
generate IPartyPool
|
||||
generate IPartyPoolViewer
|
||||
generate IPartyInfo
|
||||
|
||||
cp "$METADATA_PATH" src/contracts/
|
||||
echo liqp-deployments.json
|
||||
echo src/contracts/liqp-deployments.json
|
||||
|
||||
80
package-lock.json
generated
80
package-lock.json
generated
@@ -17,7 +17,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^23.15.0",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "^15.1.3",
|
||||
"next": "15.5.7",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
@@ -1405,15 +1405,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz",
|
||||
"integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
|
||||
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz",
|
||||
"integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
||||
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1427,9 +1427,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz",
|
||||
"integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
||||
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1443,9 +1443,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz",
|
||||
"integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
||||
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1459,9 +1459,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz",
|
||||
"integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1475,9 +1475,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz",
|
||||
"integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz",
|
||||
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1491,9 +1491,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz",
|
||||
"integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1507,9 +1507,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz",
|
||||
"integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1523,9 +1523,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz",
|
||||
"integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -6179,12 +6179,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz",
|
||||
"integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
|
||||
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.4",
|
||||
"@next/env": "15.5.7",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
@@ -6197,14 +6197,14 @@
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.5.4",
|
||||
"@next/swc-darwin-x64": "15.5.4",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.4",
|
||||
"@next/swc-linux-arm64-musl": "15.5.4",
|
||||
"@next/swc-linux-x64-gnu": "15.5.4",
|
||||
"@next/swc-linux-x64-musl": "15.5.4",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.4",
|
||||
"@next/swc-win32-x64-msvc": "15.5.4",
|
||||
"@next/swc-darwin-arm64": "15.5.7",
|
||||
"@next/swc-darwin-x64": "15.5.7",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.7",
|
||||
"@next/swc-linux-arm64-musl": "15.5.7",
|
||||
"@next/swc-linux-x64-gnu": "15.5.7",
|
||||
"@next/swc-linux-x64-musl": "15.5.7",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.7",
|
||||
"@next/swc-win32-x64-msvc": "15.5.7",
|
||||
"sharp": "^0.34.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^23.15.0",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "^15.1.3",
|
||||
"next": "15.5.7",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
version="1.1"
|
||||
viewBox="0 0 86.435997 86.435997"
|
||||
id="svg585"
|
||||
sodipodi:docname="logo-flower.svg"
|
||||
sodipodi:docname="logo-splash.svg"
|
||||
width="86.435997"
|
||||
height="86.435997"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
@@ -27,24 +27,17 @@
|
||||
inkscape:cx="218.22034"
|
||||
inkscape:cy="48.516949"
|
||||
inkscape:window-width="1864"
|
||||
inkscape:window-height="1131"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg585" />
|
||||
<!-- Flower -->
|
||||
<g
|
||||
fill="#000000"
|
||||
id="g583"
|
||||
transform="translate(-1.782,-1.782)">
|
||||
<g
|
||||
id="g581">
|
||||
<circle
|
||||
fill="#000000"
|
||||
cx="45"
|
||||
cy="44.999001"
|
||||
r="5.277"
|
||||
id="circle545" />
|
||||
<g
|
||||
id="g579">
|
||||
<g
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -1,46 +1,163 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Create Pool from Real-Time Prices Script
|
||||
* Fetches real-time prices from CoinGecko and creates a new pool on Anvil
|
||||
* Fetches real-time prices from CoinGecko and creates a new pool on the chain specified (could be mockchain, sepolia or prod)
|
||||
*/
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load environment variables from .env-secret
|
||||
config({ path: new URL('../.env-secret', import.meta.url).pathname });
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
// Network flag: 'mockchain' or 'mainnet'
|
||||
const NETWORK = process.env.NETWORK || 'mainnet';
|
||||
|
||||
// Network-specific configuration
|
||||
const NETWORK_CONFIG = {
|
||||
mockchain: {
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
privateKey: '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a', // Dev wallet #4 (sender)
|
||||
receiverPrivateKey: '0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356', // Receiver wallet
|
||||
receiverAddress: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // Receiver public key
|
||||
chainId: '31337',
|
||||
tokens: {
|
||||
USDT: {
|
||||
address: '0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6', // USXD on mockchain
|
||||
coingeckoId: 'tether',
|
||||
decimals: 6,
|
||||
feePpm: 400
|
||||
},
|
||||
USDC: {
|
||||
address: '0x2910E325cf29dd912E3476B61ef12F49cb931096', // FUSD on mockchain
|
||||
coingeckoId: 'usd-coin',
|
||||
decimals: 6,
|
||||
feePpm: 400
|
||||
}
|
||||
}
|
||||
},
|
||||
mainnet: {
|
||||
rpcUrl: process.env.MAINNET_RPC_URL,
|
||||
privateKey: process.env.PRIVATE_KEY,
|
||||
receiverAddress: '0xd3b310bd32d782f89eea49cb79656bcaccde7213', // Same as payer for mainnet
|
||||
chainId: '1',
|
||||
tokens: {
|
||||
USDT: {
|
||||
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
coingeckoId: 'tether',
|
||||
decimals: 6,
|
||||
feePpm: 40 // 0.0004%
|
||||
},
|
||||
USDC: {
|
||||
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
coingeckoId: 'usd-coin',
|
||||
decimals: 6,
|
||||
feePpm: 40 // 0.0004%
|
||||
},
|
||||
WBTC: {
|
||||
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
||||
coingeckoId: 'wrapped-bitcoin',
|
||||
decimals: 8,
|
||||
feePpm: 300 // 0.00030%
|
||||
},
|
||||
WETH: {
|
||||
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
coingeckoId: 'weth',
|
||||
decimals: 18,
|
||||
feePpm: 350 // 0.0035%
|
||||
},
|
||||
UNI: {
|
||||
address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
|
||||
coingeckoId: 'uniswap',
|
||||
decimals: 18,
|
||||
feePpm: 1450 // 0.00145%
|
||||
},
|
||||
WSOL: {
|
||||
address: '0xD31a59c85aE9D8edEFeC411D448f90841571b89c', // Wormhole Wrapped SOL
|
||||
coingeckoId: 'solana',
|
||||
decimals: 9,
|
||||
feePpm: 950 // 0.00095%
|
||||
},
|
||||
TRX: {
|
||||
address: '0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5',
|
||||
coingeckoId: 'tron',
|
||||
decimals: 6,
|
||||
feePpm: 950 // 0.00095%
|
||||
},
|
||||
AAVE: {
|
||||
address: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9',
|
||||
coingeckoId: 'aave',
|
||||
decimals: 18,
|
||||
feePpm: 1450 // 0.00145%
|
||||
},
|
||||
PEPE: {
|
||||
address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
|
||||
coingeckoId: 'pepe',
|
||||
decimals: 18,
|
||||
feePpm: 2150 // 0.00215%
|
||||
},
|
||||
SHIB: {
|
||||
address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE',
|
||||
coingeckoId: 'shiba-inu',
|
||||
decimals: 18,
|
||||
feePpm: 2150 // 0.00215%
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Get current network config
|
||||
const currentConfig = NETWORK_CONFIG[NETWORK];
|
||||
if (!currentConfig) {
|
||||
console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const RPC_URL = currentConfig.rpcUrl;
|
||||
const PRIVATE_KEY = currentConfig.privateKey;
|
||||
const RECEIVER_ADDRESS = currentConfig.receiverAddress || process.env.RECEIVER_ADDRESS;
|
||||
|
||||
// Validate required config for mainnet
|
||||
if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) {
|
||||
console.error('[!] Missing required environment variables for mainnet');
|
||||
console.error(' Required: MAINNET_RPC_URL, PRIVATE_KEY');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!RECEIVER_ADDRESS) {
|
||||
console.error('[!] Missing RECEIVER_ADDRESS in .env-secret file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Use network-specific tokens
|
||||
const TEST_TOKENS = currentConfig.tokens;
|
||||
|
||||
// Default pool parameters
|
||||
const DEFAULT_POOL_PARAMS = {
|
||||
name: 'Original Genesis of Liquidity Party',
|
||||
symbol: 'OG.LP',
|
||||
kappa: ethers.BigNumber.from('184467440737095520'), //0.01 * 2^64
|
||||
swapFeesPpm: Object.values(TEST_TOKENS).map(t => t.feePpm),
|
||||
flashFeePpm: 5, // 0.0005%
|
||||
stable: false,
|
||||
initialLpAmount: ethers.utils.parseUnits('1', 18) // 100 USD in 18 decimals
|
||||
};
|
||||
|
||||
// Input amount in USD
|
||||
const INPUT_USD_AMOUNT = 1;
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// LOAD ABIs AND CONFIG
|
||||
// ============================================================================
|
||||
|
||||
// Load chain info and get PartyPlanner address (chain ID 31337 = Anvil)
|
||||
const chainInfoData = JSON.parse(await readFile(new URL('../src/contracts/liqp-deployments.json', import.meta.url), 'utf-8'));
|
||||
const PARTY_PLANNER_ADDRESS = chainInfoData['31337'].v1.PartyPlanner;
|
||||
|
||||
// Minimal ABIs needed for this script
|
||||
const IPartyPlannerABI = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "newPool",
|
||||
"inputs": [
|
||||
{ "name": "name", "type": "string", "internalType": "string" },
|
||||
{ "name": "symbol", "type": "string", "internalType": "string" },
|
||||
{ "name": "tokens", "type": "address[]", "internalType": "contract IERC20[]" },
|
||||
{ "name": "kappa", "type": "int128", "internalType": "int128" },
|
||||
{ "name": "swapFeePpm", "type": "uint256", "internalType": "uint256" },
|
||||
{ "name": "flashFeePpm", "type": "uint256", "internalType": "uint256" },
|
||||
{ "name": "stable", "type": "bool", "internalType": "bool" },
|
||||
{ "name": "payer", "type": "address", "internalType": "address" },
|
||||
{ "name": "receiver", "type": "address", "internalType": "address" },
|
||||
{ "name": "initialDeposits", "type": "uint256[]", "internalType": "uint256[]" },
|
||||
{ "name": "initialLpAmount", "type": "uint256", "internalType": "uint256" },
|
||||
{ "name": "deadline", "type": "uint256", "internalType": "uint256" }
|
||||
],
|
||||
"outputs": [
|
||||
{ "name": "pool", "type": "address", "internalType": "contract IPartyPool" },
|
||||
{ "name": "lpAmount", "type": "uint256", "internalType": "uint256" }
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
}
|
||||
];
|
||||
const PARTY_PLANNER_ADDRESS = chainInfoData[currentConfig.chainId].v1.PartyPlanner;
|
||||
|
||||
const ERC20ABI = [
|
||||
{ "type": "function", "name": "balanceOf", "stateMutability": "view", "inputs": [{ "name": "account", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] },
|
||||
@@ -48,50 +165,6 @@ const ERC20ABI = [
|
||||
{ "type": "function", "name": "approve", "stateMutability": "nonpayable", "inputs": [{ "name": "spender", "type": "address" }, { "name": "amount", "type": "uint256" }], "outputs": [{ "name": "", "type": "bool" }] }
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
const ANVIL_RPC_URL = 'http://127.0.0.1:8545';
|
||||
|
||||
// Hardcoded private key for Anvil testing (default Anvil account #4 - payer)
|
||||
const PRIVATE_KEY = '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a';
|
||||
|
||||
// Receiver address (Anvil account #7)
|
||||
const RECEIVER_ADDRESS = '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955';
|
||||
|
||||
// Test token addresses (mapping to real coins)
|
||||
const TEST_TOKENS = {
|
||||
USDC: {
|
||||
address: '0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6', // USXD (mock) = USDC (real)
|
||||
coingeckoId: 'usd-coin',
|
||||
decimals: 6
|
||||
},
|
||||
BTC: {
|
||||
address: '0x93C7a6D00849c44Ef3E92E95DCEFfccd447909Ae', // BUTC (mock) = BTC (real)
|
||||
coingeckoId: 'bitcoin',
|
||||
decimals: 8
|
||||
},
|
||||
WETH: {
|
||||
address: '0x71a9d115E322467147391c4a71D85F8e1cA623EF', // WTETH (mock) = WETH (real)
|
||||
coingeckoId: 'weth',
|
||||
decimals: 18
|
||||
}
|
||||
};
|
||||
|
||||
// Default pool parameters
|
||||
const DEFAULT_POOL_PARAMS = {
|
||||
name: 'Balanced Portfolio Pool',
|
||||
symbol: 'BPP',
|
||||
kappa: ethers.BigNumber.from('100000000000000000'), // 0.1 * 1e18 = 1e17
|
||||
swapFeePpm: 3000, // 0.3%
|
||||
flashFeePpm: 5, // 0.0005%
|
||||
stable: false,
|
||||
initialLpAmount: ethers.BigNumber.from('1000000000000000000') // 1e18
|
||||
};
|
||||
|
||||
// Input amount in USD
|
||||
const INPUT_USD_AMOUNT = 100;
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
@@ -114,20 +187,19 @@ async function fetchCoinGeckoPrices() {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const prices = {
|
||||
USDC: data['usd-coin']?.usd || 1,
|
||||
BTC: data['bitcoin']?.usd || 0,
|
||||
WETH: data['weth']?.usd || 0
|
||||
};
|
||||
const prices = {};
|
||||
|
||||
if (prices.BTC === 0 || prices.WETH === 0) {
|
||||
throw new Error('Failed to fetch valid prices from CoinGecko');
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
prices[symbol] = data[tokenInfo.coingeckoId]?.usd || 0;
|
||||
if (prices[symbol] === 0) {
|
||||
throw new Error(`Failed to fetch valid price for ${symbol}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[+] Prices fetched successfully:`);
|
||||
console.log(` USDC: $${prices.USDC}`);
|
||||
console.log(` BTC: $${prices.BTC.toLocaleString()}`);
|
||||
console.log(` WETH: $${prices.WETH.toLocaleString()}`);
|
||||
for (const [symbol, price] of Object.entries(prices)) {
|
||||
console.log(` ${symbol.padEnd(6)}: $${price.toLocaleString()}`);
|
||||
}
|
||||
|
||||
return prices;
|
||||
} catch (error) {
|
||||
@@ -140,42 +212,39 @@ async function fetchCoinGeckoPrices() {
|
||||
* Calculate token amounts based on equal USD distribution
|
||||
*/
|
||||
function calculateTokenAmounts(prices, usdAmount) {
|
||||
const usdPerToken = usdAmount / 3; // Equally distribute among 3 tokens
|
||||
const tokenCount = Object.keys(TEST_TOKENS).length;
|
||||
const usdPerToken = usdAmount / tokenCount; // Equally distribute among all tokens
|
||||
|
||||
// Calculate raw amounts
|
||||
const usdcAmount = usdPerToken / prices.USDC;
|
||||
const btcAmount = usdPerToken / prices.BTC;
|
||||
const wethAmount = usdPerToken / prices.WETH;
|
||||
|
||||
// Convert to BigNumber with proper decimals
|
||||
const usdcAmountBN = ethers.utils.parseUnits(usdcAmount.toFixed(TEST_TOKENS.USDC.decimals), TEST_TOKENS.USDC.decimals);
|
||||
const btcAmountBN = ethers.utils.parseUnits(btcAmount.toFixed(TEST_TOKENS.BTC.decimals), TEST_TOKENS.BTC.decimals);
|
||||
const wethAmountBN = ethers.utils.parseUnits(wethAmount.toFixed(TEST_TOKENS.WETH.decimals), TEST_TOKENS.WETH.decimals);
|
||||
const tokenAmounts = {};
|
||||
|
||||
console.log(`\n[~] Calculated token amounts for $${usdAmount} ($${usdPerToken.toFixed(2)} per token):`);
|
||||
console.log(` USDC: ${usdcAmount.toFixed(6)} USDC (${usdcAmountBN.toString()} wei)`);
|
||||
console.log(` BTC: ${btcAmount.toFixed(8)} BTC (${btcAmountBN.toString()} wei)`);
|
||||
console.log(` WETH: ${wethAmount.toFixed(8)} WETH (${wethAmountBN.toString()} wei)`);
|
||||
|
||||
return {
|
||||
USDC: usdcAmountBN,
|
||||
BTC: btcAmountBN,
|
||||
WETH: wethAmountBN
|
||||
};
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
// Calculate raw amount
|
||||
const rawAmount = usdPerToken / prices[symbol];
|
||||
|
||||
// Convert to BigNumber with proper decimals
|
||||
const amountBN = ethers.utils.parseUnits(rawAmount.toFixed(tokenInfo.decimals), tokenInfo.decimals);
|
||||
|
||||
tokenAmounts[symbol] = amountBN;
|
||||
console.log(` ${symbol.padEnd(6)}: ${rawAmount.toFixed(tokenInfo.decimals)} (${amountBN.toString()} wei)`);
|
||||
}
|
||||
|
||||
return tokenAmounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check token balances
|
||||
*/
|
||||
async function checkBalances(provider, wallet, tokenAmounts) {
|
||||
console.log(`\n[~] Checking token balances for wallet: ${wallet.address}`);
|
||||
async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
|
||||
console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`);
|
||||
|
||||
const balances = {};
|
||||
let hasEnoughBalance = true;
|
||||
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, provider);
|
||||
const balance = await tokenContract.balanceOf(wallet.address);
|
||||
const balance = await tokenContract.balanceOf(receiverAddress);
|
||||
const requiredAmount = tokenAmounts[symbol];
|
||||
|
||||
balances[symbol] = balance;
|
||||
@@ -192,7 +261,7 @@ async function checkBalances(provider, wallet, tokenAmounts) {
|
||||
}
|
||||
|
||||
if (!hasEnoughBalance) {
|
||||
console.log(`\n[!] Insufficient token balance. Please ensure your wallet has enough tokens.`);
|
||||
console.log(`\n[!] Insufficient token balance. Please ensure receiver wallet has enough tokens.`);
|
||||
throw new Error('Insufficient token balance');
|
||||
}
|
||||
|
||||
@@ -203,19 +272,31 @@ async function checkBalances(provider, wallet, tokenAmounts) {
|
||||
/**
|
||||
* Approve tokens for the PartyPlanner contract
|
||||
*/
|
||||
async function approveTokens(wallet, tokenAmounts) {
|
||||
async function approveTokens(provider, tokenAmounts, receiverPrivateKey) {
|
||||
console.log(`\n[~] Approving tokens for PartyPlanner contract...`);
|
||||
|
||||
// Connect with receiver wallet for approvals
|
||||
const receiverWallet = new ethers.Wallet(receiverPrivateKey, provider);
|
||||
console.log(`[~] Using receiver wallet for approvals: ${receiverWallet.address}`);
|
||||
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, wallet);
|
||||
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, receiverWallet);
|
||||
|
||||
// Approve 1% more than needed to account for fees/slippage
|
||||
const requiredAmount = tokenAmounts[symbol];
|
||||
const approvalAmount = requiredAmount.mul(101).div(100); // 1% buffer
|
||||
|
||||
console.log(` [~] Approving ${symbol} (1% buffer)...`);
|
||||
console.log(` [~] Approving ${symbol} ${tokenInfo.address} ${approvalAmount} (1% buffer)...`);
|
||||
|
||||
try {
|
||||
// USDT and some tokens require setting allowance to 0 before setting a new value
|
||||
// Skip for BNB as it has a broken approve function
|
||||
if (symbol == 'USDT') {
|
||||
const resetTx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, 0);
|
||||
await resetTx.wait();
|
||||
console.log(` [+] ${symbol} allowance reset to 0`);
|
||||
}
|
||||
|
||||
const tx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, approvalAmount);
|
||||
await tx.wait();
|
||||
console.log(` [+] ${symbol} approved (tx: ${tx.hash})`);
|
||||
@@ -229,80 +310,62 @@ async function approveTokens(wallet, tokenAmounts) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pool
|
||||
* Create a new pool using cast send
|
||||
*/
|
||||
async function createPool(wallet, tokenAmounts) {
|
||||
console.log(`\n[~] Creating new pool...`);
|
||||
|
||||
const partyPlanner = new ethers.Contract(PARTY_PLANNER_ADDRESS, IPartyPlannerABI, wallet);
|
||||
|
||||
// Prepare parameters
|
||||
const tokenAddresses = [
|
||||
TEST_TOKENS.USDC.address,
|
||||
TEST_TOKENS.BTC.address,
|
||||
TEST_TOKENS.WETH.address
|
||||
];
|
||||
const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address);
|
||||
const initialDeposits = Object.keys(TEST_TOKENS).map(symbol => tokenAmounts[symbol].toString());
|
||||
|
||||
const initialDeposits = [
|
||||
tokenAmounts.USDC,
|
||||
tokenAmounts.BTC,
|
||||
tokenAmounts.WETH
|
||||
];
|
||||
|
||||
// Set deadline to 1 hour from now
|
||||
const deadline = Math.floor(Date.now() / 1000) + 3600;
|
||||
// Set deadline to 5 minutes from now
|
||||
const deadline = Math.floor(Date.now() / 1000) + 300;
|
||||
|
||||
console.log(`[~] Pool parameters:`);
|
||||
console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`);
|
||||
console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`);
|
||||
console.log(` Tokens: ${tokenAddresses.join(', ')}`);
|
||||
console.log(` Payer: ${wallet.address}`);
|
||||
console.log(` Receiver: ${RECEIVER_ADDRESS}`);
|
||||
console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`);
|
||||
console.log(` Payer (provides tokens): ${RECEIVER_ADDRESS}`);
|
||||
console.log(` Receiver (gets LP tokens): ${wallet.address}`);
|
||||
console.log(` Deadline: ${new Date(deadline * 1000).toISOString()}`);
|
||||
|
||||
// Build cast send command
|
||||
const castCommand = `cast send ${PARTY_PLANNER_ADDRESS} \
|
||||
"newPool(string,string,address[],int128,uint256[],uint256,bool,address,address,uint256[],uint256,uint256)" \
|
||||
"${DEFAULT_POOL_PARAMS.name}" \
|
||||
"${DEFAULT_POOL_PARAMS.symbol}" \
|
||||
"[${tokenAddresses.join(',')}]" \
|
||||
${DEFAULT_POOL_PARAMS.kappa.toString()} \
|
||||
"[${DEFAULT_POOL_PARAMS.swapFeesPpm.join(',')}]" \
|
||||
${DEFAULT_POOL_PARAMS.flashFeePpm} \
|
||||
${DEFAULT_POOL_PARAMS.stable} \
|
||||
${RECEIVER_ADDRESS} \
|
||||
${wallet.address} \
|
||||
"[${initialDeposits.join(',')}]" \
|
||||
${DEFAULT_POOL_PARAMS.initialLpAmount.toString()} \
|
||||
${deadline} \
|
||||
--rpc-url '${RPC_URL}' \
|
||||
--from 0x12db90820dafed100e40e21128e40dcd4ff6b331 \
|
||||
--trezor --mnemonic-index 0`
|
||||
|
||||
console.log(`\n[~] Cast command:\n${castCommand}\n`);
|
||||
|
||||
try {
|
||||
const tx = await partyPlanner.newPool(
|
||||
DEFAULT_POOL_PARAMS.name,
|
||||
DEFAULT_POOL_PARAMS.symbol,
|
||||
tokenAddresses,
|
||||
DEFAULT_POOL_PARAMS.kappa,
|
||||
DEFAULT_POOL_PARAMS.swapFeePpm,
|
||||
DEFAULT_POOL_PARAMS.flashFeePpm,
|
||||
DEFAULT_POOL_PARAMS.stable,
|
||||
wallet.address, // payer (account #4)
|
||||
RECEIVER_ADDRESS, // receiver (account #7)
|
||||
initialDeposits,
|
||||
DEFAULT_POOL_PARAMS.initialLpAmount,
|
||||
deadline,
|
||||
);
|
||||
|
||||
console.log(`[~] Transaction submitted: ${tx.hash}`);
|
||||
console.log(`[~] Waiting for confirmation...`);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
|
||||
// Execute cast command
|
||||
const { execSync } = await import('child_process');
|
||||
const output = execSync(castCommand, { encoding: 'utf-8' });
|
||||
|
||||
console.log(`[+] Pool created successfully!`);
|
||||
console.log(` Transaction: ${receipt.transactionHash}`);
|
||||
console.log(` Block: ${receipt.blockNumber}`);
|
||||
console.log(` Gas used: ${receipt.gasUsed.toString()}`);
|
||||
console.log(output);
|
||||
|
||||
// Try to extract pool address from events
|
||||
if (receipt.events && receipt.events.length > 0) {
|
||||
const partyStartedEvent = receipt.events.find(e => e.event === 'PartyStarted');
|
||||
if (partyStartedEvent && partyStartedEvent.args) {
|
||||
console.log(` Pool address: ${partyStartedEvent.args.pool}`);
|
||||
}
|
||||
}
|
||||
|
||||
return receipt;
|
||||
return output;
|
||||
} catch (error) {
|
||||
console.error(`[!] Failed to create pool:`, error.message);
|
||||
|
||||
// Try to extract revert reason if available
|
||||
if (error.error && error.error.message) {
|
||||
console.error(` Revert reason: ${error.error.message}`);
|
||||
if (error.stderr) {
|
||||
console.error(` Error output: ${error.stderr.toString()}`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -333,6 +396,7 @@ Example:
|
||||
async function main() {
|
||||
console.log(`${'='.repeat(70)}`);
|
||||
console.log(`Create Pool from Real-Time Prices`);
|
||||
console.log(`Network: ${NETWORK}`);
|
||||
console.log(`${'='.repeat(70)}\n`);
|
||||
|
||||
// Parse command line arguments
|
||||
@@ -370,18 +434,25 @@ async function main() {
|
||||
|
||||
// Step 2: Calculate token amounts
|
||||
const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
|
||||
|
||||
// Step 3: Connect to Anvil
|
||||
console.log(`\n[~] Connecting to Anvil at ${ANVIL_RPC_URL}...`);
|
||||
const provider = new ethers.providers.JsonRpcProvider(ANVIL_RPC_URL);
|
||||
//
|
||||
// // Step 3: Connect to wallet
|
||||
console.log(`\n[~] Connecting to sender wallet at ${RPC_URL}...`);
|
||||
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
||||
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
||||
console.log(`[+] Connected. Using wallet: ${wallet.address}`);
|
||||
|
||||
console.log(`[+] Connected. Using sender wallet: ${wallet.address}`);
|
||||
console.log(`[+] Receiver wallet: ${RECEIVER_ADDRESS}`);
|
||||
//
|
||||
// Step 4: Check balances
|
||||
await checkBalances(provider, wallet, tokenAmounts);
|
||||
await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS);
|
||||
|
||||
// Step 5: Approve tokens
|
||||
await approveTokens(wallet, tokenAmounts);
|
||||
// // Step 5: Approve tokens
|
||||
if (NETWORK === 'mockchain' && currentConfig.receiverPrivateKey) {
|
||||
// On mockchain, use receiver wallet for approvals
|
||||
await approveTokens(provider, tokenAmounts, currentConfig.receiverPrivateKey);
|
||||
} else if (NETWORK === 'mainnet') {
|
||||
// On mainnet, use the main wallet (payer and receiver are the same)
|
||||
await approveTokens(provider, tokenAmounts, PRIVATE_KEY);
|
||||
}
|
||||
|
||||
// Step 6: Create pool
|
||||
await createPool(wallet, tokenAmounts);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"create-pool": "node create_pool_from_prices.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.3",
|
||||
"ethers": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ export default function AboutPage() {
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Verify our contracts on{' '}
|
||||
<a
|
||||
href="https://sepolia.etherscan.io/address/0x081aA8AB1984680087c01a5Cd50fC9f49742434D#code"
|
||||
href="https://etherscan.io/address/0x42977f565971F6D288a05ddEbC87A17276F71A29#code"
|
||||
target="liqp_etherscan"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1209)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(9.25540316264299,0,0,9.25540316264299,100,100)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="86.435997" height="86.435997"><svg version="1.1" viewBox="0 0 86.435997 86.435997" id="svg188" sodipodi:docname="logo-flower.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1209)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(9.25540316264299,0,0,9.25540316264299,100,100)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="86.435997" height="86.435997"><svg version="1.1" viewBox="0 0 86.435997 86.435997" id="svg188" sodipodi:docname="logo-splash.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs192"><clipPath id="SvgjsClipPath1209"><rect width="1000" height="1000" x="0" y="0" rx="500" ry="500"></rect></clipPath></defs>
|
||||
<sodipodi:namedview id="namedview190" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" inkscape:zoom="5.6893399" inkscape:cx="83.489475" inkscape:cy="69.515973" inkscape:window-width="1864" inkscape:window-height="1131" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg188"></sodipodi:namedview>
|
||||
<!-- Flower -->
|
||||
<g fill="#000000" id="g186" transform="translate(-1.782,-1.782)">
|
||||
<g id="g184">
|
||||
<circle fill="#000000" cx="45" cy="44.999001" r="5.277" id="circle148"></circle>
|
||||
<g id="g182">
|
||||
<g id="g152">
|
||||
<path fill="#0d47a1" class="blue1" d="m 45.214,33.672 c 24.117,-31.271 0.369,-31.89 0.369,-31.89 0,0 -23.756,0.069 -0.369,31.89 z" id="path150"></path>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -2,6 +2,7 @@ import '@rainbow-me/rainbowkit/styles.css';
|
||||
import '@/app/globals.css';
|
||||
import { Providers } from '@/components/providers';
|
||||
import { Metadata } from 'next';
|
||||
import Script from "next/script";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://liquidity.party'),
|
||||
@@ -37,6 +38,19 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
5
src/app/terms/page.tsx
Normal file
5
src/app/terms/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import TosCard from '@/components/tos-card';
|
||||
|
||||
export default function TermsPage() {
|
||||
return <TosCard />;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { UnstakeBasketForm } from '@/components/unstake-basket-form';
|
||||
|
||||
export default function UnstakeBasketPage() {
|
||||
return (
|
||||
<div className="w-full max-w-2xl mx-auto">
|
||||
<UnstakeBasketForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -33,12 +33,12 @@ export function Header() {
|
||||
};
|
||||
|
||||
const logoSrc = !mounted ? '/logo-dark.svg' : theme === 'dark' ? '/logo-dark.svg' : '/logo-light.svg';
|
||||
const mobileLogoSrc = '/logo-splash.svg';
|
||||
|
||||
const navLinks = [
|
||||
{ href: '/', label: 'Swap' },
|
||||
{ href: '/stake', label: 'Stake' },
|
||||
{ href: '/unstake', label: 'Unstake' },
|
||||
{ href: '/unstake-basket', label: 'Unstake Basket' },
|
||||
{ href: '/about', label: 'About' },
|
||||
];
|
||||
|
||||
@@ -46,31 +46,31 @@ export function Header() {
|
||||
<header className="border-b">
|
||||
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/">
|
||||
{/* Desktop logo - just a link */}
|
||||
<Link href="/" className="hidden sm:block">
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="Liquidity Party"
|
||||
className="h-8 w-auto cursor-pointer"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Mobile logo - toggles menu */}
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="sm:hidden focus:outline-none"
|
||||
>
|
||||
<img
|
||||
src={mobileLogoSrc}
|
||||
alt="Liquidity Party"
|
||||
className="h-8 w-8 cursor-pointer"
|
||||
/>
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</button>
|
||||
|
||||
<span className="bg-yellow-500/20 text-yellow-500 text-xs font-bold px-2 py-1 rounded border border-yellow-500/50">
|
||||
BETA
|
||||
</span>
|
||||
|
||||
{/* Mobile Menu Button - Moved to left side */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="md:hidden"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
>
|
||||
{mobileMenuOpen ? (
|
||||
<X className="h-5 w-5" />
|
||||
) : (
|
||||
<Menu className="h-5 w-5" />
|
||||
)}
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -101,6 +101,16 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||
<main className="flex-1 container mx-auto px-4 py-8">
|
||||
{children}
|
||||
</main>
|
||||
<footer className="py-4 text-center text-sm text-muted-foreground border-t">
|
||||
<div className="mb-2">
|
||||
©{new Date().getFullYear()} Dexorder Trading Services, Ltd. (BVI)
|
||||
</div>
|
||||
<div>
|
||||
<a href="/terms" className="hover:text-foreground underline transition-colors">
|
||||
Terms of Service
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</ToastProvider>
|
||||
</Web3Provider>
|
||||
|
||||
@@ -6,11 +6,12 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ChevronDown, CheckCircle, XCircle, Loader2, ArrowDownUp } from 'lucide-react';
|
||||
import { useAccount } from 'wagmi';
|
||||
import { useGetAllPools, useTokenDetails, useSwapMintAmounts, useBurnSwapAmounts, useLPTokenBalance, type PoolDetails, type TokenDetails, type BurnSwapAmounts } from '@/hooks/usePartyPlanner';
|
||||
import { useSwapMint, useBurnSwap, type ActualSwapMintAmounts, type ActualBurnSwapAmounts } from '@/hooks/usePartyPool';
|
||||
import { useAccount, usePublicClient } from 'wagmi';
|
||||
import { useGetAllPools, useTokenDetails, useSwapMintAmounts, useBurnSwapAmounts, useLPTokenBalance, useBurnAmounts, type PoolDetails, type TokenDetails, type BurnSwapAmounts } from '@/hooks/usePartyPlanner';
|
||||
import { useSwapMint, useBurnSwap, useBurn, type ActualSwapMintAmounts, type ActualBurnSwapAmounts, type ActualBurnAmounts } from '@/hooks/usePartyPool';
|
||||
import { formatUnits, parseUnits } from 'viem';
|
||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||
import { ERC20ABI } from '@/contracts/ERC20ABI';
|
||||
|
||||
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
||||
type Mode = 'stake' | 'unstake';
|
||||
@@ -19,9 +20,48 @@ interface StakeFormProps {
|
||||
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 {
|
||||
address: `0x${string}`;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
const { t } = useTranslation();
|
||||
const { isConnected, address } = useAccount();
|
||||
const publicClient = usePublicClient();
|
||||
const [mode, setMode] = useState<Mode>(defaultMode);
|
||||
const [stakeAmount, setStakeAmount] = useState('');
|
||||
const [selectedPool, setSelectedPool] = useState<PoolDetails | null>(null);
|
||||
@@ -32,18 +72,33 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
const [transactionError, setTransactionError] = useState<string | null>(null);
|
||||
const [actualSwapMintAmounts, setActualSwapMintAmounts] = useState<ActualSwapMintAmounts | null>(null);
|
||||
const [actualBurnSwapAmounts, setActualBurnSwapAmounts] = useState<ActualBurnSwapAmounts | null>(null);
|
||||
const [actualBurnAmounts, setActualBurnAmounts] = useState<ActualBurnAmounts | null>(null);
|
||||
const [redeemAll, setRedeemAll] = useState(false);
|
||||
const [poolTokens, setPoolTokens] = useState<TokenInfo[]>([]);
|
||||
const poolDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const tokenDropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Fetch all pools using the new hook
|
||||
const { poolDetails, loading: poolsLoading } = useGetAllPools();
|
||||
|
||||
// Filter pools based on mode: stake mode shows only working pools, unstake shows all
|
||||
const filteredPools = useMemo(() => {
|
||||
if (!poolDetails) return null;
|
||||
if (mode === 'stake') {
|
||||
// Stake mode: only show working (non-killed) pools
|
||||
return poolDetails.filter(pool => !pool.isKilled);
|
||||
}
|
||||
// Unstake mode: show all pools (working + killed)
|
||||
return poolDetails;
|
||||
}, [poolDetails, mode]);
|
||||
|
||||
// Get token details for the user
|
||||
const { tokenDetails, loading: tokensLoading } = useTokenDetails(address);
|
||||
|
||||
// Initialize swap mint and burn swap hooks
|
||||
const { executeSwapMint, isSwapMinting } = useSwapMint();
|
||||
const { executeBurnSwap, isBurnSwapping } = useBurnSwap();
|
||||
const { executeBurn, isBurning } = useBurn();
|
||||
|
||||
// Fetch LP token balance (for unstake mode) - must be before isAmountExceedingBalance
|
||||
const { lpBalance } = useLPTokenBalance(
|
||||
@@ -103,31 +158,119 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
|
||||
// Parse the stake amount to Wei
|
||||
const maxAmountIn = useMemo(() => {
|
||||
if (!stakeAmount || !selectedToken) return undefined;
|
||||
if (!stakeAmount) return undefined;
|
||||
|
||||
try {
|
||||
// For unstake mode, LP tokens always have 18 decimals
|
||||
const decimals = mode === 'unstake' ? 18 : selectedToken.decimals;
|
||||
return parseUnits(stakeAmount, decimals);
|
||||
if (mode === 'unstake') {
|
||||
return parseUnits(stakeAmount, 18);
|
||||
}
|
||||
if (!selectedToken) return undefined;
|
||||
return parseUnits(stakeAmount, selectedToken.decimals);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}, [stakeAmount, selectedToken, mode]);
|
||||
|
||||
// Fetch swap mint amounts (for stake mode)
|
||||
const { swapMintAmounts, loading: swapMintLoading } = useSwapMintAmounts(
|
||||
const { swapMintAmounts, loading: swapMintLoading, error: swapMintError } = useSwapMintAmounts(
|
||||
mode === 'stake' ? selectedPool?.address : 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)
|
||||
const { burnSwapAmounts, loading: burnSwapLoading } = useBurnSwapAmounts(
|
||||
mode === 'unstake' ? selectedPool?.address : undefined,
|
||||
mode === 'unstake' ? maxAmountIn : undefined,
|
||||
mode === 'unstake' ? inputTokenIndex : undefined
|
||||
// Fetch burn swap amounts (for unstake mode, only when not redeeming all)
|
||||
const { burnSwapAmounts, loading: burnSwapLoading, error: burnSwapError } = useBurnSwapAmounts(
|
||||
mode === 'unstake' && !redeemAll ? selectedPool?.address : undefined,
|
||||
mode === 'unstake' && !redeemAll ? maxAmountIn : undefined,
|
||||
mode === 'unstake' && !redeemAll ? inputTokenIndex : 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%)
|
||||
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)
|
||||
const { burnAmounts, loading: burnAmountsLoading } = useBurnAmounts(
|
||||
mode === 'unstake' && redeemAll ? selectedPool?.address : undefined,
|
||||
mode === 'unstake' && redeemAll ? maxAmountIn : undefined
|
||||
);
|
||||
|
||||
// Fetch token details for the selected pool when Redeem All is active
|
||||
useEffect(() => {
|
||||
if (!publicClient || !selectedPool || mode !== 'unstake' || !redeemAll) {
|
||||
setPoolTokens([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchTokenDetails = async () => {
|
||||
const tokenInfos: TokenInfo[] = [];
|
||||
|
||||
for (const tokenAddress of selectedPool.tokens) {
|
||||
try {
|
||||
const [symbol, decimals] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
address: tokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'symbol',
|
||||
}) as Promise<string>,
|
||||
publicClient.readContract({
|
||||
address: tokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'decimals',
|
||||
}) as Promise<number>,
|
||||
]);
|
||||
|
||||
tokenInfos.push({
|
||||
address: tokenAddress as `0x${string}`,
|
||||
symbol,
|
||||
decimals,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error fetching token details for ${tokenAddress}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
setPoolTokens(tokenInfos);
|
||||
};
|
||||
|
||||
fetchTokenDetails();
|
||||
}, [publicClient, selectedPool, mode, redeemAll]);
|
||||
|
||||
// Auto-enable Redeem All for killed pools in unstake mode
|
||||
useEffect(() => {
|
||||
if (mode === 'unstake' && selectedPool?.isKilled) {
|
||||
setRedeemAll(true);
|
||||
setSelectedToken(null); // Clear token selection for killed pools
|
||||
}
|
||||
}, [mode, selectedPool]);
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@@ -144,16 +287,23 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
}, []);
|
||||
|
||||
const handleStake = async () => {
|
||||
if (!selectedPool || !selectedToken || !stakeAmount || inputTokenIndex === undefined || !maxAmountIn) {
|
||||
console.error('Missing required stake parameters');
|
||||
if (!selectedPool || !stakeAmount || !maxAmountIn) {
|
||||
console.error('Missing required parameters');
|
||||
return;
|
||||
}
|
||||
if (mode === 'unstake' && !redeemAll) {
|
||||
if (!selectedToken || inputTokenIndex === undefined) {
|
||||
console.error('Missing required unstake parameters');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setTransactionStatus('pending');
|
||||
setTransactionError(null);
|
||||
|
||||
try {
|
||||
if (mode === 'stake') {
|
||||
if (!selectedToken || inputTokenIndex === undefined) throw new Error('Missing stake parameters');
|
||||
// Execute the swap mint transaction and capture actual amounts
|
||||
const result = await executeSwapMint(
|
||||
selectedPool.address,
|
||||
@@ -166,8 +316,19 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
if (result?.actualSwapMintAmounts) {
|
||||
setActualSwapMintAmounts(result.actualSwapMintAmounts);
|
||||
}
|
||||
} else if (redeemAll) {
|
||||
// Execute full burn (redeem all tokens from the pool)
|
||||
const result = await executeBurn(
|
||||
selectedPool.address,
|
||||
maxAmountIn,
|
||||
false // unwrap = false by default
|
||||
);
|
||||
if (result?.actualBurnAmounts) {
|
||||
setActualBurnAmounts(result.actualBurnAmounts);
|
||||
}
|
||||
} else {
|
||||
// Execute the burn swap transaction and capture actual amounts
|
||||
if (inputTokenIndex === undefined) throw new Error('Missing input token index');
|
||||
const result = await executeBurnSwap(
|
||||
selectedPool.address,
|
||||
maxAmountIn,
|
||||
@@ -183,7 +344,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
|
||||
setTransactionStatus('success');
|
||||
} catch (err) {
|
||||
console.error(`${mode === 'stake' ? 'Stake' : 'Unstake'} failed:`, err);
|
||||
console.error(`${mode === 'stake' ? 'Stake' : redeemAll ? 'Redeem' : 'Unstake'} failed:`, err);
|
||||
setTransactionError(err instanceof Error ? err.message : 'Transaction failed');
|
||||
setTransactionStatus('error');
|
||||
}
|
||||
@@ -195,6 +356,10 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
setStakeAmount('');
|
||||
setSelectedPool(null);
|
||||
setSelectedToken(null);
|
||||
setRedeemAll(false);
|
||||
setActualBurnAmounts(null);
|
||||
setActualBurnSwapAmounts(null);
|
||||
setActualSwapMintAmounts(null);
|
||||
}
|
||||
setTransactionStatus('idle');
|
||||
setTransactionError(null);
|
||||
@@ -206,6 +371,10 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
setStakeAmount('');
|
||||
setSelectedPool(null);
|
||||
setSelectedToken(null);
|
||||
setRedeemAll(false);
|
||||
setActualBurnAmounts(null);
|
||||
setActualBurnSwapAmounts(null);
|
||||
setActualSwapMintAmounts(null);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -241,8 +410,8 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
</Button>
|
||||
{isPoolDropdownOpen && (
|
||||
<div className="absolute z-50 w-full mt-2 bg-popover border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
||||
{poolDetails && poolDetails.length > 0 ? (
|
||||
poolDetails.map((pool) => (
|
||||
{filteredPools && filteredPools.length > 0 ? (
|
||||
filteredPools.map((pool) => (
|
||||
<button
|
||||
key={pool.address}
|
||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none"
|
||||
@@ -252,9 +421,21 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
setSelectedToken(null);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{pool.symbol}</span>
|
||||
<span className="text-xs text-muted-foreground">{pool.name}</span>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{pool.symbol}</span>
|
||||
<span className="text-xs text-muted-foreground">{pool.name}</span>
|
||||
</div>
|
||||
{(pool.price || pool.tvl) && (
|
||||
<div className="flex flex-col items-end">
|
||||
{pool.price && (
|
||||
<span className="text-sm font-medium text-muted-foreground">{pool.price}</span>
|
||||
)}
|
||||
{pool.tvl && (
|
||||
<span className="text-xs text-muted-foreground">TVL: {pool.tvl}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
@@ -281,7 +462,14 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
>
|
||||
{selectedPool ? (
|
||||
<div className="flex flex-col items-start">
|
||||
<span className="font-medium">{selectedPool.symbol}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{selectedPool.symbol}</span>
|
||||
{selectedPool.isKilled && (
|
||||
<span className="text-xs px-2 py-0.5 bg-orange-500/20 text-orange-600 dark:text-orange-400 border border-orange-500/30 rounded">
|
||||
Redeem Only
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">{selectedPool.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
@@ -291,8 +479,8 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
</Button>
|
||||
{isPoolDropdownOpen && (
|
||||
<div className="absolute z-50 w-full mt-2 bg-popover border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
||||
{poolDetails && poolDetails.length > 0 ? (
|
||||
poolDetails.map((pool) => (
|
||||
{filteredPools && filteredPools.length > 0 ? (
|
||||
filteredPools.map((pool) => (
|
||||
<button
|
||||
key={pool.address}
|
||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none"
|
||||
@@ -302,9 +490,28 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
setSelectedToken(null);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{pool.symbol}</span>
|
||||
<span className="text-xs text-muted-foreground">{pool.name}</span>
|
||||
<div className="flex justify-between items-center w-full gap-2">
|
||||
<div className="flex flex-col flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{pool.symbol}</span>
|
||||
{pool.isKilled && (
|
||||
<span className="text-xs px-2 py-0.5 bg-orange-500/20 text-orange-600 dark:text-orange-400 border border-orange-500/30 rounded whitespace-nowrap">
|
||||
Redeem Only
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">{pool.name}</span>
|
||||
</div>
|
||||
{!pool.isKilled && (pool.price || pool.tvl) && (
|
||||
<div className="flex flex-col items-end">
|
||||
{pool.price && (
|
||||
<span className="text-sm font-medium text-muted-foreground">{pool.price}</span>
|
||||
)}
|
||||
{pool.tvl && (
|
||||
<span className="text-xs text-muted-foreground">TVL: {pool.tvl}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
@@ -395,9 +602,11 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
variant="secondary"
|
||||
className="w-full h-16 justify-between"
|
||||
onClick={() => setIsTokenDropdownOpen(!isTokenDropdownOpen)}
|
||||
disabled={!selectedPool}
|
||||
disabled={!selectedPool || redeemAll}
|
||||
>
|
||||
{selectedToken ? (
|
||||
{redeemAll ? (
|
||||
<span className="font-medium">All Tokens (Redeem All)</span>
|
||||
) : selectedToken ? (
|
||||
<span className="font-medium">{selectedToken.symbol}</span>
|
||||
) : (
|
||||
t('stake.selectToken')
|
||||
@@ -407,7 +616,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
<div className="text-xs text-muted-foreground text-right mt-1">
|
||||
{t('swap.balance')}: {lpBalance !== null && selectedPool ? formatUnits(lpBalance, 18) : '0.00'} {selectedPool?.symbol || 'LP'}
|
||||
</div>
|
||||
{isTokenDropdownOpen && (
|
||||
{!redeemAll && isTokenDropdownOpen && (
|
||||
<div className="absolute z-50 w-full mt-2 bg-popover border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
||||
{availableTokensForPool.length > 0 ? (
|
||||
availableTokensForPool.map((token) => (
|
||||
@@ -441,21 +650,42 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<label className="text-muted-foreground">{t('stake.amount')}</label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => {
|
||||
if (mode === 'stake' && selectedToken) {
|
||||
setStakeAmount(formatUnits(selectedToken.balance, selectedToken.decimals));
|
||||
} else if (mode === 'unstake' && lpBalance !== null) {
|
||||
setStakeAmount(formatUnits(lpBalance, 18));
|
||||
}
|
||||
}}
|
||||
disabled={!selectedToken || !selectedPool || (mode === 'stake' ? !selectedToken : lpBalance === null)}
|
||||
>
|
||||
MAX
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
{mode === 'unstake' && (
|
||||
<Button
|
||||
variant={redeemAll ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => {
|
||||
// Prevent toggling off for killed pools
|
||||
if (selectedPool?.isKilled) return;
|
||||
setRedeemAll(!redeemAll);
|
||||
if (!redeemAll && lpBalance !== null) {
|
||||
setStakeAmount(formatUnits(lpBalance, 18));
|
||||
}
|
||||
}}
|
||||
disabled={!selectedPool || selectedPool?.isKilled}
|
||||
title={selectedPool?.isKilled ? "Killed pools can only use Redeem All mode" : "Burn entire LP token and receive all underlying tokens"}
|
||||
>
|
||||
{redeemAll ? 'Redeem All: ON' : 'Redeem All'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => {
|
||||
if (mode === 'stake' && selectedToken) {
|
||||
setStakeAmount(formatUnits(selectedToken.balance, selectedToken.decimals));
|
||||
} else if (mode === 'unstake' && lpBalance !== null) {
|
||||
setStakeAmount(formatUnits(lpBalance, 18));
|
||||
}
|
||||
}}
|
||||
disabled={!selectedPool || (mode === 'stake' ? !selectedToken : lpBalance === null)}
|
||||
>
|
||||
MAX
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
@@ -463,7 +693,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
value={stakeAmount}
|
||||
onChange={(e) => setStakeAmount(e.target.value)}
|
||||
className="text-2xl h-16"
|
||||
disabled={!selectedToken || !selectedPool}
|
||||
disabled={!selectedPool || (mode === 'stake' && !selectedToken)}
|
||||
/>
|
||||
{isAmountExceedingBalance && (
|
||||
<p className="text-sm text-destructive">
|
||||
@@ -496,8 +726,60 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
</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) */}
|
||||
{mode === 'unstake' && 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="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">{t('stake.amountOut')}:</span>
|
||||
@@ -514,17 +796,51 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Redeem All Tokens Display */}
|
||||
{mode === 'unstake' && redeemAll && poolTokens.length > 0 && !isAmountExceedingBalance && (
|
||||
<div className="px-4 py-3 bg-muted/30 rounded-lg space-y-2">
|
||||
<div className="text-sm font-medium mb-2">You will receive:</div>
|
||||
<div className="space-y-1">
|
||||
{poolTokens.map((token, index) => (
|
||||
<div key={token.address} className="text-sm flex justify-between">
|
||||
<span className="text-muted-foreground">{token.symbol}:</span>
|
||||
<span className="font-medium">
|
||||
{burnAmountsLoading ? 'Calculating...' : burnAmounts && burnAmounts[index]
|
||||
? `${Number(formatUnits(burnAmounts[index], token.decimals)).toLocaleString('en-US', { maximumFractionDigits: 6 })}`
|
||||
: '(proportional to pool composition)'
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stake/Unstake Button */}
|
||||
<Button
|
||||
className="w-full h-14 text-lg"
|
||||
onClick={handleStake}
|
||||
disabled={!isConnected || !stakeAmount || !selectedPool || !selectedToken || isAmountExceedingBalance || isSwapMinting || isBurnSwapping}
|
||||
disabled={
|
||||
!isConnected ||
|
||||
!stakeAmount ||
|
||||
!selectedPool ||
|
||||
isAmountExceedingBalance ||
|
||||
(mode === 'stake'
|
||||
? (!selectedToken || isSwapMinting || slippageExceedsLimit || !!swapMintError)
|
||||
: (redeemAll
|
||||
? isBurning
|
||||
: (!selectedToken || inputTokenIndex === undefined || isBurnSwapping || unstakeSlippageExceedsLimit || !!burnSwapError)))
|
||||
}
|
||||
>
|
||||
{!isConnected
|
||||
? t('swap.connectWalletToSwap')
|
||||
: (isSwapMinting || isBurnSwapping)
|
||||
? mode === 'stake' ? 'Staking...' : 'Unstaking...'
|
||||
: mode === 'stake' ? t('stake.stakeButton') : 'Unstake'}
|
||||
: (mode === 'stake' && isSwapMinting)
|
||||
? 'Staking...'
|
||||
: (mode === 'unstake' && (isBurnSwapping || isBurning))
|
||||
? (redeemAll ? 'Redeeming...' : 'Unstaking...')
|
||||
: mode === 'stake'
|
||||
? t('stake.stakeButton')
|
||||
: (redeemAll ? 'Redeem All' : 'Unstake')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
|
||||
@@ -536,12 +852,14 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<Loader2 className="h-16 w-16 animate-spin text-primary" />
|
||||
<h3 className="text-xl font-semibold text-center">
|
||||
{mode === 'stake' ? 'Approving Stake' : 'Approving Unstake'}
|
||||
{mode === 'stake' ? 'Approving Stake' : redeemAll ? 'Approving Redeem All' : 'Approving Unstake'}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
{mode === 'stake'
|
||||
? `Staking ${stakeAmount} ${selectedToken?.symbol} to ${selectedPool?.symbol}`
|
||||
: `Unstaking ${stakeAmount} ${selectedPool?.symbol} LP for ${selectedToken?.symbol}`
|
||||
: redeemAll
|
||||
? `Redeeming ${stakeAmount} ${selectedPool?.symbol} LP for all pool tokens`
|
||||
: `Unstaking ${stakeAmount} ${selectedPool?.symbol} LP for ${selectedToken?.symbol}`
|
||||
}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
@@ -554,7 +872,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<CheckCircle className="h-16 w-16 text-green-500" />
|
||||
<h3 className="text-xl font-semibold text-center">
|
||||
{mode === 'stake' ? 'Stake Confirmed!' : 'Unstake Confirmed!'}
|
||||
{mode === 'stake' ? 'Stake Confirmed!' : redeemAll ? 'Redeem Confirmed!' : 'Unstake Confirmed!'}
|
||||
</h3>
|
||||
|
||||
{/* Display actual amounts or estimates */}
|
||||
@@ -573,12 +891,8 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
<span className="font-medium">{formatUnits(actualSwapMintAmounts.lpMinted, 18)} {selectedPool.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">LP Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapMintAmounts.lpFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Protocol Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapMintAmounts.protocolFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||
<span className="text-muted-foreground">Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapMintAmounts.lpFee + actualSwapMintAmounts.protocolFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
@@ -594,6 +908,44 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : redeemAll ? (
|
||||
// Redeem All mode success message
|
||||
<div className="w-full space-y-3">
|
||||
{actualBurnAmounts && selectedPool ? (
|
||||
// Show actual amounts from transaction
|
||||
<>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">LP Burned:</span>
|
||||
<span className="font-medium">{formatUnits(actualBurnAmounts.lpBurned, 18)} {selectedPool.symbol}</span>
|
||||
</div>
|
||||
<div className="text-sm font-medium mt-3 mb-2">Tokens Received:</div>
|
||||
{actualBurnAmounts.withdrawAmounts.map((amount, index) => {
|
||||
const token = poolTokens[index];
|
||||
if (!token) return null;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex justify-between text-sm pl-2">
|
||||
<span className="text-muted-foreground">{token.symbol}:</span>
|
||||
<span className="font-medium">
|
||||
{formatUnits(amount, token.decimals)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
// Fallback to estimates
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Successfully redeemed {stakeAmount} {selectedPool?.symbol} LP for all pool tokens
|
||||
<br />
|
||||
<span className="text-xs italic opacity-70">
|
||||
*Disclaimer: This is an estimate from the protocol. The actual amounts might be slightly different due to slippage.
|
||||
</span>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
// Unstake mode success message
|
||||
<div className="w-full space-y-3">
|
||||
@@ -609,12 +961,8 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||
<span className="font-medium">{formatUnits(actualBurnSwapAmounts.amountOut, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">LP Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualBurnSwapAmounts.lpFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Protocol Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualBurnSwapAmounts.protocolFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||
<span className="text-muted-foreground">Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualBurnSwapAmounts.lpFee + actualBurnSwapAmounts.protocolFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -6,17 +6,19 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowDownUp, ChevronDown, Settings, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||
import { useAccount } from 'wagmi';
|
||||
import { useAccount, useChainId } from 'wagmi';
|
||||
import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner';
|
||||
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
|
||||
import { formatUnits, parseUnits } from 'viem';
|
||||
import { SwapReviewModal } from './swap-review-modal';
|
||||
import UniswapQuote from './uniswap-quote';
|
||||
|
||||
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
||||
|
||||
export function SwapForm() {
|
||||
const { t } = useTranslation();
|
||||
const { isConnected, address } = useAccount();
|
||||
const chainId = useChainId();
|
||||
const [fromAmount, setFromAmount] = useState('');
|
||||
const [toAmount, setToAmount] = useState('');
|
||||
const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null);
|
||||
@@ -36,7 +38,7 @@ export function SwapForm() {
|
||||
const { tokenDetails, loading } = useTokenDetails(address);
|
||||
|
||||
// Get available tokens for the selected "from" token
|
||||
const { availableTokens } = useGetPoolsByToken(selectedFromToken?.address);
|
||||
const { availableTokens, error: poolsError } = useGetPoolsByToken(selectedFromToken?.address);
|
||||
|
||||
// Only calculate swap amounts when both tokens are selected
|
||||
// Use useMemo to prevent creating a new array reference on every render
|
||||
@@ -60,17 +62,44 @@ export function SwapForm() {
|
||||
currentSlippage
|
||||
);
|
||||
|
||||
// Check if user has insufficient balance
|
||||
const hasInsufficientBalance = useMemo(() => {
|
||||
if (!selectedFromToken || !fromAmount || fromAmount === '') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const inputAmount = parseUnits(fromAmount, selectedFromToken.decimals);
|
||||
return inputAmount > selectedFromToken.balance;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, [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
|
||||
const { executeSwap, estimateGas, isSwapping, gasEstimate, isEstimatingGas } = useSwap();
|
||||
|
||||
// Update "You Receive" amount when swap calculation completes
|
||||
useEffect(() => {
|
||||
if (hasInsufficientBalance) {
|
||||
setToAmount('');
|
||||
return;
|
||||
}
|
||||
if (swapAmounts && swapAmounts.length > 0 && selectedToToken) {
|
||||
const swapResult = swapAmounts[0]; // Get the first (and should be only) result
|
||||
const formattedAmount = formatUnits(swapResult.amountOut, selectedToToken.decimals);
|
||||
setToAmount(formattedAmount);
|
||||
} else {
|
||||
setToAmount('');
|
||||
}
|
||||
}, [swapAmounts, selectedToToken]);
|
||||
}, [swapAmounts, selectedToToken, hasInsufficientBalance]);
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
useEffect(() => {
|
||||
@@ -284,6 +313,7 @@ export function SwapForm() {
|
||||
onChange={(e) => setToAmount(e.target.value)}
|
||||
className="text-2xl h-16"
|
||||
disabled={!selectedFromToken}
|
||||
readOnly
|
||||
/>
|
||||
<div className="relative min-w-[160px] space-y-1" ref={toDropdownRef}>
|
||||
<Button
|
||||
@@ -326,7 +356,7 @@ export function SwapForm() {
|
||||
))
|
||||
) : selectedFromToken ? (
|
||||
<div className="px-4 py-3 text-sm text-muted-foreground">
|
||||
{loading ? 'Loading available tokens...' : 'No tokens available for swap'}
|
||||
{loading ? 'Loading available tokens...' : poolsError || 'No tokens available for swap'}
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-4 py-3 text-sm text-muted-foreground">
|
||||
@@ -339,6 +369,56 @@ export function SwapForm() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error message for unsupported tokens */}
|
||||
{poolsError && selectedFromToken && (
|
||||
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||
<p className="text-sm text-destructive">{poolsError}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error message for insufficient balance */}
|
||||
{hasInsufficientBalance && (
|
||||
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||
<p className="text-sm text-destructive">Insufficient balance</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error message for slippage exceeding 5% */}
|
||||
{slippageExceedsLimit && (
|
||||
<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 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">
|
||||
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium">⚠️ Slippage Exceeds Your Tolerance</p>
|
||||
<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.
|
||||
</p>
|
||||
</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 */}
|
||||
{isConnected && fromAmount && toAmount && (
|
||||
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">
|
||||
@@ -377,10 +457,14 @@ export function SwapForm() {
|
||||
<Button
|
||||
className="w-full h-14 text-lg"
|
||||
onClick={() => setIsReviewModalOpen(true)}
|
||||
disabled={!isConnected || !fromAmount || !toAmount}
|
||||
disabled={!isConnected || !fromAmount || !toAmount || !!poolsError || hasInsufficientBalance || slippageExceedsLimit}
|
||||
>
|
||||
{!isConnected
|
||||
? t('swap.connectWalletToSwap')
|
||||
: hasInsufficientBalance
|
||||
? 'Insufficient Balance'
|
||||
: slippageExceedsLimit
|
||||
? 'Slippage Too High'
|
||||
: 'Review'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
@@ -424,7 +508,7 @@ export function SwapForm() {
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Your transaction will revert if the price changes unfavorably by more than this percentage.
|
||||
You will be warned if the slippage exceeds this setting, and you can choose whether to proceed with the trade.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -487,12 +571,8 @@ export function SwapForm() {
|
||||
<span className="font-medium">{formatUnits(actualSwapAmounts.amountOut, selectedToToken.decimals)} {selectedToToken.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">LP Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapAmounts.lpFee, selectedFromToken.decimals)} {selectedFromToken.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Protocol Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapAmounts.protocolFee, selectedFromToken.decimals)} {selectedFromToken.symbol}</span>
|
||||
<span className="text-muted-foreground">Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapAmounts.lpFee + actualSwapAmounts.protocolFee, selectedFromToken.decimals)} {selectedFromToken.symbol}</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
675
src/components/tos-card.tsx
Normal file
675
src/components/tos-card.tsx
Normal file
@@ -0,0 +1,675 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function TosCard() {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto p-5">
|
||||
<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>
|
||||
{/* MAKE SURE TO UPDATE THE VERSION VARIABLE AS WELL */}
|
||||
<p className="text-center mb-4">Last Updated November 18, 2024</p>
|
||||
|
||||
<div className="mb-4 leading-relaxed">
|
||||
Please read these Terms of Service (the "<b>Terms</b>")
|
||||
carefully because they govern your use of the website (and all subdomains and subpages
|
||||
thereon) located at liquidity.party, including without limitation the subdomains
|
||||
app.liquidity.party and www.liquidity.party (collectively, the "<b>Site</b>"), and the
|
||||
Liquidity Party
|
||||
web application graphical user interface and any other services accessible via the Site
|
||||
(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>we</b>," "<b>our</b>," or "<b>us</b>").
|
||||
</div>
|
||||
|
||||
<div className="mb-4 leading-relaxed">
|
||||
<b>
|
||||
BY USING THE DEXORDER SERVICE, YOU REPRESENT THAT (I) YOU ARE NOT LOCATED WITHIN THE
|
||||
UNITED STATES; AND (II) YOU ARE NOT A PERSON OR ENTITY WHO IS RESIDENT IN, A CITIZEN OF,
|
||||
IS LOCATED IN, IS INCORPORATED IN, OR HAS A REGISTERED OFFICE IN ANY RESTRICTED
|
||||
TERRITORY, AS DEFINED BELOW (ANY SUCH PERSON OR ENTITY FROM WITHIN THE UNITED STATES OR
|
||||
A RESTRICTED TERRITORY, IS REFERRED TO HEREIN AS A "RESTRICTED PERSON").
|
||||
</b>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 leading-relaxed">
|
||||
WHEN YOU AGREE TO THESE TERMS, YOU ARE AGREEING (WITH LIMITED EXCEPTION) TO RESOLVE ANY
|
||||
DISPUTE BETWEEN YOU AND DEXORDER THROUGH BINDING, INDIVIDUAL ARBITRATION RATHER THAN IN
|
||||
COURT. PLEASE REVIEW CAREFULLY SECTION 16 (DISPUTE RESOLUTION) BELOW FOR DETAILS REGARDING
|
||||
ARBITRATION. HOWEVER, IF YOU ARE A RESIDENT OF A JURISDICTION WHERE APPLICABLE LAW
|
||||
PROHIBITS ARBITRATION OF DISPUTES, THE AGREEMENT TO ARBITRATE IN SECTION 16 WILL NOT APPLY
|
||||
TO YOU, BUT THE PROVISIONS OF SECTION 15 (GOVERNING LAW) WILL APPLY INSTEAD.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">1. Description of Dexorder Service</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(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
|
||||
set of smart contracts (the "<b>Protocol</b>").
|
||||
You may use the <b>App</b> to send signals to, interact with, and initiate actions on the
|
||||
decentralized exchange ("<b>DEX</b>").
|
||||
</div>
|
||||
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) <b>Interface</b>. The Dexorder Service provides you with access to the Protocol.
|
||||
All information provided in
|
||||
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
|
||||
contained on the Dexorder Service or any other information that we make available at any
|
||||
time, including blog posts, data, articles, links to third-party content, Discord content,
|
||||
news feeds, tutorials, tweets, and videos. Before you make any financial, legal, technical,
|
||||
or other decisions involving the Dexorder Service, you should seek independent professional
|
||||
advice from a licensed and qualified individual in the area for which such advice would be
|
||||
appropriate. Because the Dexorder Service provides information about the Protocol, these
|
||||
Terms also provide some information about the use of the Protocol. This information is not
|
||||
intended to be comprehensive or address all aspects of the Protocol.
|
||||
</div>
|
||||
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(c) <b>Our Relationship</b>. You acknowledge and agree that Dexorder is an online platform
|
||||
provider and not a financial institution, broker dealer, exchange or money services
|
||||
business. Dexorder does not direct or control the day-to-day activities of users accessing
|
||||
the Dexorder Service. Neither we nor any affiliated entity is a party to any transaction on
|
||||
the blockchain network underlying the Protocol; we do not have possession, custody or
|
||||
control over any cryptoassets appearing on the Dexorder Service or on the Protocol; and we
|
||||
do not have possession, custody, or control over any user's funds or cryptoassets. Further,
|
||||
we do not store, send, or receive any funds or cryptoassets on your behalf. You understand
|
||||
that when you interact with any Protocol smart contracts, you retain control over your
|
||||
cryptoassets at all times. You are solely responsible for evaluating any proposed technical
|
||||
changes and how such changes may alter current or future Interactions. Furthermore, you
|
||||
understand and acknowledge that only you have absolute and ultimate authority over the
|
||||
implementation of any such changes and the responsibility therefor. The private key
|
||||
associated with your Vault is the only private key that can control the cryptoassets in the
|
||||
Vault. You alone are responsible for securing your private keys. We do not have access to
|
||||
your private keys. Because the Protocol is non-custodial, we are not intermediaries, agents,
|
||||
advisors, or custodians, and we do not have a fiduciary relationship or obligation to you
|
||||
regarding any other decisions or activities that you affect when using the Dexorder Service
|
||||
or interacting with the Protocol. You acknowledge that we, for the avoidance of doubt, do
|
||||
not have any information regarding any users, users' identities, or services beyond what is
|
||||
available, obtainable publicly via the blockchain, or shared by you when you access the
|
||||
Dexorder Service. We are not responsible for any activities you engage in when using the
|
||||
Dexorder Service, and you should understand the risks associated with cryptoassets,
|
||||
blockchain technology generally, and the Interface.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">2. Agreement to Terms</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
By using our Dexorder Service, you agree to be bound by these Terms. If you don't agree to
|
||||
be bound by these Terms, do not use the Dexorder Service.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">3. Changes to these Terms or the Dexorder Service</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
We may update the Terms, including any addendum terms, from time to time in our sole
|
||||
discretion. If we do, we'll let you know by posting the updated Terms on the Site and/or may
|
||||
also send other communications. It's important that you review the Terms whenever we update
|
||||
them or you use the Dexorder Service. If you continue to use the Dexorder Service after we
|
||||
have posted updated Terms it means that you accept and agree to the changes. If you don't
|
||||
agree to be bound by the changes, you may not use the Dexorder Service anymore. Because our
|
||||
Dexorder Service is evolving over time we may change or discontinue all or any part of the
|
||||
Dexorder Service, at any time and without notice, at our sole discretion.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">4. Who May Use the Dexorder Service?</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(a) <u>Eligibility</u>. The Dexorder Service is only available to users in certain
|
||||
jurisdictions outside of the United States and that are at least 18 years old, capable of
|
||||
forming a binding contract with the Dexorder and not otherwise barred from using the
|
||||
Dexorder Service under Applicable Law. You may not attempt to access or use the Dexorder
|
||||
Service if you are not permitted to do so (including without limitation if you are a
|
||||
Restricted Person).
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) <u>Compliance</u>. You certify that you will comply with all Applicable Law when using
|
||||
the Dexorder Service. You are solely responsible for ensuring that your access and use of
|
||||
the Dexorder Service in such country, territory, or jurisdiction does not violate any
|
||||
Applicable Laws. You must not use any software or networking techniques, including use of a
|
||||
virtual private network ("<b>VPN</b>") to circumvent or attempt to circumvent this
|
||||
prohibition. We reserve the right to monitor the locations from which our Dexorder Service
|
||||
is accessed. Furthermore, we reserve the right, at any time, in our sole discretion, to
|
||||
block access to the Dexorder Service, in whole or in part, from any geographic location, IP
|
||||
addresses, and unique device identifiers
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">5. Use of the Dexorder Service</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(a) <u>User Representations and Warranties</u>. As a condition to accessing or using the
|
||||
Dexorder Service, you represent and warrant to Dexorder that:
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(i) if you are entering into these Terms as an individual, then you are of legal age in the
|
||||
jurisdiction in which you reside and you have the legal capacity to enter into these Terms
|
||||
and be bound by them and if you are entering into these Terms as an entity, then you must
|
||||
have the legal authority to accept these Terms on that entity's behalf, in which case "you"
|
||||
(except as used in this paragraph) will mean that entity;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">(ii) you are not in or residing in the United States;</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(iii) you are not in or residing in Cuba, Iran, North Korea, Syria, Belarus, Russia, and the
|
||||
Crimea, Luhansk, Donetsk, Zaporizhzhia, and Kherson regions of Ukraine, or any other country
|
||||
or jurisdiction to which the Cayman Islands, the United Kingdom, United States, the United
|
||||
Nations Security Council, or the European Union embargoes goods or imposes similar sanctions
|
||||
(collectively, "<b>Restricted Territories</b>");
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(iv) you are not on any sanctions list or equivalent maintained by the Cayman Islands, the
|
||||
United Kingdom, United States, the United Nations Security Council, or the European Union
|
||||
(collectively, "<b>Sanctions Lists Persons</b>") and you do not intend to transact with any
|
||||
Restricted Person or Sanctions List Person;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(v) you do not, and will not, use VPN software or any other privacy or anonymization tools
|
||||
or techniques to circumvent, or attempt to circumvent, any restrictions that apply to the
|
||||
Dexorder Service;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(vi) you have obtained all required consents from any individual whose personal information
|
||||
you transfer to us in connection with your use of the Dexorder Service; and
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(vii) all information that you provide through the Dexorder Service is current, complete,
|
||||
true, and accurate and you will maintain the security and confidentiality of your private
|
||||
keys associated with your public wallet address, passwords, API keys, passwords or other
|
||||
information associated with your Vault or otherwise, as applicable.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(viii) your access to the Dexorder Service is not: (a) prohibited by and does not otherwise
|
||||
violate or assist you to violate any domestic or foreign law, rule, statute, regulation,
|
||||
by-law, order, protocol, code, decree, or another directive, requirement, or guideline,
|
||||
published or in force that applies to or is otherwise intended to govern or regulate any
|
||||
person, property, transaction, activity, event or other matter, including any rule, order,
|
||||
judgment, directive or other requirement or guideline issued by any domestic or foreign
|
||||
federal, provincial or state, municipal, local or other governmental, regulatory, judicial or
|
||||
administrative authority having jurisdiction over Dexorder, you, the Site or the Dexorder
|
||||
Service, or as otherwise duly enacted, enforceable by law, the common law or equity
|
||||
(collectively, "<b>Applicable Laws</b>"); or (b) contribute to or facilitate any illegal
|
||||
activity.
|
||||
</div>
|
||||
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) <u>Limitations</u>. As a condition to accessing or using the Dexorder Service or the
|
||||
Site, you acknowledge, understand, and agree to the following:
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(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
|
||||
Interactions to be executed on the Protocol, 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
|
||||
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)
|
||||
periodic maintenance procedures or repairs that Dexorder or any of its suppliers or
|
||||
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
|
||||
unavailability of underlying blockchain infrastructure; (f) unavailability of third-party
|
||||
service providers or external partners for any reason.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(ii) the Site and the Dexorder Service may evolve, which means Dexorder may apply changes,
|
||||
replace, or discontinue (temporarily or permanently) the Dexorder Service at any time in its
|
||||
sole discretion;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(iii) Dexorder does not act as an agent for you or any other user of the Site or the
|
||||
Dexorder Service;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(iv) you are solely responsible for your use of the Dexorder Service; and
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(v) we owe no fiduciary duties or liabilities to you or any other party, and that to the
|
||||
extent any such duties or liabilities may exist at law or in equity, you hereby irrevocably
|
||||
disclaim, waive, and eliminate those duties and liabilities.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">6. Interactions; Fees</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(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
|
||||
Services to process and cause Interactions to operate on the Protocol.
|
||||
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
|
||||
cryptocurrency, which is never accessible by Dexorder.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) <u>Fees</u>.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(i) Dexorder charges fees 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
|
||||
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
|
||||
income or property.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed ml-8">
|
||||
(ii) There may be associated fees in connection with transactions enacted on a blockchain.
|
||||
All transactions using blockchains require the payment of gas fees, which are essentially
|
||||
transaction fees paid on every transaction that occurs on the selected blockchain network.
|
||||
We do not collect any such fees. Please note that accessing the Protocol may result in you
|
||||
incurring gas fees, which are non-refundable, and are paid by you in all circumstances. You
|
||||
pay all gas fees incurred by you as relating to interacting with the Protocol.
|
||||
</div>
|
||||
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(c) <u>Tax Records and Reporting</u>. You are solely responsible for all costs incurred by
|
||||
you in using the Dexorder Service, and for determining, collecting, reporting, and paying
|
||||
all applicable Taxes that you may be required by law to collect and remit to any
|
||||
governmental or regulatory agencies. As used herein, "<b>Taxes</b>" means the taxes, duties,
|
||||
levies, tariffs, and other charges imposed by any federal, state, multinational or local
|
||||
governmental or regulatory authority. We reserve the right to report any activity occurring
|
||||
using the Dexorder Service to relevant tax authorities as required under Applicable Law. You
|
||||
are solely responsible for maintaining all relevant Tax records and complying with any
|
||||
reporting requirements you may have as related to our Dexorder Service. You are further
|
||||
solely responsible for independently maintaining the accuracy of any record submitted to any
|
||||
tax authority including any information derived from the Dexorder Service.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(d) Suspensions or Terminations. In addition to the other suspension and termination rights
|
||||
in these Terms, we may suspend or terminate your access to the Dexorder Service, or any and
|
||||
all Interactions, at any time in connection with any Interaction or transaction (i) as
|
||||
required by Applicable Law or any governmental authority, (ii) if we are unable to process
|
||||
or execute an Interaction or transaction after several attempts (as described in the
|
||||
Execution Policy or otherwise in Dexorder's reasonable discretion), or (iii) if we in our
|
||||
sole and reasonable discretion determine you are violating the terms of any third-party
|
||||
service provider or these Terms, including, without limitation, if we reasonably believe any
|
||||
of your representations and warranties may be untrue or inaccurate or you are violating or
|
||||
have violated any of the geographical restrictions that apply to the Dexorder Service, and
|
||||
in any case we will not be liable to you for any losses or damages you may suffer as a
|
||||
result of or in connection with the Dexorder Service being inaccessible to you at any time
|
||||
or for any reason. Such suspension or termination shall not constitute a breach of these
|
||||
Terms by Dexorder. In accordance with its anti- money laundering, anti-terrorism,
|
||||
anti-fraud, and other compliance policies and practices, we may impose limitations and
|
||||
controls on the ability of you or any beneficiary to utilize the Dexorder Service. Such
|
||||
limitations may include rejecting transaction requests, freezing funds in any case where
|
||||
Dexorder has such ability, or otherwise restricting you from using the Dexorder Service, all
|
||||
to the extent of our ability to do so.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">7. General Prohibitions and Dexorder's Enforcement Rights.</h2>
|
||||
<div className="mb-4 leading-relaxed">You agree not to do any of the following:</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(a) Engage in or induce others to engage in any form of unauthorized access, hacking, or
|
||||
social engineering, including without limitation any distributed denial or service or DDoS
|
||||
attack, of Dexorder, the Dexorder Service, or any users of the foregoing;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) Use, display, mirror or frame the Dexorder Service or any individual element within the
|
||||
Dexorder Service, Dexorder's name, any Dexorder trademark, logo or other proprietary
|
||||
information, or the layout and design of any page or form contained on a page, without
|
||||
Dexorder's express written consent;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(c) Access, tamper with, or use non-public areas of the Dexorder Service, Dexorder's
|
||||
computer systems, or the technical delivery systems of Dexorder's providers;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(d) Attempt to probe, scan or test the vulnerability of any Dexorder system or network or
|
||||
breach any security or authentication measures;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(e) Avoid, bypass, remove, deactivate, impair, descramble or otherwise circumvent any
|
||||
technological measure implemented by Dexorder or any of Dexorder's providers or any other
|
||||
third party (including another user) to protect the Dexorder Service;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(f) Attempt to access or search the Dexorder Service or download content from the Dexorder
|
||||
Service using any engine, software, tool, agent, device or mechanism (including spiders,
|
||||
robots, crawlers, data mining tools or the like) other than the software and/or search
|
||||
agents provided by Dexorder or other generally available third-party web browsers;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(g) Use any meta tags or other hidden text or metadata utilizing a Dexorder trademark, logo,
|
||||
URL or product name without Dexorder's express written consent;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(h) Forge any TCP/IP packet header or any part of the header information in any email or
|
||||
newsgroup posting, or in any way use the Dexorder Service to send altered, deceptive or
|
||||
false source-identifying information;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(i) Attempt to decipher, decompile, disassemble or reverse engineer any of the software used
|
||||
to provide the Dexorder Service;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(j) Interfere with, or attempt to interfere with, the access of any user, host or network,
|
||||
including, without limitation, sending a virus, exploiting any bug, overloading, flooding,
|
||||
spamming, or mail- bombing the Dexorder Service;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(k) Use the Dexorder Service for benchmarking or analysis in a manner that could, directly
|
||||
or indirectly, interfere with, detract from, or otherwise harm the Dexorder Service or DEX;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(l) Collect or store any personally identifiable information from the Dexorder Service from
|
||||
other users of the Dexorder Service without their express permission;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(m) Impersonate or misrepresent your affiliation with any person or entity;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(n) Create or list any counterfeit items (including digital assets);
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(o) Fabricate in any way any transaction or process related thereto;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(p) Engage or assist in any activity that violates any law, statute, ordinance, regulation,
|
||||
or sanctions program, , or that involves proceeds of any unlawful activity (including but
|
||||
not limited to money laundering, terrorist financing or deliberately engaging in activities
|
||||
designed to adversely affect the performance of the Dexorder Service);
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">(q) Engage in deceptive or manipulative trading activities;</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(r) Disguise or interfere in any way with the IP address of the computer you are using to
|
||||
access or use the Dexorder Service or that otherwise prevents us from correctly identifying
|
||||
the IP address of the computer you are using to access the Dexorder Service;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(s) Transmit, exchange, or otherwise support the direct or indirect proceeds of criminal or
|
||||
fraudulent activity;
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">(t) Violate any Applicable Law or regulation; or</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(u) Encourage or enable any other individual to do any of the foregoing.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
Dexorder is not obligated to monitor access to or use of the Dexorder Service or to review
|
||||
or edit any content. However, we have the right to do so for the purpose of operating the
|
||||
Dexorder Service, to ensure compliance with these Terms and to comply with Applicable Law or
|
||||
other legal requirements. We reserve the right, but are not obligated, to suspend or
|
||||
terminate access to the Dexorder Service at any time if we believe you are violating these
|
||||
Terms. We have the right to investigate violations of these Terms or conduct that affects
|
||||
the Dexorder Service. We may also consult and cooperate with law enforcement authorities to
|
||||
prosecute users who violate the law.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">8. Feedback</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
We appreciate feedback, comments, ideas, proposals and suggestions for improvements to the
|
||||
Dexorder Service ("<b>Feedback</b>"). If you choose to submit Feedback, you agree that we
|
||||
are free to use it (and permit others to use it) without any restriction or compensation to
|
||||
you.
|
||||
</div>
|
||||
|
||||
<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">
|
||||
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
|
||||
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
|
||||
acknowledge sole responsibility for and assume all risk arising from, your use of any
|
||||
third-party resources. Our provision of access to Third Party Resources does not constitute
|
||||
approval, endorsement, or control of such Third Party Resource.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">10. Termination</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
We may suspend or terminate your access to and use of the Dexorder Service, at our sole
|
||||
discretion, at any time and without notice to you. You acknowledge and agree that we shall
|
||||
have no liability or obligation to you in such event and that you will not be entitled to a
|
||||
refund of any amounts that you have already paid to us or any third party, to the fullest
|
||||
extent permitted by Applicable Law. Upon any termination, discontinuation, or cancellation
|
||||
of the Dexorder Service or your account, the following Sections will survive: 6.(d) , 7 , 8
|
||||
, 10 , 11 , 13 , 14 , 15 , 16 , and 17 .
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">11. Warranty Disclaimers</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
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
|
||||
BLOCKCHAIN MESSAGING FUNCTIONALITY UNDERLYING THE DEXORDER
|
||||
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
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT AND NON-INFRINGEMENT, AND
|
||||
ANY WARRANTIES ARISING OUT OF COURSE OF DEALING OR USAGE OF TRADE. WE MAKE NO WARRANTY THAT
|
||||
THE UTILITIES WILL MEET YOUR REQUIREMENTS, BE AVAILABLE ON AN UNINTERRUPTED, SECURE, OR
|
||||
ERROR-FREE BASIS. WE MAKE NO WARRANTY REGARDING THE QUALITY, ACCURACY, TIMELINESS,
|
||||
TRUTHFULNESS, COMPLETENESS OR RELIABILITY OF ANY INFORMATION OR CONTENT ON THE UTILITIES.
|
||||
DEXORDER FURTHER EXPRESSLY DISCLAIMS ALL LIABILITY OR RESPONSIBILITY IN CONNECTION WITH
|
||||
THIRD PARTY SERVICES. NOTHING HEREIN NOR ANY USE OF THE UTILITIES IN CONNECTION WITH THIRD
|
||||
PARTY SERVICES CONSTITUTES OUR ENDORSEMENT, RECOMMENDATION OR ANY OTHER AFFILIATION OF OR
|
||||
WITH ANY THIRD PARTY SERVICES.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
DEXORDER DOES NOT REPRESENT OR WARRANT THAT ANY CONTENT ON THE UTILITIES IS ACCURATE,
|
||||
COMPLETE, RELIABLE, CURRENT OR ERROR-FREE. WE WILL NOT BE LIABLE FOR ANY LOSS OF ANY KIND
|
||||
FROM ANY ACTION TAKEN OR TAKEN IN RELIANCE ON MATERIAL OR INFORMATION CONTAINED ON THE
|
||||
UTILITIES. DEXORDER CANNOT AND DOES NOT REPRESENT OR WARRANT THAT THE UTILITIES, ANY CONTENT
|
||||
THEREIN, OR OUR SERVERS ARE FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
WE CANNOT GUARANTEE THE SECURITY OF ANY DATA THAT YOU DISCLOSE ONLINE. YOU ACCEPT THE
|
||||
INHERENT SECURITY RISKS OF PROVIDING INFORMATION AND DEALING ONLINE OVER THE INTERNET AND
|
||||
WILL NOT HOLD US RESPONSIBLE FOR ANY BREACH OF SECURITY.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
DEXORDER WILL NOT BE RESPONSIBLE OR LIABLE TO YOU FOR ANY LOSS AND TAKES NO RESPONSIBILITY
|
||||
FOR, AND WILL NOT BE LIABLE TO YOU FOR, ANY USE OF THE UTILITIES, INCLUDING BUT NOT LIMITED
|
||||
TO ANY LOSSES, DAMAGES OR CLAIMS ARISING FROM: (I) USER ERROR SUCH AS FORGOTTEN PASSWORDS,
|
||||
INCORRECTLY CONSTRUCTED TRANSACTIONS, EXCEEDING TRANSFER LIMITS OF THIRD PARTY RESOURCES OR
|
||||
THE DEX, OR MISTYPED WALLET ADDRESSES; (II) SERVER FAILURE OR DATA LOSS; (III) BLOCKCHAIN
|
||||
NETWORKS, CRYPTOCURRENCY WALLETS, CORRUPT FILES, SOFTWARE ERRORS, OR BUGS; (IV) UNAUTHORIZED
|
||||
ACCESS TO THE UTILITIES; OR (V) ANY THIRD PARTY UTILITIES, INCLUDING WITHOUT LIMITATION THE
|
||||
USE OF VIRUSES, PHISHING, BRUTEFORCING OR OTHER MEANS OF ATTACK AGAINST ANY BLOCKCHAIN
|
||||
NETWORK UNDERLYING THE UTILITIES.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
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,
|
||||
VIRUSES, DENIAL OF SERVICE OR OTHER ATTACKS, TECHNICAL FAILURE OF THE UTILITIES AND/OR
|
||||
TELECOMMUNICATIONS INFRASTRUCTURE OR DISRUPTION, AND THEREFORE WE EXPRESSLY DISCLAIM ANY
|
||||
EXPRESS OR IMPLIED WARRANTY REGARDING THE USE AND/OR AVAILABILITY, ACCESSIBILITY, SECURITY
|
||||
OR PERFORMANCE OF THE UTILITIES CAUSED BY SUCH FACTORS. WE DO NOT MAKE ANY REPRESENTATIONS
|
||||
OR WARRANTIES AGAINST THE POSSIBILITY OF DELETION, MISDELIVERY OR FAILURE TO STORE
|
||||
COMMUNICATIONS, PERSONALIZED SETTINGS OR OTHER DATA. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||
EXCLUSION OF CERTAIN WARRANTIES. ACCORDINGLY, SOME OF THE ABOVE DISCLAIMERS OF WARRANTIES
|
||||
MAY NOT APPLY TO YOU BUT OTHERS REMAIN IN EFFECT.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
You understand that your use of the Utilities is entirely at your own risk. You assume all
|
||||
risks associated with using the Utilities, and digital assets and decentralized systems
|
||||
generally, including but not limited to, that digital assets are highly volatile; you may
|
||||
not have ready access to assets; and you may lose some or all of your tokens or other
|
||||
assets, including with respect to an Interaction or the Vault. You agree that you will have
|
||||
no recourse against Dexorder for any losses due to your use of the Utilities. For example,
|
||||
these losses may arise from or relate to: (i) lost funds; (ii) server failure or data loss;
|
||||
(iii) corrupted digital wallet files; (iv) unauthorized access; (v) errors, mistakes, or
|
||||
inaccuracies; or (vi) third-party activities.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">12. Assumption of Risk.</h2>
|
||||
<div className="mb-4 leading-relaxed">You accept, acknowledge and assume the following risks:</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(a) You are solely responsible for determining what, if any, Taxes apply to your
|
||||
transactions through the Utilities. Neither Dexorder nor any Dexorder affiliates are
|
||||
responsible for determining the Taxes that apply to such transactions.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) A lack of use or public interest in the creation and development of distributed
|
||||
ecosystems could negatively impact the development of those ecosystems and related
|
||||
applications, and could therefore also negatively impact the potential utility or value of
|
||||
certain digital assets.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(c) By accessing and using the Utilities, you represent that you understand the inherent
|
||||
risks associated with using cryptographic and blockchain-based systems, and that you have a
|
||||
working knowledge of the usage and intricacies of tokens such as, bitcoin (BTC), ether
|
||||
(ETH), and other digital tokens such as those following the Ethereum Token Standard
|
||||
(ERC-20). You further understand that the markets for tokens can be highly volatile due to
|
||||
factors including (but not limited to) adoption, speculation, technology, security, and
|
||||
regulation. You acknowledge that the cost and speed of transacting with cryptographic and
|
||||
blockchain-based systems are variable and may increase at any time. Accordingly, you
|
||||
understand and agree to assume full responsibility for all of the risks of accessing and
|
||||
using and engaging with the Utilities.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">13. Indemnity</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
You will indemnify, defend (at Dexorder's option) and hold Dexorder and its affiliates and
|
||||
their respective officers, directors, employees and agents, harmless from and against any
|
||||
claims, disputes, demands, liabilities, damages, losses, and costs and expenses, including,
|
||||
without limitation, reasonable legal and accounting fees arising out of or in any way
|
||||
connected with: (a) your access to or use of the Utilities, (b) Interactions and the Vault,
|
||||
(c) your violation of these Terms, or (d) your negligence, willful misconduct, fraud, or
|
||||
violation of Applicable Laws. You may not settle or otherwise compromise any claim subject
|
||||
to this Section without Dexorder's prior written approval.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">14. Limitation of Liability</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(a) TO THE MAXIMUM EXTENT PERMITTED BY LAW, NEITHER DEXORDER NOR ITS SERVICE PROVIDERS
|
||||
INVOLVED IN CREATING, PRODUCING, OR DELIVERING THE UTILITIES WILL BE LIABLE FOR ANY
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES, OR DAMAGES FOR LOST PROFITS, LOST
|
||||
REVENUES, LOST SAVINGS, LOST BUSINESS OPPORTUNITY, LOSS OF DATA OR GOODWILL, SERVICE
|
||||
INTERRUPTION, COMPUTER DAMAGE OR SYSTEM FAILURE OR THE COST OF SUBSTITUTE SERVICES OF ANY
|
||||
KIND ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR FROM THE USE OF OR INABILITY TO USE
|
||||
THE UTILITIES, WHETHER BASED ON WARRANTY, CONTRACT, TORT (INCLUDING NEGLIGENCE), PRODUCT
|
||||
LIABILITY OR ANY OTHER LEGAL THEORY, AND WHETHER OR NOT DEXORDER OR ITS SERVICE PROVIDERS
|
||||
HAS BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGE, EVEN IF A LIMITED REMEDY SET FORTH
|
||||
HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) TO THE MAXIMUM EXTENT PERMITTED BY THE LAW, IN NO EVENT WILL DEXORDER'S TOTAL LIABILITY
|
||||
ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR FROM THE USE OF OR INABILITY TO USE THE
|
||||
UTILITIES EXCEED THE TOTAL FEES YOU HAVE PAID OR ARE PAYABLE BY YOU TO DEXORDER FOR USE OF
|
||||
THE UTILITIES (EXCLUDING GAS FEES), OR ONE HUNDRED DOLLARS ($100) IF YOU HAVE NOT HAD ANY
|
||||
PAYMENT OBLIGATIONS TO DEXORDER, AS APPLICABLE.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(c) THE EXCLUSIONS AND LIMITATIONS OF DAMAGES SET FORTH ABOVE ARE FUNDAMENTAL ELEMENTS OF
|
||||
THE BASIS OF THE BARGAIN BETWEEN DEXORDER AND YOU.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">15. Governing Law and Forum Choice</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
These Terms will be governed by and construed in accordance with the laws of the British
|
||||
Virgin Islands without regard to its conflict of laws provisions.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">16. Dispute Resolution</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(a) <u>Mandatory Arbitration of Disputes</u>. We each agree that any dispute, claim or
|
||||
controversy arising out of or relating to these Terms or the breach, termination,
|
||||
enforcement, interpretation or validity thereof or the use of the Utilities (collectively, "
|
||||
<b>Disputes</b>") will be resolved{' '}
|
||||
<b>
|
||||
solely by binding, individual arbitration and not in a class, representative or
|
||||
consolidated action or proceeding.
|
||||
</b>{' '}
|
||||
You and Dexorder agree that the Cayman Islands Arbitration Law governs the interpretation
|
||||
and enforcement of these Terms, and that you and Dexorder are each waiving the right to a
|
||||
trial by jury or to participate in a class action. This arbitration provision shall survive
|
||||
termination of these Terms.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) <u>Exceptions</u>. As limited exceptions to Section 16.(a) above: (i) each party may
|
||||
seek to resolve a Dispute in small claims court if it qualifies; and (ii) each party retains
|
||||
the right to seek injunctive or other equitable relief from a court to prevent (or enjoin)
|
||||
the infringement or misappropriation of our intellectual property rights.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(c) <u>Conducting Arbitration and Arbitration Rules</u>. The arbitration will be conducted
|
||||
by the Cayman International Mediation & Arbitration Centre (CI-MAC) in accordance with
|
||||
its arbitration rules in force at the time of the dispute ("<b>CI-MAC Rules</b>"), except as
|
||||
modified by these Terms. The CI-MAC Rules are available at{' '}
|
||||
<a href="https://www.caymanarbitration.com/arbitrationrules2023">
|
||||
https://www.caymanarbitration.com/arbitrationrules2023
|
||||
</a>
|
||||
. A party who wishes to start arbitration must submit a written request for arbitration to
|
||||
CI-MAC and give notice to the other party as specified in the CI-MAC Rules. CI-MAC provides
|
||||
instructions on submitting a request for arbitration under Section 3 (Request for
|
||||
arbitration) of the CI-MAC Rules.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
Any arbitration hearings will take place in the county (or parish) where you live, unless
|
||||
the parties agree to a different location. The parties agree that the arbitrator shall have
|
||||
exclusive authority to decide all issues relating to the interpretation, applicability,
|
||||
enforceability and scope of this arbitration agreement.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(d) <u>Arbitration Costs</u>. Payment of all filing, administration and arbitrator fees will
|
||||
be governed by the JAMS Rules, and we won't seek to recover the administration and
|
||||
arbitrator fees we are responsible for paying, unless the arbitrator finds your Dispute
|
||||
frivolous. If we prevail in arbitration we'll pay all of our attorneys' fees and costs and
|
||||
won't seek to recover them from you. If you prevail in arbitration you will be entitled to
|
||||
an award of attorneys' fees and expenses to the extent provided under Applicable Law.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(e) <u>Injunctive and Declaratory Relief</u>. Except as provided in Section 16.(b) above,
|
||||
the arbitrator shall determine all issues of liability on the merits of any claim asserted
|
||||
by either party and may award declaratory or injunctive relief only in favor of the
|
||||
individual party seeking relief and only to the extent necessary to provide relief warranted
|
||||
by that party's individual claim. To the extent that you or we prevail on a claim and seek
|
||||
public injunctive relief (that is, injunctive relief that has the primary purpose and effect
|
||||
of prohibiting unlawful acts that threaten future injury to the public), the entitlement to
|
||||
and extent of such relief must be litigated in a civil court of competent jurisdiction and
|
||||
not in arbitration. The parties agree that litigation of any issues of public injunctive
|
||||
relief shall be stayed pending the outcome of the merits of any individual claims in
|
||||
arbitration.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(f) <u>Class Action Waiver</u>.{' '}
|
||||
<b>
|
||||
YOU AND DEXORDER AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS
|
||||
INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR
|
||||
REPRESENTATIVE PROCEEDING.
|
||||
</b>{' '}
|
||||
Further, if the parties' Dispute is resolved through arbitration, the arbitrator may not
|
||||
consolidate another person's claims with your claims, and may not otherwise preside over any
|
||||
form of a representative or class proceeding. If this specific provision is found to be
|
||||
unenforceable, then the entirety of this Dispute Resolution section shall be null and void.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(g) <u>Severability</u>. With the exception of any of the provisions in Section 16.(f) of
|
||||
these Terms ("<b>Class Action Waiver</b>"), if an arbitrator or court of competent
|
||||
jurisdiction decides that any part of these Terms is invalid or unenforceable, the other
|
||||
parts of these Terms will still apply.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">17. General Terms</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(a) <u>Reservation of Rights</u>. Dexorder and its licensors exclusively own all right,
|
||||
title and interest in and to the Dexorder Service, including all associated intellectual
|
||||
property rights. You acknowledge that the Dexorder Service is protected by copyright,
|
||||
trademark, and other laws of the United States and foreign countries. You agree not to
|
||||
remove, alter or obscure any copyright, trademark, service mark or other proprietary rights
|
||||
notices incorporated in or accompanying the Dexorder Service.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(b) <u>Entire Agreement</u>. These Terms, including any addendum terms, constitute the
|
||||
entire and exclusive understanding and agreement between Dexorder and you regarding the
|
||||
Dexorder Service, and these Terms supersede and replace all prior oral or written
|
||||
understandings or agreements between Dexorder and you regarding the Dexorder Service. If any
|
||||
provision of these Terms is held invalid or unenforceable by an arbitrator or a court of
|
||||
competent jurisdiction, that provision will be enforced to the maximum extent permissible
|
||||
and the other provisions of these Terms will remain in full force and effect. Except where
|
||||
provided by Applicable Law in your jurisdiction, you may not assign or transfer these Terms,
|
||||
by operation of law or otherwise, without Dexorder's prior written consent. Any attempt by
|
||||
you to assign or transfer these Terms absent our consent or your statutory right, will be
|
||||
null. Dexorder may freely assign or transfer these Terms without restriction. Subject to the
|
||||
foregoing, these Terms will bind and inure to the benefit of the parties, their successors
|
||||
and permitted assigns.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(c) <u>Notices</u>. Any notices or other communications provided by Dexorder under these
|
||||
Terms will be given: (i) via email; or (ii) by posting to the Dexorder Service. For notices
|
||||
made by email, the date of receipt will be deemed the date on which such notice is
|
||||
transmitted.
|
||||
</div>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
(d) <u>Waiver of Rights</u>. Dexorder's failure to enforce any right or provision of these
|
||||
Terms will not be considered a waiver of such right or provision. The waiver of any such
|
||||
right or provision will be effective only if in writing and signed by a duly authorized
|
||||
representative of Dexorder. Except as expressly set forth in these Terms, the exercise by
|
||||
either party of any of its remedies under these Terms will be without prejudice to its other
|
||||
remedies under these Terms or otherwise.
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-semibold mt-6 mb-4">18. Contact Information</h2>
|
||||
<div className="mb-4 leading-relaxed">
|
||||
If you have any questions about these Terms or the Dexorder Service, please contact Dexorder
|
||||
at <a href="mailto:legal@dexorder.com">legal@dexorder.com</a> or{' '}
|
||||
<a href="mailto:support@dexorder.com">support@dexorder.com</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
248
src/components/uniswap-quote.tsx
Normal file
248
src/components/uniswap-quote.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
@@ -1,379 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CheckCircle, XCircle, Loader2, Trash2 } from 'lucide-react';
|
||||
import { useAccount, usePublicClient } from 'wagmi';
|
||||
import { useGetAllPools, useLPTokenBalance, type PoolDetails } from '@/hooks/usePartyPlanner';
|
||||
import { useBurn, type ActualBurnAmounts } from '@/hooks/usePartyPool';
|
||||
import { formatUnits, parseUnits } from 'viem';
|
||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||
import { ERC20ABI } from '@/contracts/ERC20ABI';
|
||||
|
||||
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
||||
|
||||
interface PoolWithBalance extends PoolDetails {
|
||||
lpBalance: bigint;
|
||||
}
|
||||
|
||||
interface TokenInfo {
|
||||
address: `0x${string}`;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export function UnstakeBasketForm() {
|
||||
const { t } = useTranslation();
|
||||
const { isConnected, address } = useAccount();
|
||||
const publicClient = usePublicClient();
|
||||
const [selectedPools, setSelectedPools] = useState<Set<string>>(new Set());
|
||||
const [poolsWithBalances, setPoolsWithBalances] = useState<PoolWithBalance[]>([]);
|
||||
const [transactionStatus, setTransactionStatus] = useState<TransactionStatus>('idle');
|
||||
const [transactionError, setTransactionError] = useState<string | null>(null);
|
||||
const [actualBurnResults, setActualBurnResults] = useState<{[key: string]: ActualBurnAmounts}>({});
|
||||
const [poolTokens, setPoolTokens] = useState<{[key: string]: TokenInfo[]}>({});
|
||||
|
||||
// Fetch all pools using the hook
|
||||
const { poolDetails, loading: poolsLoading } = useGetAllPools();
|
||||
|
||||
// Initialize burn hook
|
||||
const { executeBurn, isBurning } = useBurn();
|
||||
|
||||
// Fetch LP balances for all pools
|
||||
useEffect(() => {
|
||||
if (!poolDetails || !address || !publicClient) return;
|
||||
|
||||
const fetchBalances = async () => {
|
||||
const poolsWithBal: PoolWithBalance[] = [];
|
||||
|
||||
for (const pool of poolDetails) {
|
||||
try {
|
||||
const balance = await publicClient.readContract({
|
||||
address: pool.address,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'balanceOf',
|
||||
args: [address],
|
||||
}) as bigint;
|
||||
|
||||
if (balance > 0n) {
|
||||
poolsWithBal.push({
|
||||
...pool,
|
||||
lpBalance: balance,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error fetching balance for pool ${pool.address}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
setPoolsWithBalances(poolsWithBal);
|
||||
};
|
||||
|
||||
fetchBalances();
|
||||
}, [poolDetails, address, publicClient]);
|
||||
|
||||
// Fetch token details for selected pools
|
||||
useEffect(() => {
|
||||
if (!publicClient || selectedPools.size === 0) return;
|
||||
|
||||
const fetchTokenDetails = async () => {
|
||||
const tokenInfoMap: {[key: string]: TokenInfo[]} = {};
|
||||
|
||||
for (const poolAddress of Array.from(selectedPools)) {
|
||||
const pool = poolsWithBalances.find(p => p.address === poolAddress);
|
||||
if (!pool) continue;
|
||||
|
||||
const tokenInfos: TokenInfo[] = [];
|
||||
|
||||
for (const tokenAddress of pool.tokens) {
|
||||
try {
|
||||
const [symbol, decimals] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
address: tokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'symbol',
|
||||
}) as Promise<string>,
|
||||
publicClient.readContract({
|
||||
address: tokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'decimals',
|
||||
}) as Promise<number>,
|
||||
]);
|
||||
|
||||
tokenInfos.push({
|
||||
address: tokenAddress as `0x${string}`,
|
||||
symbol,
|
||||
decimals,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error fetching token details for ${tokenAddress}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
tokenInfoMap[poolAddress] = tokenInfos;
|
||||
}
|
||||
|
||||
setPoolTokens(tokenInfoMap);
|
||||
};
|
||||
|
||||
fetchTokenDetails();
|
||||
}, [publicClient, selectedPools, poolsWithBalances]);
|
||||
|
||||
const togglePoolSelection = (poolAddress: string) => {
|
||||
const newSelected = new Set(selectedPools);
|
||||
if (newSelected.has(poolAddress)) {
|
||||
newSelected.delete(poolAddress);
|
||||
} else {
|
||||
newSelected.add(poolAddress);
|
||||
}
|
||||
setSelectedPools(newSelected);
|
||||
};
|
||||
|
||||
const handleBurnAll = async () => {
|
||||
if (selectedPools.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTransactionStatus('pending');
|
||||
setTransactionError(null);
|
||||
setActualBurnResults({});
|
||||
|
||||
try {
|
||||
const results: {[key: string]: ActualBurnAmounts} = {};
|
||||
|
||||
// Execute burn for each selected pool
|
||||
for (const poolAddress of Array.from(selectedPools)) {
|
||||
const pool = poolsWithBalances.find(p => p.address === poolAddress);
|
||||
if (!pool) continue;
|
||||
|
||||
console.log(`Burning LP tokens for pool ${pool.symbol}...`);
|
||||
|
||||
// Execute the burn transaction
|
||||
const result = await executeBurn(
|
||||
pool.address,
|
||||
pool.lpBalance,
|
||||
false // unwrap = false by default
|
||||
);
|
||||
|
||||
// Store actual burn amounts if available
|
||||
if (result?.actualBurnAmounts) {
|
||||
results[poolAddress] = result.actualBurnAmounts;
|
||||
}
|
||||
}
|
||||
|
||||
setActualBurnResults(results);
|
||||
setTransactionStatus('success');
|
||||
} catch (err) {
|
||||
console.error('Burn failed:', err);
|
||||
setTransactionError(err instanceof Error ? err.message : 'Transaction failed');
|
||||
setTransactionStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
if (transactionStatus === 'success') {
|
||||
// Clear selections and refresh
|
||||
setSelectedPools(new Set());
|
||||
// Refresh pool balances
|
||||
window.location.reload();
|
||||
}
|
||||
setTransactionStatus('idle');
|
||||
setTransactionError(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-2xl mx-auto relative">
|
||||
<CardHeader>
|
||||
<CardTitle>Unstake Basket</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Select pools to burn all LP tokens and receive all underlying tokens proportionally
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Pool Selection List */}
|
||||
{poolsLoading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
) : poolsWithBalances.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<p>No pools with LP token balance found.</p>
|
||||
<p className="text-sm">Stake some tokens first to use this feature.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{poolsWithBalances.map((pool) => (
|
||||
<div
|
||||
key={pool.address}
|
||||
className={`p-4 border rounded-lg cursor-pointer transition-colors ${
|
||||
selectedPools.has(pool.address)
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-primary/50'
|
||||
}`}
|
||||
onClick={() => togglePoolSelection(pool.address)}
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedPools.has(pool.address)}
|
||||
onChange={() => {}}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium">{pool.symbol}</div>
|
||||
<div className="text-xs text-muted-foreground">{pool.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-medium">
|
||||
{formatUnits(pool.lpBalance, 18)} LP
|
||||
</div>
|
||||
{poolTokens[pool.address] && (
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{poolTokens[pool.address].length} tokens
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedPools.has(pool.address) && poolTokens[pool.address] && (
|
||||
<div className="mt-3 pt-3 border-t border-border">
|
||||
<div className="text-xs text-muted-foreground mb-2">You will receive:</div>
|
||||
<div className="space-y-1">
|
||||
{poolTokens[pool.address].map((token) => (
|
||||
<div key={token.address} className="text-xs flex justify-between">
|
||||
<span>{token.symbol}</span>
|
||||
<span className="text-muted-foreground">
|
||||
(proportional to pool composition)
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Burn Button */}
|
||||
<Button
|
||||
className="w-full h-14 text-lg"
|
||||
onClick={handleBurnAll}
|
||||
disabled={!isConnected || selectedPools.size === 0 || isBurning}
|
||||
>
|
||||
{!isConnected
|
||||
? 'Connect Wallet'
|
||||
: isBurning
|
||||
? 'Burning...'
|
||||
: `Burn ${selectedPools.size} Pool${selectedPools.size !== 1 ? 's' : ''}`}
|
||||
</Button>
|
||||
|
||||
{selectedPools.size > 0 && (
|
||||
<div className="text-xs text-center text-muted-foreground">
|
||||
This will burn all LP tokens from {selectedPools.size} selected pool{selectedPools.size !== 1 ? 's' : ''}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
{/* Transaction Modal Overlay */}
|
||||
{transactionStatus !== 'idle' && (
|
||||
<div className="absolute inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center z-50 rounded-lg">
|
||||
<div className="bg-card border rounded-lg p-8 max-w-md w-full mx-4 shadow-lg max-h-[80vh] overflow-y-auto">
|
||||
{transactionStatus === 'pending' && (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<Loader2 className="h-16 w-16 animate-spin text-primary" />
|
||||
<h3 className="text-xl font-semibold text-center">
|
||||
Burning LP Tokens
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Burning LP tokens from {selectedPools.size} pool{selectedPools.size !== 1 ? 's' : ''}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
Please confirm the transactions in your wallet
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{transactionStatus === 'success' && (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<CheckCircle className="h-16 w-16 text-green-500" />
|
||||
<h3 className="text-xl font-semibold text-center">
|
||||
Burn Confirmed!
|
||||
</h3>
|
||||
|
||||
<div className="w-full space-y-4">
|
||||
{Object.entries(actualBurnResults).map(([poolAddress, burnAmounts]) => {
|
||||
const pool = poolsWithBalances.find(p => p.address === poolAddress);
|
||||
const tokens = poolTokens[poolAddress];
|
||||
|
||||
if (!pool || !tokens) return null;
|
||||
|
||||
return (
|
||||
<div key={poolAddress} className="border rounded-lg p-4">
|
||||
<div className="font-medium mb-2">{pool.symbol}</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">LP Burned:</span>
|
||||
<span className="font-medium">
|
||||
{formatUnits(burnAmounts.lpBurned, 18)} {pool.symbol}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground mb-1">Tokens Received:</div>
|
||||
{burnAmounts.withdrawAmounts.map((amount, index) => {
|
||||
const token = tokens[index];
|
||||
if (!token) return null;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex justify-between text-sm pl-2">
|
||||
<span className="text-muted-foreground">{token.symbol}:</span>
|
||||
<span className="font-medium">
|
||||
{formatUnits(amount, token.decimals)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleCloseModal}
|
||||
className="w-full mt-4"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{transactionStatus === 'error' && (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<XCircle className="h-16 w-16 text-destructive" />
|
||||
<h3 className="text-xl font-semibold text-center">
|
||||
Burn Failed
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground text-center break-words">
|
||||
{transactionError || 'Transaction failed'}
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleCloseModal}
|
||||
variant="outline"
|
||||
className="w-full mt-4"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
296
src/contracts/IPartyInfoABI.ts
Normal file
296
src/contracts/IPartyInfoABI.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
/* GENERATED FILE: DO NOT EDIT! */
|
||||
|
||||
const IPartyInfoABI = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "burnAmounts",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "lpTokenAmount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "withdrawAmounts",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "burnSwapAmounts",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "lpAmount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "flashFee",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "fee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "maxFlashLoan",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "mintAmounts",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "lpTokenAmount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "depositAmounts",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "poolPrice",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "price",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapMintAmounts",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "inputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "maxAmountIn",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "amountInUsed",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpMinted",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapToLimitAmounts",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "inputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "limitPrice",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "amountIn",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "working",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
}
|
||||
] as const;
|
||||
|
||||
export default IPartyInfoABI;
|
||||
@@ -110,6 +110,85 @@ const IPartyPlannerABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "newPool",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "tokens",
|
||||
"type": "address[]",
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "kappa",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "swapFeesPpm",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "flashFeePpm",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "stable",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "initialDeposits",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "initialLpAmount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "deadline",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "lpAmount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "newPool",
|
||||
|
||||
@@ -324,25 +324,6 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "getToken",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract IERC20"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "initialMint",
|
||||
@@ -434,6 +415,19 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "payable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "mintImpl",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "name",
|
||||
@@ -515,6 +509,11 @@ const IPartyPoolABI = [
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "fundingSelector",
|
||||
"type": "bytes4",
|
||||
"internalType": "bytes4"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
@@ -549,6 +548,11 @@ const IPartyPoolABI = [
|
||||
"name": "unwrap",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "cbData",
|
||||
"type": "bytes",
|
||||
"internalType": "bytes"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -614,6 +618,19 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapImpl",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapMint",
|
||||
@@ -672,6 +689,11 @@ const IPartyPoolABI = [
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "fundingSelector",
|
||||
"type": "bytes4",
|
||||
"internalType": "bytes4"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
@@ -701,6 +723,11 @@ const IPartyPoolABI = [
|
||||
"name": "unwrap",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "cbData",
|
||||
"type": "bytes",
|
||||
"internalType": "bytes"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -735,6 +762,25 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "token",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract IERC20"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "totalSupply",
|
||||
|
||||
@@ -6,8 +6,22 @@ import chainInfo from '@/contracts/liqp-deployments.json';
|
||||
import IPartyPlannerABI from '@/contracts/IPartyPlannerABI';
|
||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
|
||||
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
|
||||
import { ERC20ABI } from '@/contracts/ERC20ABI';
|
||||
|
||||
// Helper function to format large numbers with K, M, B suffixes
|
||||
function formatTVL(value: number): string {
|
||||
if (value >= 1_000_000_000) {
|
||||
return `$${(value / 1_000_000_000).toFixed(2)}B`;
|
||||
} else if (value >= 1_000_000) {
|
||||
return `$${(value / 1_000_000).toFixed(2)}M`;
|
||||
} else if (value >= 1_000) {
|
||||
return `$${(value / 1_000).toFixed(2)}K`;
|
||||
} else {
|
||||
return `$${value.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function useGetAllTokens(offset: number = 0, limit: number = 100) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -35,7 +49,8 @@ export function useGetAllTokens(offset: number = 0, limit: number = 100) {
|
||||
|
||||
// Get chain ID and contract address
|
||||
const chainId = await publicClient.getChainId();
|
||||
const address = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyPoolViewer: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
// @ts-ignore
|
||||
const address = (chainInfo as Record<string, { v1: { PartyPlanner: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
|
||||
if (!address) {
|
||||
setError('IPartyPlanner contract not found for current chain');
|
||||
@@ -84,6 +99,8 @@ export interface SwapRoute {
|
||||
poolAddress: `0x${string}`;
|
||||
inputTokenIndex: number;
|
||||
outputTokenIndex: number;
|
||||
inputTokenDecimal: number;
|
||||
outputTokenDecimal: number;
|
||||
}
|
||||
|
||||
export interface AvailableToken {
|
||||
@@ -120,19 +137,20 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and contract address
|
||||
// Get chain ID and contract addresses
|
||||
const chainId = await publicClient.getChainId();
|
||||
const address = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyPoolViewer: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
const plannerAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!address) {
|
||||
setError('IPartyPlanner contract not found for current chain');
|
||||
if (!plannerAddress || !partyInfoAddress) {
|
||||
setError('IPartyPlanner or PartyInfo contract not found for current chain');
|
||||
setAvailableTokens([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call getPoolsByToken function
|
||||
const poolsResult = await publicClient.readContract({
|
||||
address: address as `0x${string}`,
|
||||
address: plannerAddress as `0x${string}`,
|
||||
abi: IPartyPlannerABI,
|
||||
functionName: 'getPoolsByToken',
|
||||
args: [tokenAddress, BigInt(offset), BigInt(limit)],
|
||||
@@ -150,68 +168,161 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter pools to only working ones
|
||||
const workingPools: `0x${string}`[] = [];
|
||||
for (const poolAddress of poolsResult) {
|
||||
try {
|
||||
const isWorking = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'working',
|
||||
args: [poolAddress],
|
||||
}) as boolean;
|
||||
|
||||
if (isWorking) {
|
||||
workingPools.push(poolAddress);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error checking if pool ${poolAddress} is working:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
// If no working pools found, set error message
|
||||
if (workingPools.length === 0 && poolsResult.length > 0) {
|
||||
setError('This token is no longer supported. All pools containing this token are currently inactive.');
|
||||
setAvailableTokens([]);
|
||||
return;
|
||||
} else if (workingPools.length === 0) {
|
||||
setError('No pools found for this token.');
|
||||
setAvailableTokens([]);
|
||||
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
|
||||
const tokenRoutesMap = new Map<string, AvailableToken>();
|
||||
|
||||
// For each pool, fetch all tokens and track indices
|
||||
for (const poolAddress of poolsResult) {
|
||||
try {
|
||||
const tokensInPool = await publicClient.readContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'allTokens',
|
||||
}) as readonly `0x${string}`[];
|
||||
// For each working pool, process tokens
|
||||
for (let poolIdx = 0; poolIdx < workingPools.length; poolIdx++) {
|
||||
const poolAddress = workingPools[poolIdx];
|
||||
const poolTokensResult = poolTokensResults[poolIdx];
|
||||
|
||||
// Find the input token index in this pool
|
||||
const inputTokenIndex = tokensInPool.findIndex(
|
||||
(token) => token.toLowerCase() === tokenAddress.toLowerCase()
|
||||
);
|
||||
if (poolTokensResult.status !== 'success') {
|
||||
console.error('Failed to fetch tokens for pool', poolAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inputTokenIndex === -1) {
|
||||
console.error('Input token not found in pool', poolAddress);
|
||||
const tokensInPool = poolTokensResult.result as readonly `0x${string}`[];
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Process each token in the pool
|
||||
for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) {
|
||||
const outputTokenAddress = tokensInPool[outputTokenIndex];
|
||||
const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase());
|
||||
const outputTokenSymbol = outputTokenData?.symbol ?? null;
|
||||
const outputTokenDecimal = outputTokenData?.decimals ?? null;
|
||||
|
||||
// Skip if it's the same as the input token
|
||||
if (outputTokenIndex === inputTokenIndex) {
|
||||
continue;
|
||||
}
|
||||
// Skip tokens with the same symbol as the selected token
|
||||
if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the symbol of this token
|
||||
const outputTokenSymbol = await publicClient.readContract({
|
||||
// Skip tokens if decimals failed to load
|
||||
if (inputTokenDecimal === null || outputTokenDecimal === null) {
|
||||
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,
|
||||
abi: ERC20ABI,
|
||||
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,
|
||||
symbol: outputTokenSymbol,
|
||||
swapRoutes: [],
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching tokens from pool', poolAddress, err);
|
||||
|
||||
// Add this swap route
|
||||
tokenRoutesMap.get(tokenKey)!.swapRoutes.push({
|
||||
poolAddress,
|
||||
inputTokenIndex,
|
||||
outputTokenIndex,
|
||||
inputTokenDecimal,
|
||||
outputTokenDecimal,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,55 +369,54 @@ export function useTokenDetails(userAddress: `0x${string}` | undefined) {
|
||||
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[] = [];
|
||||
|
||||
// Make individual calls for each token
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const tokenAddress = tokens[i];
|
||||
try {
|
||||
const [name, symbol, decimals, balance] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
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)),
|
||||
]);
|
||||
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({
|
||||
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,
|
||||
});
|
||||
}
|
||||
details.push({
|
||||
address: tokens[i],
|
||||
name: nameResult.status === 'success' ? (nameResult.result as string) : 'Unknown',
|
||||
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : '???',
|
||||
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18,
|
||||
balance: balanceResult.status === 'success' ? (balanceResult.result as bigint) : BigInt(0),
|
||||
index: i,
|
||||
});
|
||||
}
|
||||
|
||||
setTokenDetails(details);
|
||||
@@ -332,6 +442,9 @@ export interface PoolDetails {
|
||||
name: string;
|
||||
symbol: string;
|
||||
tokens: readonly `0x${string}`[];
|
||||
price?: string; // Formatted price string
|
||||
tvl?: string; // Formatted TVL string (e.g., "$1.2M")
|
||||
isKilled: boolean; // Whether the pool has been killed
|
||||
}
|
||||
|
||||
export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
||||
@@ -360,12 +473,13 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and contract address
|
||||
// Get chain ID and contract addresses
|
||||
const chainId = await publicClient.getChainId();
|
||||
const address = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyPoolViewer: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
const plannerAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!address) {
|
||||
setError('IPartyPlanner contract not found for current chain');
|
||||
if (!plannerAddress || !partyInfoAddress) {
|
||||
setError('IPartyPlanner or PartyInfo contract not found for current chain');
|
||||
setPools([]);
|
||||
setPoolDetails([]);
|
||||
return;
|
||||
@@ -373,7 +487,7 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
||||
|
||||
// Call getAllPools function
|
||||
const result = await publicClient.readContract({
|
||||
address: address as `0x${string}`,
|
||||
address: plannerAddress as `0x${string}`,
|
||||
abi: IPartyPlannerABI,
|
||||
functionName: 'getAllPools',
|
||||
args: [BigInt(offset), BigInt(limit)],
|
||||
@@ -381,11 +495,11 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
||||
|
||||
setPools(result);
|
||||
|
||||
// Fetch details for each pool
|
||||
// Fetch details for each pool and check if it's working
|
||||
const details: PoolDetails[] = [];
|
||||
for (const poolAddress of result) {
|
||||
try {
|
||||
const [name, symbol, tokens] = await Promise.all([
|
||||
const [name, symbol, tokens, isWorking] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
address: poolAddress,
|
||||
abi: ERC20ABI,
|
||||
@@ -401,23 +515,83 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'allTokens',
|
||||
}).catch(() => [] as readonly `0x${string}`[]),
|
||||
publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'working',
|
||||
args: [poolAddress],
|
||||
}).catch(() => false),
|
||||
]);
|
||||
|
||||
// Fetch pool price and TVL (only for working pools)
|
||||
let priceStr: string | undefined;
|
||||
let tvlStr: string | undefined;
|
||||
|
||||
if (isWorking) {
|
||||
// Fetch token decimals and balance first (needed for both price and TVL)
|
||||
try {
|
||||
if (tokens && tokens.length > 0) {
|
||||
const firstTokenAddress = tokens[0];
|
||||
// Get token decimals, balance, and pool price in parallel
|
||||
const [decimals, balance, priceRaw] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
address: firstTokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'decimals',
|
||||
}) as Promise<number>,
|
||||
publicClient.readContract({
|
||||
address: firstTokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'balanceOf',
|
||||
args: [poolAddress],
|
||||
}) 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
|
||||
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 approximateTVL = tokenBalance * tokens.length;
|
||||
tvlStr = formatTVL(approximateTVL);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error fetching pool price/TVL for ${poolAddress}:`, err);
|
||||
priceStr = undefined;
|
||||
tvlStr = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Add all pools (both working and killed)
|
||||
details.push({
|
||||
address: poolAddress,
|
||||
name: name as string,
|
||||
symbol: symbol as string,
|
||||
tokens: tokens as readonly `0x${string}`[],
|
||||
price: priceStr,
|
||||
tvl: tvlStr,
|
||||
isKilled: !isWorking,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching pool details for', poolAddress, err);
|
||||
// Add pool with fallback values
|
||||
details.push({
|
||||
address: poolAddress,
|
||||
name: 'Unknown Pool',
|
||||
symbol: 'POOL',
|
||||
tokens: [],
|
||||
});
|
||||
// Skip pools that fail to load
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,17 +620,20 @@ export interface SwapMintAmounts {
|
||||
amountInUsed: bigint;
|
||||
fee: bigint;
|
||||
lpMinted: bigint;
|
||||
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||
}
|
||||
|
||||
export interface BurnSwapAmounts {
|
||||
amountOut: bigint;
|
||||
outFee: bigint;
|
||||
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||
}
|
||||
|
||||
export function useSwapMintAmounts(
|
||||
poolAddress: `0x${string}` | undefined,
|
||||
inputTokenIndex: number | undefined,
|
||||
maxAmountIn: bigint | undefined
|
||||
maxAmountIn: bigint | undefined,
|
||||
inputTokenDecimals: number | undefined // Decimals of the input token
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -477,8 +654,11 @@ export function useSwapMintAmounts(
|
||||
}
|
||||
|
||||
const fetchSwapMintAmounts = async () => {
|
||||
if (!publicClient) {
|
||||
setLoading(false);
|
||||
if (!publicClient) return;
|
||||
|
||||
// Early validation
|
||||
if (inputTokenDecimals === undefined) {
|
||||
setError('inputTokenDecimals is required but was undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -486,32 +666,58 @@ export function useSwapMintAmounts(
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and contract address
|
||||
const chainId = await publicClient.getChainId();
|
||||
const viewerAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyPoolViewer: string } }>)[chainId.toString()]?.v1?.PartyPoolViewer;
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!viewerAddress) {
|
||||
setError('IPartyPoolViewer contract not found for current chain');
|
||||
if (!partyInfoAddress) {
|
||||
setError('PartyInfo contract not found for current chain');
|
||||
setSwapMintAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call swapMintAmounts function
|
||||
const result = await publicClient.readContract({
|
||||
address: viewerAddress as `0x${string}`,
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyPoolViewerABI,
|
||||
functionName: 'swapMintAmounts',
|
||||
args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn],
|
||||
}) as readonly [bigint, bigint, bigint];
|
||||
|
||||
// Fetch and calculate pool price
|
||||
let poolPrice: number | undefined;
|
||||
let calculatedSlippage: number | undefined;
|
||||
|
||||
try {
|
||||
const poolPriceInt128 = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'poolPrice',
|
||||
args: [poolAddress, BigInt(inputTokenIndex)],
|
||||
}) 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({
|
||||
amountInUsed: result[0],
|
||||
fee: result[1],
|
||||
lpMinted: result[2],
|
||||
fee: result[2],
|
||||
lpMinted: result[1],
|
||||
calculatedSlippage,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error calling swapMintAmounts:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch swap mint amounts');
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch swap mint amounts';
|
||||
setError(errorMessage);
|
||||
setSwapMintAmounts(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -519,7 +725,7 @@ export function useSwapMintAmounts(
|
||||
};
|
||||
|
||||
fetchSwapMintAmounts();
|
||||
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn]);
|
||||
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, inputTokenDecimals]);
|
||||
|
||||
return {
|
||||
swapMintAmounts,
|
||||
@@ -532,7 +738,8 @@ export function useSwapMintAmounts(
|
||||
export function useBurnSwapAmounts(
|
||||
poolAddress: `0x${string}` | undefined,
|
||||
lpAmount: bigint | undefined,
|
||||
inputTokenIndex: number | undefined
|
||||
inputTokenIndex: number | undefined,
|
||||
tokenDecimals: number | undefined // Decimals of the output token
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -561,57 +768,66 @@ export function useBurnSwapAmounts(
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and contract address
|
||||
const chainId = await publicClient.getChainId();
|
||||
const viewerAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyPoolViewer: string } }>)[chainId.toString()]?.v1?.PartyPoolViewer;
|
||||
// @ts-ignore
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!viewerAddress) {
|
||||
setError('IPartyPoolViewer contract not found for current chain');
|
||||
if (!partyInfoAddress) {
|
||||
setError('PartyInfo contract not found for current chain');
|
||||
setBurnSwapAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Log inputs
|
||||
console.log('🔍 burnSwapAmounts INPUTS:', {
|
||||
chainId: chainId.toString(),
|
||||
rpcUrl: publicClient.transport?.url || 'Unknown',
|
||||
poolAddress,
|
||||
lpAmount: lpAmount.toString(),
|
||||
inputTokenIndex,
|
||||
viewerAddress,
|
||||
});
|
||||
|
||||
// Call burnSwapAmounts function - returns [amountOut, outFee]
|
||||
const result = await publicClient.readContract({
|
||||
address: viewerAddress as `0x${string}`,
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyPoolViewerABI,
|
||||
functionName: 'burnSwapAmounts',
|
||||
args: [poolAddress, lpAmount, BigInt(inputTokenIndex)],
|
||||
}) 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
|
||||
let calculatedSlippage: number | undefined;
|
||||
if (tokenDecimals !== undefined) {
|
||||
try {
|
||||
// Get the market price from poolPrice (quoteTokenIndex = 0)
|
||||
const poolPriceInt128 = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'poolPrice',
|
||||
args: [poolAddress, BigInt(inputTokenIndex)],
|
||||
}) as bigint;
|
||||
|
||||
// Convert Q64 format to decimal (price = priceValue / 2^64)
|
||||
let poolPrice = Number(poolPriceInt128) / 2 ** 64;
|
||||
poolPrice = (poolPrice * Math.pow(10, 18 - tokenDecimals));
|
||||
|
||||
// For burnSwap: swapPrice = (outAmount + fee) / lpAmount
|
||||
// Need to apply decimal corrections: outAmount and fee are in token decimals, lpAmount is in 18 decimals
|
||||
const outAmountDecimal = Number(result[0]) / Math.pow(10, tokenDecimals);
|
||||
const feeDecimal = Number(result[1]) / Math.pow(10, tokenDecimals);
|
||||
const lpAmountDecimal = Number(lpAmount) / Math.pow(10, 18); // LP tokens have 18 decimals
|
||||
|
||||
const swapPrice = (outAmountDecimal + feeDecimal) / lpAmountDecimal;
|
||||
calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100;
|
||||
|
||||
} catch (slippageErr) {
|
||||
console.error(`Error calculating slippage for burnSwap:`, slippageErr);
|
||||
}
|
||||
}
|
||||
|
||||
const parsedAmounts = {
|
||||
amountOut: result[0],
|
||||
outFee: result[1],
|
||||
calculatedSlippage,
|
||||
};
|
||||
|
||||
// Log parsed result
|
||||
console.log('✅ burnSwapAmounts PARSED:', {
|
||||
amountOut: parsedAmounts.amountOut.toString(),
|
||||
outFee: parsedAmounts.outFee.toString(),
|
||||
});
|
||||
|
||||
setBurnSwapAmounts(parsedAmounts);
|
||||
} catch (err) {
|
||||
console.error('Error calling burnSwapAmounts:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch burn swap amounts');
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch burn swap amounts';
|
||||
setError(errorMessage);
|
||||
setBurnSwapAmounts(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -619,7 +835,7 @@ export function useBurnSwapAmounts(
|
||||
};
|
||||
|
||||
fetchBurnSwapAmounts();
|
||||
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex]);
|
||||
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, tokenDecimals]);
|
||||
|
||||
return {
|
||||
burnSwapAmounts,
|
||||
@@ -688,4 +904,74 @@ export function useLPTokenBalance(
|
||||
error,
|
||||
isReady: mounted,
|
||||
};
|
||||
}
|
||||
|
||||
export function useBurnAmounts(
|
||||
poolAddress: `0x${string}` | undefined,
|
||||
lpTokenAmount: bigint | undefined
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [burnAmounts, setBurnAmounts] = useState<bigint[] | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted || !poolAddress || !lpTokenAmount || lpTokenAmount === 0n) {
|
||||
setBurnAmounts(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchBurnAmounts = async () => {
|
||||
if (!publicClient) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and PartyInfo contract address
|
||||
const chainId = await publicClient.getChainId();
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!partyInfoAddress) {
|
||||
setError('PartyInfo contract not found for current chain');
|
||||
setBurnAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call burnAmounts function
|
||||
const amounts = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyPoolViewerABI,
|
||||
functionName: 'burnAmounts',
|
||||
args: [poolAddress, lpTokenAmount],
|
||||
}) as bigint[];
|
||||
|
||||
setBurnAmounts(amounts);
|
||||
} catch (err) {
|
||||
console.error('Error fetching burn amounts:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch burn amounts');
|
||||
setBurnAmounts(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchBurnAmounts();
|
||||
}, [publicClient, mounted, poolAddress, lpTokenAmount]);
|
||||
|
||||
return {
|
||||
burnAmounts,
|
||||
loading,
|
||||
error,
|
||||
isReady: mounted,
|
||||
};
|
||||
}
|
||||
@@ -2,14 +2,37 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { usePublicClient, useWalletClient } from 'wagmi';
|
||||
import { decodeEventLog } from 'viem';
|
||||
import { decodeEventLog, parseUnits } from 'viem';
|
||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||
import chainInfo from '../../../lmsr-amm/liqp-deployments.json';
|
||||
import chainInfo from '../contracts/liqp-deployments.json';
|
||||
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
|
||||
import type { AvailableToken } from './usePartyPlanner';
|
||||
|
||||
// Q96 constant for price calculations
|
||||
const Q96 = 1n << 96n;
|
||||
|
||||
/**
|
||||
* Calculate slippage percentage based on market price and actual swap execution price
|
||||
* @param marketPrice The current market price from the pool (in Q64 format, already converted to decimal)
|
||||
* @param swapOutputAmount The output amount from the swap
|
||||
* @param swapInputAmount The input amount for the swap
|
||||
* @param swapFee The fee charged for the swap
|
||||
* @returns Slippage as a percentage (e.g., 5.5 means 5.5%)
|
||||
*/
|
||||
export function calculateSlippage(
|
||||
marketPrice: number,
|
||||
swapOutputAmount: number,
|
||||
swapInputAmount: number,
|
||||
swapFee: number
|
||||
): number {
|
||||
// Calculate actual swap price with decimal correction
|
||||
const swapPrice = swapOutputAmount / ((swapInputAmount) - (swapFee));
|
||||
// Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100
|
||||
const slippage = ((marketPrice - swapPrice) / marketPrice) * 100;
|
||||
|
||||
return slippage;
|
||||
}
|
||||
|
||||
export interface SwapAmountResult {
|
||||
tokenAddress: `0x${string}`;
|
||||
tokenSymbol: string;
|
||||
@@ -20,6 +43,7 @@ export interface SwapAmountResult {
|
||||
kappa: bigint;
|
||||
inputTokenIndex: number;
|
||||
outputTokenIndex: number;
|
||||
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,22 +119,11 @@ export function useSwapAmounts(
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const amountInWei = BigInt(Math.floor(parseFloat(fromAmount) * Math.pow(10, 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 amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals);
|
||||
|
||||
const results: SwapAmountResult[] = [];
|
||||
const chainId = await publicClient.getChainId();
|
||||
const partyInfoAddress = (chainInfo as any)[chainId]?.v1?.PartyInfo as `0x${string}` | undefined;
|
||||
|
||||
// Calculate swap amounts for ALL routes of each token
|
||||
for (const token of availableTokens) {
|
||||
@@ -121,21 +134,19 @@ export function useSwapAmounts(
|
||||
// Evaluate ALL routes for this token
|
||||
for (const route of token.swapRoutes) {
|
||||
try {
|
||||
// Get swap amounts
|
||||
const swapResult = await publicClient.readContract({
|
||||
// Get swap amounts with NO LIMIT
|
||||
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({
|
||||
address: route.poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'swapAmounts',
|
||||
args: [
|
||||
BigInt(route.inputTokenIndex),
|
||||
BigInt(route.outputTokenIndex),
|
||||
amountInWei,
|
||||
limitPrice,
|
||||
amountInTokenUnits,
|
||||
0n, // NO LIMIT
|
||||
],
|
||||
}) as readonly [bigint, bigint, bigint];
|
||||
|
||||
const [amountIn, amountOut, fee] = swapResult;
|
||||
|
||||
// Get kappa for this pool
|
||||
const kappa = await publicClient.readContract({
|
||||
address: route.poolAddress,
|
||||
@@ -143,16 +154,46 @@ export function useSwapAmounts(
|
||||
functionName: 'kappa',
|
||||
}) as bigint;
|
||||
|
||||
// Calculate slippage for this route
|
||||
let calculatedSlippage: number | undefined;
|
||||
if (partyInfoAddress) {
|
||||
try {
|
||||
// Get the current market price from PoolInfo
|
||||
const priceInt128 = await publicClient.readContract({
|
||||
address: partyInfoAddress,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'price',
|
||||
args: [route.poolAddress, BigInt(route.inputTokenIndex), BigInt(route.outputTokenIndex)],
|
||||
}) as bigint;
|
||||
|
||||
// Convert Q128 format to decimal (price = priceValue / 2^128)
|
||||
// Then apply decimal conversion: 10^18 / 10^outputTokenDecimals
|
||||
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);
|
||||
console.log('calculatedSlippage', calculatedSlippage)
|
||||
} catch (slippageErr) {
|
||||
console.error(`Error calculating slippage for ${token.symbol}:`, slippageErr);
|
||||
}
|
||||
}
|
||||
|
||||
routeResults.push({
|
||||
tokenAddress: token.address,
|
||||
tokenSymbol: token.symbol,
|
||||
amountIn,
|
||||
amountOut,
|
||||
fee,
|
||||
amountIn: swapInputAmount,
|
||||
amountOut: swapOutputAmount,
|
||||
fee: inFee,
|
||||
poolAddress: route.poolAddress,
|
||||
kappa,
|
||||
inputTokenIndex: route.inputTokenIndex,
|
||||
outputTokenIndex: route.outputTokenIndex,
|
||||
calculatedSlippage,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error calculating swap for ${token.symbol} via ${route.poolAddress}:`, err);
|
||||
@@ -319,9 +360,7 @@ export function useSwap() {
|
||||
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
|
||||
console.log('✅ Approval confirmed');
|
||||
|
||||
// STEP 2: Calculate limit price and deadline
|
||||
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100));
|
||||
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
|
||||
// STEP 2: Calculate deadline
|
||||
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200);
|
||||
|
||||
console.log('🚀 Executing swap with params:', {
|
||||
@@ -331,25 +370,27 @@ export function useSwap() {
|
||||
inputTokenIndex,
|
||||
outputTokenIndex,
|
||||
maxAmountIn: maxAmountIn.toString(),
|
||||
limitPrice: limitPrice.toString(),
|
||||
limitPrice: '0 (no limit)',
|
||||
deadline: deadline.toString(),
|
||||
unwrap: false,
|
||||
});
|
||||
|
||||
// STEP 3: Execute the swap transaction
|
||||
// STEP 3: Execute the swap transaction with no limit price
|
||||
const hash = await walletClient.writeContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'swap',
|
||||
args: [
|
||||
userAddress, // payer
|
||||
'0x00000000', // selector (bytes4(0))
|
||||
userAddress, // receiver
|
||||
BigInt(inputTokenIndex),
|
||||
BigInt(outputTokenIndex),
|
||||
maxAmountIn,
|
||||
limitPrice,
|
||||
0n, // no limit price
|
||||
deadline,
|
||||
false, // unwrap
|
||||
'0x', // cbData (empty bytes)
|
||||
],
|
||||
});
|
||||
|
||||
@@ -813,6 +854,7 @@ export function useBurn() {
|
||||
});
|
||||
|
||||
if (decodedLog.eventName === 'Burn') {
|
||||
// @ts-ignore
|
||||
const { amounts, lpBurned } = decodedLog.args as {
|
||||
amounts: bigint[];
|
||||
lpBurned: bigint;
|
||||
|
||||
404
src/init_pools.js
Normal file
404
src/init_pools.js
Normal file
@@ -0,0 +1,404 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Uniswap V4 Quote Script
|
||||
* Connects to Anvil and gets swap quotes from multiple Uniswap V4 pools
|
||||
*/
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
import { Token } from '@uniswap/sdk-core';
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
const ANVIL_RPC_URL = 'http://127.0.0.1:8545';
|
||||
|
||||
// Hardcoded private key for Anvil testing (default Anvil account #0)
|
||||
const PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
||||
|
||||
// Contract addresses
|
||||
const QUOTER_ADDRESS = '0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203';
|
||||
const STATE_VIEW_ADDRESS = '0x7ffe42c4a5deea5b0fec41c94c136cf115597227';
|
||||
const POSITION_MANAGER_ADDRESS = '0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e';
|
||||
|
||||
// Chain ID
|
||||
const ChainId = {
|
||||
MAINNET: 1
|
||||
};
|
||||
|
||||
// Token definitions
|
||||
const TOKENS = {
|
||||
'USDC': new Token(
|
||||
ChainId.MAINNET,
|
||||
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
6,
|
||||
'USDC',
|
||||
'USDC'
|
||||
),
|
||||
'WETH': new Token(
|
||||
ChainId.MAINNET,
|
||||
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
18,
|
||||
'WETH',
|
||||
'WETH'
|
||||
),
|
||||
'USDT': new Token(
|
||||
ChainId.MAINNET,
|
||||
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
6,
|
||||
'USDT',
|
||||
'USDT'
|
||||
),
|
||||
'WBTC': new Token(
|
||||
ChainId.MAINNET,
|
||||
'0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
||||
8,
|
||||
'WBTC',
|
||||
'WBTC'
|
||||
)
|
||||
};
|
||||
|
||||
// Pool definitions
|
||||
const POOLS = {
|
||||
// 'WBTC/USDT': '0x9Db9e0e53058C89e5B94e29621a205198648425B',
|
||||
'ETH/USDT': '0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36'
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// CONTRACT ABIs
|
||||
// ============================================================================
|
||||
|
||||
const QUOTER_ABI = [
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
components: [
|
||||
{ name: 'currency0', type: 'address' },
|
||||
{ name: 'currency1', type: 'address' },
|
||||
{ name: 'fee', type: 'uint24' },
|
||||
{ name: 'tickSpacing', type: 'int24' },
|
||||
{ name: 'hooks', type: 'address' }
|
||||
],
|
||||
name: 'poolKey',
|
||||
type: 'tuple'
|
||||
},
|
||||
{ name: 'zeroForOne', type: 'bool' },
|
||||
{ name: 'exactAmount', type: 'uint128' },
|
||||
{ name: 'hookData', type: 'bytes' }
|
||||
],
|
||||
name: 'params',
|
||||
type: 'tuple'
|
||||
}
|
||||
],
|
||||
name: 'quoteExactInputSingle',
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{ name: 'amountOut', type: 'uint256' }
|
||||
],
|
||||
name: 'result',
|
||||
type: 'tuple'
|
||||
}
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
];
|
||||
|
||||
const STATE_VIEW_ABI = [
|
||||
{
|
||||
inputs: [
|
||||
{ name: 'poolId', type: 'bytes32' }
|
||||
],
|
||||
name: 'getSlot0',
|
||||
outputs: [
|
||||
{ name: 'sqrtPriceX96', type: 'uint160' },
|
||||
{ name: 'tick', type: 'int24' },
|
||||
{ name: 'protocolFee', type: 'uint24' },
|
||||
{ name: 'lpFee', type: 'uint24' }
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
];
|
||||
|
||||
const POSITION_MANAGER_ABI = [
|
||||
{
|
||||
inputs: [{ name: 'poolId', type: 'bytes25' }],
|
||||
name: 'poolKeys',
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{ name: 'currency0', type: 'address' },
|
||||
{ name: 'currency1', type: 'address' },
|
||||
{ name: 'fee', type: 'uint24' },
|
||||
{ name: 'tickSpacing', type: 'int24' },
|
||||
{ name: 'hooks', type: 'address' }
|
||||
],
|
||||
name: 'poolKey',
|
||||
type: 'tuple'
|
||||
}
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
function formatAmount(amountWei, decimals) {
|
||||
return ethers.utils.formatUnits(amountWei, decimals);
|
||||
}
|
||||
|
||||
function parseAmount(amountStr, decimals) {
|
||||
return ethers.utils.parseUnits(amountStr, decimals);
|
||||
}
|
||||
|
||||
async function getPoolKey(provider, poolId) {
|
||||
try {
|
||||
const positionManager = new ethers.Contract(
|
||||
POSITION_MANAGER_ADDRESS,
|
||||
POSITION_MANAGER_ABI,
|
||||
provider
|
||||
);
|
||||
|
||||
|
||||
const poolIdBytes25 = poolId.slice(0, 52);
|
||||
const poolKey = await positionManager.poolKeys(poolIdBytes25);
|
||||
|
||||
return {
|
||||
currency0: poolKey.currency0,
|
||||
currency1: poolKey.currency1,
|
||||
fee: poolKey.fee,
|
||||
tickSpacing: poolKey.tickSpacing,
|
||||
hooks: poolKey.hooks
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[!] Error fetching pool key: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getPoolPrice(provider, poolId) {
|
||||
try {
|
||||
const stateView = new ethers.Contract(
|
||||
STATE_VIEW_ADDRESS,
|
||||
STATE_VIEW_ABI,
|
||||
provider
|
||||
);
|
||||
|
||||
const slot0 = await stateView.getSlot0(poolId);
|
||||
|
||||
return {
|
||||
sqrtPriceX96: slot0.sqrtPriceX96,
|
||||
tick: slot0.tick,
|
||||
protocolFee: slot0.protocolFee,
|
||||
lpFee: slot0.lpFee
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[!] Error fetching pool price: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getSwapQuote(provider, poolKey, amountIn, tokenInAddress, tokenOutAddress) {
|
||||
try {
|
||||
const quoter = new ethers.Contract(
|
||||
QUOTER_ADDRESS,
|
||||
QUOTER_ABI,
|
||||
provider
|
||||
);
|
||||
|
||||
// Determine swap direction (zeroForOne)
|
||||
const currency0 = poolKey.currency0.toLowerCase();
|
||||
const currency1 = poolKey.currency1.toLowerCase();
|
||||
const tokenInLower = tokenInAddress.toLowerCase();
|
||||
|
||||
let zeroForOne;
|
||||
if (tokenInLower === currency0) {
|
||||
zeroForOne = true;
|
||||
} else if (tokenInLower === currency1) {
|
||||
zeroForOne = false;
|
||||
} else {
|
||||
// Check by comparison if tokens aren't matching pool currencies exactly
|
||||
zeroForOne = tokenInLower < tokenOutAddress.toLowerCase();
|
||||
}
|
||||
|
||||
// Build quote params
|
||||
const params = {
|
||||
poolKey: {
|
||||
currency0: poolKey.currency0,
|
||||
currency1: poolKey.currency1,
|
||||
fee: poolKey.fee,
|
||||
tickSpacing: poolKey.tickSpacing,
|
||||
hooks: poolKey.hooks
|
||||
},
|
||||
zeroForOne: zeroForOne,
|
||||
exactAmount: amountIn.toString(),
|
||||
hookData: '0x00'
|
||||
};
|
||||
|
||||
// Call quoter
|
||||
const result = await quoter.callStatic.quoteExactInputSingle(params);
|
||||
|
||||
return result.amountOut;
|
||||
} catch (error) {
|
||||
console.error(`[!] Error getting quote: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function findTokenByAddress(address) {
|
||||
const addressLower = address.toLowerCase();
|
||||
for (const [symbol, token] of Object.entries(TOKENS)) {
|
||||
if (token.address.toLowerCase() === addressLower) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`
|
||||
Usage: node init_pool.js [--pools <pools>]
|
||||
|
||||
Options:
|
||||
--pools <pools> Comma-separated pool names (default: all pools)
|
||||
--help Show this help message
|
||||
|
||||
Note: Amount is hardcoded to 100 USDT
|
||||
|
||||
Examples:
|
||||
node init_pool.js
|
||||
node init_pool.js --pools "WBTC/USDT,ETH/USDT"
|
||||
`);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN FUNCTION
|
||||
// ============================================================================
|
||||
|
||||
async function main() {
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Hardcoded values
|
||||
const amount = "100";
|
||||
const tokenSymbol = "USDT";
|
||||
let poolsStr = Object.keys(POOLS).join(',');
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--pools' && i + 1 < args.length) {
|
||||
poolsStr = args[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup provider and wallet
|
||||
const provider = new ethers.providers.JsonRpcProvider(ANVIL_RPC_URL);
|
||||
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
||||
|
||||
console.log(`[+] Connected to Anvil at ${ANVIL_RPC_URL}`);
|
||||
console.log(`[*] Using wallet: ${wallet.address}`);
|
||||
|
||||
const tokenIn = TOKENS[tokenSymbol];
|
||||
const amountInWei = parseAmount(amount, tokenIn.decimals);
|
||||
|
||||
console.log(`\n${'='.repeat(70)}`);
|
||||
console.log(`[~] Getting quotes for swapping ${amount} ${tokenSymbol}`);
|
||||
console.log(`${'='.repeat(70)}\n`);
|
||||
|
||||
// Parse pool selection
|
||||
const selectedPools = poolsStr.split(',').map(p => p.trim());
|
||||
|
||||
// Process each pool
|
||||
for (const poolName of selectedPools) {
|
||||
if (!POOLS[poolName]) {
|
||||
console.log(`[!] Unknown pool: ${poolName}, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const poolAddress = POOLS[poolName];
|
||||
console.log(`\n[>] Pool: ${poolName}`);
|
||||
console.log(` Address: ${poolAddress}`);
|
||||
console.log(` ${'-'.repeat(66)}`);
|
||||
|
||||
// Get pool key
|
||||
const poolKey = await getPoolKey(provider, poolAddress);
|
||||
if (!poolKey) {
|
||||
console.log(` [!] Failed to fetch pool key\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` [+] Pool key fetched`);
|
||||
console.log(` Currency0: ${poolKey.currency0}`);
|
||||
console.log(` Currency1: ${poolKey.currency1}`);
|
||||
console.log(` Fee: ${poolKey.fee} (${(poolKey.fee / 10000).toFixed(2)}%)`);
|
||||
|
||||
// Get pool price info
|
||||
const poolPrice = await getPoolPrice(provider, poolAddress);
|
||||
if (poolPrice) {
|
||||
console.log(` Current Tick: ${poolPrice.tick}`);
|
||||
console.log(` LP Fee: ${poolPrice.lpFee} (${(poolPrice.lpFee / 10000).toFixed(2)}%)`);
|
||||
}
|
||||
|
||||
// Identify tokens in the pool
|
||||
const token0Info = findTokenByAddress(poolKey.currency0);
|
||||
const token1Info = findTokenByAddress(poolKey.currency1);
|
||||
|
||||
if (!token0Info || !token1Info) {
|
||||
console.log(` [!] Unknown token addresses in pool\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` Token pair: ${token0Info.symbol}/${token1Info.symbol}`);
|
||||
|
||||
// Determine which token to swap to
|
||||
let tokenOut;
|
||||
if (tokenIn.address.toLowerCase() === token0Info.address.toLowerCase()) {
|
||||
tokenOut = token1Info;
|
||||
} else if (tokenIn.address.toLowerCase() === token1Info.address.toLowerCase()) {
|
||||
tokenOut = token0Info;
|
||||
} else {
|
||||
console.log(` [!] Input token ${tokenIn.symbol} not in this pool\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get quote
|
||||
console.log(`\n [~] Quote: ${amount} ${tokenIn.symbol} -> ${tokenOut.symbol}`);
|
||||
const amountOutWei = await getSwapQuote(
|
||||
provider,
|
||||
poolKey,
|
||||
amountInWei,
|
||||
tokenIn.address,
|
||||
tokenOut.address
|
||||
);
|
||||
|
||||
if (amountOutWei) {
|
||||
const amountOut = formatAmount(amountOutWei, tokenOut.decimals);
|
||||
const exchangeRate = parseFloat(amountOut) / parseFloat(amount);
|
||||
|
||||
console.log(` [+] Amount Out: ${amountOut} ${tokenOut.symbol}`);
|
||||
console.log(` [+] Exchange Rate: 1 ${tokenIn.symbol} = ${exchangeRate} ${tokenOut.symbol}`);
|
||||
} else {
|
||||
console.log(` [!] Failed to get quote`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n${'='.repeat(70)}\n`);
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main().catch(error => {
|
||||
console.error('[!] Error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user