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
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
*secret*
|
||||||
|
.env
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/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
|
DEV=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CHAIN_ID=${1:-11155111} # Defaults to Sepolia
|
|
||||||
PROJECT=liquidity-party
|
PROJECT=liquidity-party
|
||||||
REMOTE=git.dxod.org/dexorder/dexorder
|
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 [ "$BUILD" != "1" ]; then
|
||||||
if [ "$DEV" = "1" ]; then
|
if [ "$DEV" = "1" ]; then
|
||||||
TAG="dev$(date +%Y%m%d%H%M%S)"
|
TAG="dev$(date +%Y%m%d%H%M%S)"
|
||||||
@@ -32,12 +26,11 @@ else
|
|||||||
echo Building
|
echo Building
|
||||||
fi
|
fi
|
||||||
|
|
||||||
bin/generate-contracts "$CHAIN_ID" || exit 1
|
|
||||||
npm run build || exit 1
|
npm run build || exit 1
|
||||||
|
|
||||||
if [ "$BUILD" != "1" ]; then
|
if [ "$BUILD" != "1" ]; then
|
||||||
echo Deploying with tag $TAG
|
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:latest dexorder/$PROJECT:$TAG
|
||||||
docker tag dexorder/$PROJECT:$TAG $REMOTE/$PROJECT:$TAG
|
docker tag dexorder/$PROJECT:$TAG $REMOTE/$PROJECT:$TAG
|
||||||
docker tag $REMOTE/$PROJECT:$TAG $REMOTE/$PROJECT:latest
|
docker tag $REMOTE/$PROJECT:$TAG $REMOTE/$PROJECT:latest
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ case "$CHAIN_ID" in
|
|||||||
"sepolia")
|
"sepolia")
|
||||||
CHAIN_ID=11155111
|
CHAIN_ID=11155111
|
||||||
;;
|
;;
|
||||||
|
"mockchain")
|
||||||
|
CHAIN_ID=31337
|
||||||
|
;;
|
||||||
"mainnet")
|
"mainnet")
|
||||||
CHAIN_ID=1
|
CHAIN_ID=1
|
||||||
;;
|
;;
|
||||||
@@ -34,7 +37,7 @@ generate() {
|
|||||||
|
|
||||||
generate IPartyPlanner
|
generate IPartyPlanner
|
||||||
generate IPartyPool
|
generate IPartyPool
|
||||||
generate IPartyPoolViewer
|
generate IPartyInfo
|
||||||
|
|
||||||
cp "$METADATA_PATH" src/contracts/
|
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",
|
"clsx": "^2.1.1",
|
||||||
"i18next": "^23.15.0",
|
"i18next": "^23.15.0",
|
||||||
"lucide-react": "^0.460.0",
|
"lucide-react": "^0.460.0",
|
||||||
"next": "^15.1.3",
|
"next": "15.5.7",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -1405,15 +1405,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
|
||||||
"integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==",
|
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
||||||
"integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==",
|
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1427,9 +1427,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
||||||
"integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==",
|
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1443,9 +1443,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
||||||
"integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==",
|
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1459,9 +1459,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
||||||
"integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==",
|
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1475,9 +1475,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz",
|
||||||
"integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==",
|
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1491,9 +1491,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
||||||
"integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==",
|
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1507,9 +1507,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
||||||
"integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==",
|
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1523,9 +1523,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz",
|
||||||
"integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==",
|
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -6179,12 +6179,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.5.4",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
|
||||||
"integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==",
|
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.5.4",
|
"@next/env": "15.5.7",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
@@ -6197,14 +6197,14 @@
|
|||||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "15.5.4",
|
"@next/swc-darwin-arm64": "15.5.7",
|
||||||
"@next/swc-darwin-x64": "15.5.4",
|
"@next/swc-darwin-x64": "15.5.7",
|
||||||
"@next/swc-linux-arm64-gnu": "15.5.4",
|
"@next/swc-linux-arm64-gnu": "15.5.7",
|
||||||
"@next/swc-linux-arm64-musl": "15.5.4",
|
"@next/swc-linux-arm64-musl": "15.5.7",
|
||||||
"@next/swc-linux-x64-gnu": "15.5.4",
|
"@next/swc-linux-x64-gnu": "15.5.7",
|
||||||
"@next/swc-linux-x64-musl": "15.5.4",
|
"@next/swc-linux-x64-musl": "15.5.7",
|
||||||
"@next/swc-win32-arm64-msvc": "15.5.4",
|
"@next/swc-win32-arm64-msvc": "15.5.7",
|
||||||
"@next/swc-win32-x64-msvc": "15.5.4",
|
"@next/swc-win32-x64-msvc": "15.5.7",
|
||||||
"sharp": "^0.34.3"
|
"sharp": "^0.34.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"i18next": "^23.15.0",
|
"i18next": "^23.15.0",
|
||||||
"lucide-react": "^0.460.0",
|
"lucide-react": "^0.460.0",
|
||||||
"next": "^15.1.3",
|
"next": "15.5.7",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
version="1.1"
|
version="1.1"
|
||||||
viewBox="0 0 86.435997 86.435997"
|
viewBox="0 0 86.435997 86.435997"
|
||||||
id="svg585"
|
id="svg585"
|
||||||
sodipodi:docname="logo-flower.svg"
|
sodipodi:docname="logo-splash.svg"
|
||||||
width="86.435997"
|
width="86.435997"
|
||||||
height="86.435997"
|
height="86.435997"
|
||||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
@@ -27,24 +27,17 @@
|
|||||||
inkscape:cx="218.22034"
|
inkscape:cx="218.22034"
|
||||||
inkscape:cy="48.516949"
|
inkscape:cy="48.516949"
|
||||||
inkscape:window-width="1864"
|
inkscape:window-width="1864"
|
||||||
inkscape:window-height="1131"
|
inkscape:window-height="1011"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="0"
|
inkscape:window-y="0"
|
||||||
inkscape:window-maximized="1"
|
inkscape:window-maximized="1"
|
||||||
inkscape:current-layer="svg585" />
|
inkscape:current-layer="svg585" />
|
||||||
<!-- Flower -->
|
|
||||||
<g
|
<g
|
||||||
fill="#000000"
|
fill="#000000"
|
||||||
id="g583"
|
id="g583"
|
||||||
transform="translate(-1.782,-1.782)">
|
transform="translate(-1.782,-1.782)">
|
||||||
<g
|
<g
|
||||||
id="g581">
|
id="g581">
|
||||||
<circle
|
|
||||||
fill="#000000"
|
|
||||||
cx="45"
|
|
||||||
cy="44.999001"
|
|
||||||
r="5.277"
|
|
||||||
id="circle545" />
|
|
||||||
<g
|
<g
|
||||||
id="g579">
|
id="g579">
|
||||||
<g
|
<g
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -1,46 +1,163 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Create Pool from Real-Time Prices Script
|
* 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 { ethers } from 'ethers';
|
||||||
import { readFile } from 'fs/promises';
|
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 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 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;
|
const PARTY_PLANNER_ADDRESS = chainInfoData[currentConfig.chainId].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 ERC20ABI = [
|
const ERC20ABI = [
|
||||||
{ "type": "function", "name": "balanceOf", "stateMutability": "view", "inputs": [{ "name": "account", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] },
|
{ "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" }] }
|
{ "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
|
// HELPER FUNCTIONS
|
||||||
@@ -114,20 +187,19 @@ async function fetchCoinGeckoPrices() {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
const prices = {
|
const prices = {};
|
||||||
USDC: data['usd-coin']?.usd || 1,
|
|
||||||
BTC: data['bitcoin']?.usd || 0,
|
|
||||||
WETH: data['weth']?.usd || 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (prices.BTC === 0 || prices.WETH === 0) {
|
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||||
throw new Error('Failed to fetch valid prices from CoinGecko');
|
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(`[+] Prices fetched successfully:`);
|
||||||
console.log(` USDC: $${prices.USDC}`);
|
for (const [symbol, price] of Object.entries(prices)) {
|
||||||
console.log(` BTC: $${prices.BTC.toLocaleString()}`);
|
console.log(` ${symbol.padEnd(6)}: $${price.toLocaleString()}`);
|
||||||
console.log(` WETH: $${prices.WETH.toLocaleString()}`);
|
}
|
||||||
|
|
||||||
return prices;
|
return prices;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -140,42 +212,39 @@ async function fetchCoinGeckoPrices() {
|
|||||||
* Calculate token amounts based on equal USD distribution
|
* Calculate token amounts based on equal USD distribution
|
||||||
*/
|
*/
|
||||||
function calculateTokenAmounts(prices, usdAmount) {
|
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 tokenAmounts = {};
|
||||||
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);
|
|
||||||
|
|
||||||
console.log(`\n[~] Calculated token amounts for $${usdAmount} ($${usdPerToken.toFixed(2)} per token):`);
|
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 {
|
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||||
USDC: usdcAmountBN,
|
// Calculate raw amount
|
||||||
BTC: btcAmountBN,
|
const rawAmount = usdPerToken / prices[symbol];
|
||||||
WETH: wethAmountBN
|
|
||||||
};
|
// 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
|
* Check token balances
|
||||||
*/
|
*/
|
||||||
async function checkBalances(provider, wallet, tokenAmounts) {
|
async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
|
||||||
console.log(`\n[~] Checking token balances for wallet: ${wallet.address}`);
|
console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`);
|
||||||
|
|
||||||
const balances = {};
|
const balances = {};
|
||||||
let hasEnoughBalance = true;
|
let hasEnoughBalance = true;
|
||||||
|
|
||||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||||
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, provider);
|
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];
|
const requiredAmount = tokenAmounts[symbol];
|
||||||
|
|
||||||
balances[symbol] = balance;
|
balances[symbol] = balance;
|
||||||
@@ -192,7 +261,7 @@ async function checkBalances(provider, wallet, tokenAmounts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hasEnoughBalance) {
|
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');
|
throw new Error('Insufficient token balance');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,19 +272,31 @@ async function checkBalances(provider, wallet, tokenAmounts) {
|
|||||||
/**
|
/**
|
||||||
* Approve tokens for the PartyPlanner contract
|
* 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...`);
|
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)) {
|
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
|
// Approve 1% more than needed to account for fees/slippage
|
||||||
const requiredAmount = tokenAmounts[symbol];
|
const requiredAmount = tokenAmounts[symbol];
|
||||||
const approvalAmount = requiredAmount.mul(101).div(100); // 1% buffer
|
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 {
|
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);
|
const tx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, approvalAmount);
|
||||||
await tx.wait();
|
await tx.wait();
|
||||||
console.log(` [+] ${symbol} approved (tx: ${tx.hash})`);
|
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) {
|
async function createPool(wallet, tokenAmounts) {
|
||||||
console.log(`\n[~] Creating new pool...`);
|
console.log(`\n[~] Creating new pool...`);
|
||||||
|
|
||||||
const partyPlanner = new ethers.Contract(PARTY_PLANNER_ADDRESS, IPartyPlannerABI, wallet);
|
|
||||||
|
|
||||||
// Prepare parameters
|
// Prepare parameters
|
||||||
const tokenAddresses = [
|
const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address);
|
||||||
TEST_TOKENS.USDC.address,
|
const initialDeposits = Object.keys(TEST_TOKENS).map(symbol => tokenAmounts[symbol].toString());
|
||||||
TEST_TOKENS.BTC.address,
|
|
||||||
TEST_TOKENS.WETH.address
|
|
||||||
];
|
|
||||||
|
|
||||||
const initialDeposits = [
|
// Set deadline to 5 minutes from now
|
||||||
tokenAmounts.USDC,
|
const deadline = Math.floor(Date.now() / 1000) + 300;
|
||||||
tokenAmounts.BTC,
|
|
||||||
tokenAmounts.WETH
|
|
||||||
];
|
|
||||||
|
|
||||||
// Set deadline to 1 hour from now
|
|
||||||
const deadline = Math.floor(Date.now() / 1000) + 3600;
|
|
||||||
|
|
||||||
console.log(`[~] Pool parameters:`);
|
console.log(`[~] Pool parameters:`);
|
||||||
console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`);
|
console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`);
|
||||||
console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`);
|
console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`);
|
||||||
console.log(` Tokens: ${tokenAddresses.join(', ')}`);
|
console.log(` Tokens: ${tokenAddresses.join(', ')}`);
|
||||||
console.log(` Payer: ${wallet.address}`);
|
console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`);
|
||||||
console.log(` Receiver: ${RECEIVER_ADDRESS}`);
|
console.log(` Payer (provides tokens): ${RECEIVER_ADDRESS}`);
|
||||||
|
console.log(` Receiver (gets LP tokens): ${wallet.address}`);
|
||||||
console.log(` Deadline: ${new Date(deadline * 1000).toISOString()}`);
|
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 {
|
try {
|
||||||
const tx = await partyPlanner.newPool(
|
// Execute cast command
|
||||||
DEFAULT_POOL_PARAMS.name,
|
const { execSync } = await import('child_process');
|
||||||
DEFAULT_POOL_PARAMS.symbol,
|
const output = execSync(castCommand, { encoding: 'utf-8' });
|
||||||
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();
|
|
||||||
|
|
||||||
console.log(`[+] Pool created successfully!`);
|
console.log(`[+] Pool created successfully!`);
|
||||||
console.log(` Transaction: ${receipt.transactionHash}`);
|
console.log(output);
|
||||||
console.log(` Block: ${receipt.blockNumber}`);
|
|
||||||
console.log(` Gas used: ${receipt.gasUsed.toString()}`);
|
|
||||||
|
|
||||||
// Try to extract pool address from events
|
return output;
|
||||||
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;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[!] Failed to create pool:`, error.message);
|
console.error(`[!] Failed to create pool:`, error.message);
|
||||||
|
if (error.stderr) {
|
||||||
// Try to extract revert reason if available
|
console.error(` Error output: ${error.stderr.toString()}`);
|
||||||
if (error.error && error.error.message) {
|
|
||||||
console.error(` Revert reason: ${error.error.message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +396,7 @@ Example:
|
|||||||
async function main() {
|
async function main() {
|
||||||
console.log(`${'='.repeat(70)}`);
|
console.log(`${'='.repeat(70)}`);
|
||||||
console.log(`Create Pool from Real-Time Prices`);
|
console.log(`Create Pool from Real-Time Prices`);
|
||||||
|
console.log(`Network: ${NETWORK}`);
|
||||||
console.log(`${'='.repeat(70)}\n`);
|
console.log(`${'='.repeat(70)}\n`);
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
@@ -370,18 +434,25 @@ async function main() {
|
|||||||
|
|
||||||
// Step 2: Calculate token amounts
|
// Step 2: Calculate token amounts
|
||||||
const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
|
const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
|
||||||
|
//
|
||||||
// Step 3: Connect to Anvil
|
// // Step 3: Connect to wallet
|
||||||
console.log(`\n[~] Connecting to Anvil at ${ANVIL_RPC_URL}...`);
|
console.log(`\n[~] Connecting to sender wallet at ${RPC_URL}...`);
|
||||||
const provider = new ethers.providers.JsonRpcProvider(ANVIL_RPC_URL);
|
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
||||||
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
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
|
// Step 4: Check balances
|
||||||
await checkBalances(provider, wallet, tokenAmounts);
|
await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS);
|
||||||
|
|
||||||
// Step 5: Approve tokens
|
// // Step 5: Approve tokens
|
||||||
await approveTokens(wallet, tokenAmounts);
|
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
|
// Step 6: Create pool
|
||||||
await createPool(wallet, tokenAmounts);
|
await createPool(wallet, tokenAmounts);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"create-pool": "node create_pool_from_prices.js"
|
"create-pool": "node create_pool_from_prices.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"ethers": "^5.7.2"
|
"ethers": "^5.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ export default function AboutPage() {
|
|||||||
<p className="text-muted-foreground leading-relaxed">
|
<p className="text-muted-foreground leading-relaxed">
|
||||||
Verify our contracts on{' '}
|
Verify our contracts on{' '}
|
||||||
<a
|
<a
|
||||||
href="https://sepolia.etherscan.io/address/0x081aA8AB1984680087c01a5Cd50fC9f49742434D#code"
|
href="https://etherscan.io/address/0x42977f565971F6D288a05ddEbC87A17276F71A29#code"
|
||||||
target="liqp_etherscan"
|
target="liqp_etherscan"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-primary hover:underline"
|
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>
|
<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>
|
<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 fill="#000000" id="g186" transform="translate(-1.782,-1.782)">
|
||||||
<g id="g184">
|
<g id="g184">
|
||||||
<circle fill="#000000" cx="45" cy="44.999001" r="5.277" id="circle148"></circle>
|
|
||||||
<g id="g182">
|
<g id="g182">
|
||||||
<g id="g152">
|
<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>
|
<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 '@/app/globals.css';
|
||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL('https://liquidity.party'),
|
metadataBase: new URL('https://liquidity.party'),
|
||||||
@@ -37,6 +38,19 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className="min-h-screen">
|
<body className="min-h-screen">
|
||||||
|
{/* Google tag (gtag.js) */}
|
||||||
|
<Script
|
||||||
|
src="https://www.googletagmanager.com/gtag/js?id=G-GH2R6NTLC3"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
/>
|
||||||
|
<Script id="gtag-init" strategy="afterInteractive">
|
||||||
|
{`
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){window.dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'G-GH2R6NTLC3');
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
<Providers>{children}</Providers>
|
<Providers>{children}</Providers>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
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 logoSrc = !mounted ? '/logo-dark.svg' : theme === 'dark' ? '/logo-dark.svg' : '/logo-light.svg';
|
||||||
|
const mobileLogoSrc = '/logo-splash.svg';
|
||||||
|
|
||||||
const navLinks = [
|
const navLinks = [
|
||||||
{ href: '/', label: 'Swap' },
|
{ href: '/', label: 'Swap' },
|
||||||
{ href: '/stake', label: 'Stake' },
|
{ href: '/stake', label: 'Stake' },
|
||||||
{ href: '/unstake', label: 'Unstake' },
|
{ href: '/unstake', label: 'Unstake' },
|
||||||
{ href: '/unstake-basket', label: 'Unstake Basket' },
|
|
||||||
{ href: '/about', label: 'About' },
|
{ href: '/about', label: 'About' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -46,31 +46,31 @@ export function Header() {
|
|||||||
<header className="border-b">
|
<header className="border-b">
|
||||||
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link href="/">
|
{/* Desktop logo - just a link */}
|
||||||
|
<Link href="/" className="hidden sm:block">
|
||||||
<img
|
<img
|
||||||
src={logoSrc}
|
src={logoSrc}
|
||||||
alt="Liquidity Party"
|
alt="Liquidity Party"
|
||||||
className="h-8 w-auto cursor-pointer"
|
className="h-8 w-auto cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</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">
|
<span className="bg-yellow-500/20 text-yellow-500 text-xs font-bold px-2 py-1 rounded border border-yellow-500/50">
|
||||||
BETA
|
BETA
|
||||||
</span>
|
</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>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<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">
|
<main className="flex-1 container mx-auto px-4 py-8">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</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>
|
</div>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</Web3Provider>
|
</Web3Provider>
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { ChevronDown, CheckCircle, XCircle, Loader2, ArrowDownUp } from 'lucide-react';
|
import { ChevronDown, CheckCircle, XCircle, Loader2, ArrowDownUp } from 'lucide-react';
|
||||||
import { useAccount } from 'wagmi';
|
import { useAccount, usePublicClient } from 'wagmi';
|
||||||
import { useGetAllPools, useTokenDetails, useSwapMintAmounts, useBurnSwapAmounts, useLPTokenBalance, type PoolDetails, type TokenDetails, type BurnSwapAmounts } from '@/hooks/usePartyPlanner';
|
import { useGetAllPools, useTokenDetails, useSwapMintAmounts, useBurnSwapAmounts, useLPTokenBalance, useBurnAmounts, type PoolDetails, type TokenDetails, type BurnSwapAmounts } from '@/hooks/usePartyPlanner';
|
||||||
import { useSwapMint, useBurnSwap, type ActualSwapMintAmounts, type ActualBurnSwapAmounts } from '@/hooks/usePartyPool';
|
import { useSwapMint, useBurnSwap, useBurn, type ActualSwapMintAmounts, type ActualBurnSwapAmounts, type ActualBurnAmounts } from '@/hooks/usePartyPool';
|
||||||
import { formatUnits, parseUnits } from 'viem';
|
import { formatUnits, parseUnits } from 'viem';
|
||||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||||
|
import { ERC20ABI } from '@/contracts/ERC20ABI';
|
||||||
|
|
||||||
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
||||||
type Mode = 'stake' | 'unstake';
|
type Mode = 'stake' | 'unstake';
|
||||||
@@ -19,9 +20,48 @@ interface StakeFormProps {
|
|||||||
defaultMode?: Mode;
|
defaultMode?: Mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper component for slippage warnings
|
||||||
|
function SlippageWarning({
|
||||||
|
slippage,
|
||||||
|
action,
|
||||||
|
isError
|
||||||
|
}: {
|
||||||
|
slippage: number;
|
||||||
|
action: string;
|
||||||
|
isError: boolean;
|
||||||
|
}) {
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||||
|
<p className="text-sm text-destructive font-medium">⚠️ Slippage Exceeds 5%</p>
|
||||||
|
<p className="text-xs text-destructive/80 mt-1">
|
||||||
|
The estimated slippage for this {action} is {Math.abs(slippage).toFixed(2)}%.
|
||||||
|
We cannot process this {action} as you may lose too much money due to the high slippage.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
|
||||||
|
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium">⚠️ High Slippage Warning</p>
|
||||||
|
<p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1">
|
||||||
|
The estimated slippage for this {action} is {Math.abs(slippage).toFixed(2)}%. You may lose money due to low liquidity in this pool.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenInfo {
|
||||||
|
address: `0x${string}`;
|
||||||
|
symbol: string;
|
||||||
|
decimals: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isConnected, address } = useAccount();
|
const { isConnected, address } = useAccount();
|
||||||
|
const publicClient = usePublicClient();
|
||||||
const [mode, setMode] = useState<Mode>(defaultMode);
|
const [mode, setMode] = useState<Mode>(defaultMode);
|
||||||
const [stakeAmount, setStakeAmount] = useState('');
|
const [stakeAmount, setStakeAmount] = useState('');
|
||||||
const [selectedPool, setSelectedPool] = useState<PoolDetails | null>(null);
|
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 [transactionError, setTransactionError] = useState<string | null>(null);
|
||||||
const [actualSwapMintAmounts, setActualSwapMintAmounts] = useState<ActualSwapMintAmounts | null>(null);
|
const [actualSwapMintAmounts, setActualSwapMintAmounts] = useState<ActualSwapMintAmounts | null>(null);
|
||||||
const [actualBurnSwapAmounts, setActualBurnSwapAmounts] = useState<ActualBurnSwapAmounts | 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 poolDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
const tokenDropdownRef = useRef<HTMLDivElement>(null);
|
const tokenDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Fetch all pools using the new hook
|
// Fetch all pools using the new hook
|
||||||
const { poolDetails, loading: poolsLoading } = useGetAllPools();
|
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
|
// Get token details for the user
|
||||||
const { tokenDetails, loading: tokensLoading } = useTokenDetails(address);
|
const { tokenDetails, loading: tokensLoading } = useTokenDetails(address);
|
||||||
|
|
||||||
// Initialize swap mint and burn swap hooks
|
// Initialize swap mint and burn swap hooks
|
||||||
const { executeSwapMint, isSwapMinting } = useSwapMint();
|
const { executeSwapMint, isSwapMinting } = useSwapMint();
|
||||||
const { executeBurnSwap, isBurnSwapping } = useBurnSwap();
|
const { executeBurnSwap, isBurnSwapping } = useBurnSwap();
|
||||||
|
const { executeBurn, isBurning } = useBurn();
|
||||||
|
|
||||||
// Fetch LP token balance (for unstake mode) - must be before isAmountExceedingBalance
|
// Fetch LP token balance (for unstake mode) - must be before isAmountExceedingBalance
|
||||||
const { lpBalance } = useLPTokenBalance(
|
const { lpBalance } = useLPTokenBalance(
|
||||||
@@ -103,31 +158,119 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
|
|
||||||
// Parse the stake amount to Wei
|
// Parse the stake amount to Wei
|
||||||
const maxAmountIn = useMemo(() => {
|
const maxAmountIn = useMemo(() => {
|
||||||
if (!stakeAmount || !selectedToken) return undefined;
|
if (!stakeAmount) return undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// For unstake mode, LP tokens always have 18 decimals
|
// For unstake mode, LP tokens always have 18 decimals
|
||||||
const decimals = mode === 'unstake' ? 18 : selectedToken.decimals;
|
if (mode === 'unstake') {
|
||||||
return parseUnits(stakeAmount, decimals);
|
return parseUnits(stakeAmount, 18);
|
||||||
|
}
|
||||||
|
if (!selectedToken) return undefined;
|
||||||
|
return parseUnits(stakeAmount, selectedToken.decimals);
|
||||||
} catch {
|
} catch {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}, [stakeAmount, selectedToken, mode]);
|
}, [stakeAmount, selectedToken, mode]);
|
||||||
|
|
||||||
// Fetch swap mint amounts (for stake 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' ? selectedPool?.address : undefined,
|
||||||
mode === 'stake' ? inputTokenIndex : undefined,
|
mode === 'stake' ? inputTokenIndex : undefined,
|
||||||
mode === 'stake' ? maxAmountIn : undefined
|
mode === 'stake' ? maxAmountIn : undefined,
|
||||||
|
mode === 'stake' && selectedToken ? selectedToken.decimals : undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fetch burn swap amounts (for unstake mode)
|
// Fetch burn swap amounts (for unstake mode, only when not redeeming all)
|
||||||
const { burnSwapAmounts, loading: burnSwapLoading } = useBurnSwapAmounts(
|
const { burnSwapAmounts, loading: burnSwapLoading, error: burnSwapError } = useBurnSwapAmounts(
|
||||||
mode === 'unstake' ? selectedPool?.address : undefined,
|
mode === 'unstake' && !redeemAll ? selectedPool?.address : undefined,
|
||||||
mode === 'unstake' ? maxAmountIn : undefined,
|
mode === 'unstake' && !redeemAll ? maxAmountIn : undefined,
|
||||||
mode === 'unstake' ? inputTokenIndex : 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
|
// Close dropdowns when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
@@ -144,16 +287,23 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleStake = async () => {
|
const handleStake = async () => {
|
||||||
if (!selectedPool || !selectedToken || !stakeAmount || inputTokenIndex === undefined || !maxAmountIn) {
|
if (!selectedPool || !stakeAmount || !maxAmountIn) {
|
||||||
console.error('Missing required stake parameters');
|
console.error('Missing required parameters');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (mode === 'unstake' && !redeemAll) {
|
||||||
|
if (!selectedToken || inputTokenIndex === undefined) {
|
||||||
|
console.error('Missing required unstake parameters');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTransactionStatus('pending');
|
setTransactionStatus('pending');
|
||||||
setTransactionError(null);
|
setTransactionError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (mode === 'stake') {
|
if (mode === 'stake') {
|
||||||
|
if (!selectedToken || inputTokenIndex === undefined) throw new Error('Missing stake parameters');
|
||||||
// Execute the swap mint transaction and capture actual amounts
|
// Execute the swap mint transaction and capture actual amounts
|
||||||
const result = await executeSwapMint(
|
const result = await executeSwapMint(
|
||||||
selectedPool.address,
|
selectedPool.address,
|
||||||
@@ -166,8 +316,19 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
if (result?.actualSwapMintAmounts) {
|
if (result?.actualSwapMintAmounts) {
|
||||||
setActualSwapMintAmounts(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 {
|
} else {
|
||||||
// Execute the burn swap transaction and capture actual amounts
|
// Execute the burn swap transaction and capture actual amounts
|
||||||
|
if (inputTokenIndex === undefined) throw new Error('Missing input token index');
|
||||||
const result = await executeBurnSwap(
|
const result = await executeBurnSwap(
|
||||||
selectedPool.address,
|
selectedPool.address,
|
||||||
maxAmountIn,
|
maxAmountIn,
|
||||||
@@ -183,7 +344,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
|
|
||||||
setTransactionStatus('success');
|
setTransactionStatus('success');
|
||||||
} catch (err) {
|
} 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');
|
setTransactionError(err instanceof Error ? err.message : 'Transaction failed');
|
||||||
setTransactionStatus('error');
|
setTransactionStatus('error');
|
||||||
}
|
}
|
||||||
@@ -195,6 +356,10 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
setStakeAmount('');
|
setStakeAmount('');
|
||||||
setSelectedPool(null);
|
setSelectedPool(null);
|
||||||
setSelectedToken(null);
|
setSelectedToken(null);
|
||||||
|
setRedeemAll(false);
|
||||||
|
setActualBurnAmounts(null);
|
||||||
|
setActualBurnSwapAmounts(null);
|
||||||
|
setActualSwapMintAmounts(null);
|
||||||
}
|
}
|
||||||
setTransactionStatus('idle');
|
setTransactionStatus('idle');
|
||||||
setTransactionError(null);
|
setTransactionError(null);
|
||||||
@@ -206,6 +371,10 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
setStakeAmount('');
|
setStakeAmount('');
|
||||||
setSelectedPool(null);
|
setSelectedPool(null);
|
||||||
setSelectedToken(null);
|
setSelectedToken(null);
|
||||||
|
setRedeemAll(false);
|
||||||
|
setActualBurnAmounts(null);
|
||||||
|
setActualBurnSwapAmounts(null);
|
||||||
|
setActualSwapMintAmounts(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -241,8 +410,8 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
{isPoolDropdownOpen && (
|
{isPoolDropdownOpen && (
|
||||||
<div className="absolute z-50 w-full mt-2 bg-popover border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
<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 ? (
|
{filteredPools && filteredPools.length > 0 ? (
|
||||||
poolDetails.map((pool) => (
|
filteredPools.map((pool) => (
|
||||||
<button
|
<button
|
||||||
key={pool.address}
|
key={pool.address}
|
||||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none"
|
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);
|
setSelectedToken(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex justify-between items-center w-full">
|
||||||
<span className="font-medium">{pool.symbol}</span>
|
<div className="flex flex-col">
|
||||||
<span className="text-xs text-muted-foreground">{pool.name}</span>
|
<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>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
@@ -281,7 +462,14 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
>
|
>
|
||||||
{selectedPool ? (
|
{selectedPool ? (
|
||||||
<div className="flex flex-col items-start">
|
<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>
|
<span className="text-xs text-muted-foreground">{selectedPool.name}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -291,8 +479,8 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
{isPoolDropdownOpen && (
|
{isPoolDropdownOpen && (
|
||||||
<div className="absolute z-50 w-full mt-2 bg-popover border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
<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 ? (
|
{filteredPools && filteredPools.length > 0 ? (
|
||||||
poolDetails.map((pool) => (
|
filteredPools.map((pool) => (
|
||||||
<button
|
<button
|
||||||
key={pool.address}
|
key={pool.address}
|
||||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none"
|
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);
|
setSelectedToken(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex justify-between items-center w-full gap-2">
|
||||||
<span className="font-medium">{pool.symbol}</span>
|
<div className="flex flex-col flex-1 min-w-0">
|
||||||
<span className="text-xs text-muted-foreground">{pool.name}</span>
|
<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>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
@@ -395,9 +602,11 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="w-full h-16 justify-between"
|
className="w-full h-16 justify-between"
|
||||||
onClick={() => setIsTokenDropdownOpen(!isTokenDropdownOpen)}
|
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>
|
<span className="font-medium">{selectedToken.symbol}</span>
|
||||||
) : (
|
) : (
|
||||||
t('stake.selectToken')
|
t('stake.selectToken')
|
||||||
@@ -407,7 +616,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
<div className="text-xs text-muted-foreground text-right mt-1">
|
<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'}
|
{t('swap.balance')}: {lpBalance !== null && selectedPool ? formatUnits(lpBalance, 18) : '0.00'} {selectedPool?.symbol || 'LP'}
|
||||||
</div>
|
</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">
|
<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.length > 0 ? (
|
||||||
availableTokensForPool.map((token) => (
|
availableTokensForPool.map((token) => (
|
||||||
@@ -441,21 +650,42 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center text-sm">
|
<div className="flex justify-between items-center text-sm">
|
||||||
<label className="text-muted-foreground">{t('stake.amount')}</label>
|
<label className="text-muted-foreground">{t('stake.amount')}</label>
|
||||||
<Button
|
<div className="flex gap-2">
|
||||||
variant="ghost"
|
{mode === 'unstake' && (
|
||||||
size="sm"
|
<Button
|
||||||
className="h-6 px-2 text-xs"
|
variant={redeemAll ? 'default' : 'outline'}
|
||||||
onClick={() => {
|
size="sm"
|
||||||
if (mode === 'stake' && selectedToken) {
|
className="h-6 px-2 text-xs"
|
||||||
setStakeAmount(formatUnits(selectedToken.balance, selectedToken.decimals));
|
onClick={() => {
|
||||||
} else if (mode === 'unstake' && lpBalance !== null) {
|
// Prevent toggling off for killed pools
|
||||||
setStakeAmount(formatUnits(lpBalance, 18));
|
if (selectedPool?.isKilled) return;
|
||||||
}
|
setRedeemAll(!redeemAll);
|
||||||
}}
|
if (!redeemAll && lpBalance !== null) {
|
||||||
disabled={!selectedToken || !selectedPool || (mode === 'stake' ? !selectedToken : lpBalance === null)}
|
setStakeAmount(formatUnits(lpBalance, 18));
|
||||||
>
|
}
|
||||||
MAX
|
}}
|
||||||
</Button>
|
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>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -463,7 +693,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
value={stakeAmount}
|
value={stakeAmount}
|
||||||
onChange={(e) => setStakeAmount(e.target.value)}
|
onChange={(e) => setStakeAmount(e.target.value)}
|
||||||
className="text-2xl h-16"
|
className="text-2xl h-16"
|
||||||
disabled={!selectedToken || !selectedPool}
|
disabled={!selectedPool || (mode === 'stake' && !selectedToken)}
|
||||||
/>
|
/>
|
||||||
{isAmountExceedingBalance && (
|
{isAmountExceedingBalance && (
|
||||||
<p className="text-sm text-destructive">
|
<p className="text-sm text-destructive">
|
||||||
@@ -496,8 +726,60 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Error messages for output zero */}
|
||||||
|
{mode === 'stake' && swapMintError && stakeAmount && (
|
||||||
|
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||||
|
<p className="text-sm text-destructive font-medium">⚠️ Cannot Process Stake</p>
|
||||||
|
<p className="text-xs text-destructive/80 mt-1">
|
||||||
|
{swapMintError}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === 'unstake' && !redeemAll && burnSwapError && stakeAmount && (
|
||||||
|
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||||
|
<p className="text-sm text-destructive font-medium">⚠️ Cannot Process Unstake</p>
|
||||||
|
<p className="text-xs text-destructive/80 mt-1">
|
||||||
|
{burnSwapError}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Slippage warnings - consolidated for both stake and unstake modes */}
|
||||||
|
{mode === 'stake' && !swapMintError && slippageExceedsLimit && swapMintAmounts?.calculatedSlippage !== undefined && (
|
||||||
|
<SlippageWarning
|
||||||
|
slippage={swapMintAmounts.calculatedSlippage}
|
||||||
|
action="stake"
|
||||||
|
isError={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === 'stake' && !swapMintError && !slippageExceedsLimit && slippageIsHigh && swapMintAmounts?.calculatedSlippage !== undefined && (
|
||||||
|
<SlippageWarning
|
||||||
|
slippage={swapMintAmounts.calculatedSlippage}
|
||||||
|
action="stake"
|
||||||
|
isError={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === 'unstake' && !redeemAll && !burnSwapError && unstakeSlippageExceedsLimit && burnSwapAmounts?.calculatedSlippage !== undefined && (
|
||||||
|
<SlippageWarning
|
||||||
|
slippage={burnSwapAmounts.calculatedSlippage}
|
||||||
|
action="unstake"
|
||||||
|
isError={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === 'unstake' && !redeemAll && !burnSwapError && !unstakeSlippageExceedsLimit && slippageIsHigh && burnSwapAmounts?.calculatedSlippage !== undefined && (
|
||||||
|
<SlippageWarning
|
||||||
|
slippage={burnSwapAmounts.calculatedSlippage}
|
||||||
|
action="unstake"
|
||||||
|
isError={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Burn Swap Amounts Display (Unstake Mode) */}
|
{/* Burn Swap Amounts Display (Unstake Mode) */}
|
||||||
{mode === 'unstake' && burnSwapAmounts && selectedToken && !isAmountExceedingBalance && (
|
{mode === 'unstake' && !redeemAll && burnSwapAmounts && selectedToken && !isAmountExceedingBalance && (
|
||||||
<div className="px-4 py-3 bg-muted/30 rounded-lg space-y-2">
|
<div className="px-4 py-3 bg-muted/30 rounded-lg space-y-2">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-muted-foreground">{t('stake.amountOut')}:</span>
|
<span className="text-muted-foreground">{t('stake.amountOut')}:</span>
|
||||||
@@ -514,17 +796,51 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
</div>
|
</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 */}
|
{/* Stake/Unstake Button */}
|
||||||
<Button
|
<Button
|
||||||
className="w-full h-14 text-lg"
|
className="w-full h-14 text-lg"
|
||||||
onClick={handleStake}
|
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
|
{!isConnected
|
||||||
? t('swap.connectWalletToSwap')
|
? t('swap.connectWalletToSwap')
|
||||||
: (isSwapMinting || isBurnSwapping)
|
: (mode === 'stake' && isSwapMinting)
|
||||||
? mode === 'stake' ? 'Staking...' : 'Unstaking...'
|
? 'Staking...'
|
||||||
: mode === 'stake' ? t('stake.stakeButton') : 'Unstake'}
|
: (mode === 'unstake' && (isBurnSwapping || isBurning))
|
||||||
|
? (redeemAll ? 'Redeeming...' : 'Unstaking...')
|
||||||
|
: mode === 'stake'
|
||||||
|
? t('stake.stakeButton')
|
||||||
|
: (redeemAll ? 'Redeem All' : 'Unstake')}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
@@ -536,12 +852,14 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<Loader2 className="h-16 w-16 animate-spin text-primary" />
|
<Loader2 className="h-16 w-16 animate-spin text-primary" />
|
||||||
<h3 className="text-xl font-semibold text-center">
|
<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>
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground text-center">
|
<p className="text-sm text-muted-foreground text-center">
|
||||||
{mode === 'stake'
|
{mode === 'stake'
|
||||||
? `Staking ${stakeAmount} ${selectedToken?.symbol} to ${selectedPool?.symbol}`
|
? `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>
|
||||||
<p className="text-xs text-muted-foreground text-center">
|
<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">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<CheckCircle className="h-16 w-16 text-green-500" />
|
<CheckCircle className="h-16 w-16 text-green-500" />
|
||||||
<h3 className="text-xl font-semibold text-center">
|
<h3 className="text-xl font-semibold text-center">
|
||||||
{mode === 'stake' ? 'Stake Confirmed!' : 'Unstake Confirmed!'}
|
{mode === 'stake' ? 'Stake Confirmed!' : redeemAll ? 'Redeem Confirmed!' : 'Unstake Confirmed!'}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Display actual amounts or estimates */}
|
{/* 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>
|
<span className="font-medium">{formatUnits(actualSwapMintAmounts.lpMinted, 18)} {selectedPool.symbol}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-muted-foreground">LP Fee:</span>
|
<span className="text-muted-foreground">Fee:</span>
|
||||||
<span className="font-medium">{formatUnits(actualSwapMintAmounts.lpFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
<span className="font-medium">{formatUnits(actualSwapMintAmounts.lpFee + actualSwapMintAmounts.protocolFee, 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>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -594,6 +908,44 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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
|
// Unstake mode success message
|
||||||
<div className="w-full space-y-3">
|
<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>
|
<span className="font-medium">{formatUnits(actualBurnSwapAmounts.amountOut, selectedToken.decimals)} {selectedToken.symbol}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-muted-foreground">LP Fee:</span>
|
<span className="text-muted-foreground">Fee:</span>
|
||||||
<span className="font-medium">{formatUnits(actualBurnSwapAmounts.lpFee, selectedToken.decimals)} {selectedToken.symbol}</span>
|
<span className="font-medium">{formatUnits(actualBurnSwapAmounts.lpFee + actualBurnSwapAmounts.protocolFee, 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>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { ArrowDownUp, ChevronDown, Settings, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
import { ArrowDownUp, ChevronDown, Settings, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||||
import { useAccount } from 'wagmi';
|
import { useAccount, useChainId } from 'wagmi';
|
||||||
import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner';
|
import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner';
|
||||||
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
|
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
|
||||||
import { formatUnits, parseUnits } from 'viem';
|
import { formatUnits, parseUnits } from 'viem';
|
||||||
import { SwapReviewModal } from './swap-review-modal';
|
import { SwapReviewModal } from './swap-review-modal';
|
||||||
|
import UniswapQuote from './uniswap-quote';
|
||||||
|
|
||||||
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
||||||
|
|
||||||
export function SwapForm() {
|
export function SwapForm() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isConnected, address } = useAccount();
|
const { isConnected, address } = useAccount();
|
||||||
|
const chainId = useChainId();
|
||||||
const [fromAmount, setFromAmount] = useState('');
|
const [fromAmount, setFromAmount] = useState('');
|
||||||
const [toAmount, setToAmount] = useState('');
|
const [toAmount, setToAmount] = useState('');
|
||||||
const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null);
|
const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null);
|
||||||
@@ -36,7 +38,7 @@ export function SwapForm() {
|
|||||||
const { tokenDetails, loading } = useTokenDetails(address);
|
const { tokenDetails, loading } = useTokenDetails(address);
|
||||||
|
|
||||||
// Get available tokens for the selected "from" token
|
// 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
|
// Only calculate swap amounts when both tokens are selected
|
||||||
// Use useMemo to prevent creating a new array reference on every render
|
// Use useMemo to prevent creating a new array reference on every render
|
||||||
@@ -60,17 +62,44 @@ export function SwapForm() {
|
|||||||
currentSlippage
|
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
|
// Initialize swap hook
|
||||||
const { executeSwap, estimateGas, isSwapping, gasEstimate, isEstimatingGas } = useSwap();
|
const { executeSwap, estimateGas, isSwapping, gasEstimate, isEstimatingGas } = useSwap();
|
||||||
|
|
||||||
// Update "You Receive" amount when swap calculation completes
|
// Update "You Receive" amount when swap calculation completes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (hasInsufficientBalance) {
|
||||||
|
setToAmount('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (swapAmounts && swapAmounts.length > 0 && selectedToToken) {
|
if (swapAmounts && swapAmounts.length > 0 && selectedToToken) {
|
||||||
const swapResult = swapAmounts[0]; // Get the first (and should be only) result
|
const swapResult = swapAmounts[0]; // Get the first (and should be only) result
|
||||||
const formattedAmount = formatUnits(swapResult.amountOut, selectedToToken.decimals);
|
const formattedAmount = formatUnits(swapResult.amountOut, selectedToToken.decimals);
|
||||||
setToAmount(formattedAmount);
|
setToAmount(formattedAmount);
|
||||||
|
} else {
|
||||||
|
setToAmount('');
|
||||||
}
|
}
|
||||||
}, [swapAmounts, selectedToToken]);
|
}, [swapAmounts, selectedToToken, hasInsufficientBalance]);
|
||||||
|
|
||||||
// Close dropdowns when clicking outside
|
// Close dropdowns when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -284,6 +313,7 @@ export function SwapForm() {
|
|||||||
onChange={(e) => setToAmount(e.target.value)}
|
onChange={(e) => setToAmount(e.target.value)}
|
||||||
className="text-2xl h-16"
|
className="text-2xl h-16"
|
||||||
disabled={!selectedFromToken}
|
disabled={!selectedFromToken}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
<div className="relative min-w-[160px] space-y-1" ref={toDropdownRef}>
|
<div className="relative min-w-[160px] space-y-1" ref={toDropdownRef}>
|
||||||
<Button
|
<Button
|
||||||
@@ -326,7 +356,7 @@ export function SwapForm() {
|
|||||||
))
|
))
|
||||||
) : selectedFromToken ? (
|
) : selectedFromToken ? (
|
||||||
<div className="px-4 py-3 text-sm text-muted-foreground">
|
<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>
|
||||||
) : (
|
) : (
|
||||||
<div className="px-4 py-3 text-sm text-muted-foreground">
|
<div className="px-4 py-3 text-sm text-muted-foreground">
|
||||||
@@ -339,6 +369,56 @@ export function SwapForm() {
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Gas Estimate, Slippage, and Fees */}
|
||||||
{isConnected && fromAmount && toAmount && (
|
{isConnected && fromAmount && toAmount && (
|
||||||
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">
|
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">
|
||||||
@@ -377,10 +457,14 @@ export function SwapForm() {
|
|||||||
<Button
|
<Button
|
||||||
className="w-full h-14 text-lg"
|
className="w-full h-14 text-lg"
|
||||||
onClick={() => setIsReviewModalOpen(true)}
|
onClick={() => setIsReviewModalOpen(true)}
|
||||||
disabled={!isConnected || !fromAmount || !toAmount}
|
disabled={!isConnected || !fromAmount || !toAmount || !!poolsError || hasInsufficientBalance || slippageExceedsLimit}
|
||||||
>
|
>
|
||||||
{!isConnected
|
{!isConnected
|
||||||
? t('swap.connectWalletToSwap')
|
? t('swap.connectWalletToSwap')
|
||||||
|
: hasInsufficientBalance
|
||||||
|
? 'Insufficient Balance'
|
||||||
|
: slippageExceedsLimit
|
||||||
|
? 'Slippage Too High'
|
||||||
: 'Review'}
|
: 'Review'}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -424,7 +508,7 @@ export function SwapForm() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mt-2">
|
<p className="text-xs text-muted-foreground mt-2">
|
||||||
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -487,12 +571,8 @@ export function SwapForm() {
|
|||||||
<span className="font-medium">{formatUnits(actualSwapAmounts.amountOut, selectedToToken.decimals)} {selectedToToken.symbol}</span>
|
<span className="font-medium">{formatUnits(actualSwapAmounts.amountOut, selectedToToken.decimals)} {selectedToToken.symbol}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-muted-foreground">LP Fee:</span>
|
<span className="text-muted-foreground">Fee:</span>
|
||||||
<span className="font-medium">{formatUnits(actualSwapAmounts.lpFee, selectedFromToken.decimals)} {selectedFromToken.symbol}</span>
|
<span className="font-medium">{formatUnits(actualSwapAmounts.lpFee + actualSwapAmounts.protocolFee, 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>
|
|
||||||
</div>
|
</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"
|
"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",
|
"type": "function",
|
||||||
"name": "newPool",
|
"name": "newPool",
|
||||||
|
|||||||
@@ -324,25 +324,6 @@ const IPartyPoolABI = [
|
|||||||
],
|
],
|
||||||
"stateMutability": "nonpayable"
|
"stateMutability": "nonpayable"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "function",
|
|
||||||
"name": "getToken",
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256",
|
|
||||||
"internalType": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract IERC20"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "initialMint",
|
"name": "initialMint",
|
||||||
@@ -434,6 +415,19 @@ const IPartyPoolABI = [
|
|||||||
],
|
],
|
||||||
"stateMutability": "payable"
|
"stateMutability": "payable"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "mintImpl",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -515,6 +509,11 @@ const IPartyPoolABI = [
|
|||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "address"
|
"internalType": "address"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "fundingSelector",
|
||||||
|
"type": "bytes4",
|
||||||
|
"internalType": "bytes4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "receiver",
|
"name": "receiver",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
@@ -549,6 +548,11 @@ const IPartyPoolABI = [
|
|||||||
"name": "unwrap",
|
"name": "unwrap",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"internalType": "bool"
|
"internalType": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cbData",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -614,6 +618,19 @@ const IPartyPoolABI = [
|
|||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "swapImpl",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "swapMint",
|
"name": "swapMint",
|
||||||
@@ -672,6 +689,11 @@ const IPartyPoolABI = [
|
|||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "address"
|
"internalType": "address"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "fundingSelector",
|
||||||
|
"type": "bytes4",
|
||||||
|
"internalType": "bytes4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "receiver",
|
"name": "receiver",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
@@ -701,6 +723,11 @@ const IPartyPoolABI = [
|
|||||||
"name": "unwrap",
|
"name": "unwrap",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"internalType": "bool"
|
"internalType": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cbData",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -735,6 +762,25 @@ const IPartyPoolABI = [
|
|||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "token",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract IERC20"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "totalSupply",
|
"name": "totalSupply",
|
||||||
|
|||||||
@@ -6,8 +6,22 @@ import chainInfo from '@/contracts/liqp-deployments.json';
|
|||||||
import IPartyPlannerABI from '@/contracts/IPartyPlannerABI';
|
import IPartyPlannerABI from '@/contracts/IPartyPlannerABI';
|
||||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||||
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
|
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
|
||||||
|
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
|
||||||
import { ERC20ABI } from '@/contracts/ERC20ABI';
|
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) {
|
export function useGetAllTokens(offset: number = 0, limit: number = 100) {
|
||||||
const publicClient = usePublicClient();
|
const publicClient = usePublicClient();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -35,7 +49,8 @@ export function useGetAllTokens(offset: number = 0, limit: number = 100) {
|
|||||||
|
|
||||||
// Get chain ID and contract address
|
// Get chain ID and contract address
|
||||||
const chainId = await publicClient.getChainId();
|
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) {
|
if (!address) {
|
||||||
setError('IPartyPlanner contract not found for current chain');
|
setError('IPartyPlanner contract not found for current chain');
|
||||||
@@ -84,6 +99,8 @@ export interface SwapRoute {
|
|||||||
poolAddress: `0x${string}`;
|
poolAddress: `0x${string}`;
|
||||||
inputTokenIndex: number;
|
inputTokenIndex: number;
|
||||||
outputTokenIndex: number;
|
outputTokenIndex: number;
|
||||||
|
inputTokenDecimal: number;
|
||||||
|
outputTokenDecimal: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvailableToken {
|
export interface AvailableToken {
|
||||||
@@ -120,19 +137,20 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Get chain ID and contract address
|
// Get chain ID and contract addresses
|
||||||
const chainId = await publicClient.getChainId();
|
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) {
|
if (!plannerAddress || !partyInfoAddress) {
|
||||||
setError('IPartyPlanner contract not found for current chain');
|
setError('IPartyPlanner or PartyInfo contract not found for current chain');
|
||||||
setAvailableTokens([]);
|
setAvailableTokens([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call getPoolsByToken function
|
// Call getPoolsByToken function
|
||||||
const poolsResult = await publicClient.readContract({
|
const poolsResult = await publicClient.readContract({
|
||||||
address: address as `0x${string}`,
|
address: plannerAddress as `0x${string}`,
|
||||||
abi: IPartyPlannerABI,
|
abi: IPartyPlannerABI,
|
||||||
functionName: 'getPoolsByToken',
|
functionName: 'getPoolsByToken',
|
||||||
args: [tokenAddress, BigInt(offset), BigInt(limit)],
|
args: [tokenAddress, BigInt(offset), BigInt(limit)],
|
||||||
@@ -150,68 +168,161 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
|
|||||||
return;
|
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
|
// Map to store available tokens with their swap routes
|
||||||
const tokenRoutesMap = new Map<string, AvailableToken>();
|
const tokenRoutesMap = new Map<string, AvailableToken>();
|
||||||
|
|
||||||
// For each pool, fetch all tokens and track indices
|
// For each working pool, process tokens
|
||||||
for (const poolAddress of poolsResult) {
|
for (let poolIdx = 0; poolIdx < workingPools.length; poolIdx++) {
|
||||||
try {
|
const poolAddress = workingPools[poolIdx];
|
||||||
const tokensInPool = await publicClient.readContract({
|
const poolTokensResult = poolTokensResults[poolIdx];
|
||||||
address: poolAddress,
|
|
||||||
abi: IPartyPoolABI,
|
|
||||||
functionName: 'allTokens',
|
|
||||||
}) as readonly `0x${string}`[];
|
|
||||||
|
|
||||||
// Find the input token index in this pool
|
if (poolTokensResult.status !== 'success') {
|
||||||
const inputTokenIndex = tokensInPool.findIndex(
|
console.error('Failed to fetch tokens for pool', poolAddress);
|
||||||
(token) => token.toLowerCase() === tokenAddress.toLowerCase()
|
continue;
|
||||||
);
|
}
|
||||||
|
|
||||||
if (inputTokenIndex === -1) {
|
const tokensInPool = poolTokensResult.result as readonly `0x${string}`[];
|
||||||
console.error('Input token not found in pool', poolAddress);
|
|
||||||
|
// Find the input token index in this pool
|
||||||
|
const inputTokenIndex = tokensInPool.findIndex(
|
||||||
|
(token) => token.toLowerCase() === tokenAddress.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (inputTokenIndex === -1) {
|
||||||
|
console.error('Input token not found in pool', poolAddress);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputTokenData = tokenDataMap.get(tokenAddress.toLowerCase());
|
||||||
|
const inputTokenDecimal = inputTokenData?.decimals ?? null;
|
||||||
|
|
||||||
|
// Process each token in the pool
|
||||||
|
for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) {
|
||||||
|
const outputTokenAddress = tokensInPool[outputTokenIndex];
|
||||||
|
|
||||||
|
// Skip if it's the same as the input token
|
||||||
|
if (outputTokenIndex === inputTokenIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each token in the pool
|
const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase());
|
||||||
for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) {
|
const outputTokenSymbol = outputTokenData?.symbol ?? null;
|
||||||
const outputTokenAddress = tokensInPool[outputTokenIndex];
|
const outputTokenDecimal = outputTokenData?.decimals ?? null;
|
||||||
|
|
||||||
// Skip if it's the same as the input token
|
// Skip tokens with the same symbol as the selected token
|
||||||
if (outputTokenIndex === inputTokenIndex) {
|
if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the symbol of this token
|
// Skip tokens if decimals failed to load
|
||||||
const outputTokenSymbol = await publicClient.readContract({
|
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,
|
address: outputTokenAddress,
|
||||||
abi: ERC20ABI,
|
symbol: outputTokenSymbol,
|
||||||
functionName: 'symbol',
|
swapRoutes: [],
|
||||||
}).catch(() => null);
|
|
||||||
|
|
||||||
// Skip tokens with the same symbol as the selected token
|
|
||||||
if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create or update the available token entry
|
|
||||||
const tokenKey = outputTokenAddress.toLowerCase();
|
|
||||||
if (!tokenRoutesMap.has(tokenKey)) {
|
|
||||||
tokenRoutesMap.set(tokenKey, {
|
|
||||||
address: outputTokenAddress,
|
|
||||||
symbol: outputTokenSymbol,
|
|
||||||
swapRoutes: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this swap route
|
|
||||||
tokenRoutesMap.get(tokenKey)!.swapRoutes.push({
|
|
||||||
poolAddress,
|
|
||||||
inputTokenIndex,
|
|
||||||
outputTokenIndex,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build multicall contracts array - 4 calls per token (name, symbol, decimals, balanceOf)
|
||||||
|
const contracts = tokens.flatMap((tokenAddress) => [
|
||||||
|
{
|
||||||
|
address: tokenAddress,
|
||||||
|
abi: ERC20ABI,
|
||||||
|
functionName: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: tokenAddress,
|
||||||
|
abi: ERC20ABI,
|
||||||
|
functionName: 'symbol',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: tokenAddress,
|
||||||
|
abi: ERC20ABI,
|
||||||
|
functionName: 'decimals',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: tokenAddress,
|
||||||
|
abi: ERC20ABI,
|
||||||
|
functionName: 'balanceOf',
|
||||||
|
args: [userAddress],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Execute multicall
|
||||||
|
const results = await publicClient.multicall({
|
||||||
|
contracts: contracts as any,
|
||||||
|
allowFailure: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse results
|
||||||
const details: TokenDetails[] = [];
|
const details: TokenDetails[] = [];
|
||||||
|
|
||||||
// Make individual calls for each token
|
|
||||||
for (let i = 0; i < tokens.length; i++) {
|
for (let i = 0; i < tokens.length; i++) {
|
||||||
const tokenAddress = tokens[i];
|
const baseIndex = i * 4;
|
||||||
try {
|
const nameResult = results[baseIndex];
|
||||||
const [name, symbol, decimals, balance] = await Promise.all([
|
const symbolResult = results[baseIndex + 1];
|
||||||
publicClient.readContract({
|
const decimalsResult = results[baseIndex + 2];
|
||||||
address: tokenAddress,
|
const balanceResult = results[baseIndex + 3];
|
||||||
abi: ERC20ABI,
|
|
||||||
functionName: 'name',
|
|
||||||
}).catch(() => 'Unknown'),
|
|
||||||
publicClient.readContract({
|
|
||||||
address: tokenAddress,
|
|
||||||
abi: ERC20ABI,
|
|
||||||
functionName: 'symbol',
|
|
||||||
}).catch(() => '???'),
|
|
||||||
publicClient.readContract({
|
|
||||||
address: tokenAddress,
|
|
||||||
abi: ERC20ABI,
|
|
||||||
functionName: 'decimals',
|
|
||||||
}).catch(() => 18),
|
|
||||||
publicClient.readContract({
|
|
||||||
address: tokenAddress,
|
|
||||||
abi: ERC20ABI,
|
|
||||||
functionName: 'balanceOf',
|
|
||||||
args: [userAddress],
|
|
||||||
}).catch(() => BigInt(0)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
details.push({
|
details.push({
|
||||||
address: tokenAddress,
|
address: tokens[i],
|
||||||
name: name as string,
|
name: nameResult.status === 'success' ? (nameResult.result as string) : 'Unknown',
|
||||||
symbol: symbol as string,
|
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : '???',
|
||||||
decimals: Number(decimals),
|
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18,
|
||||||
balance: balance as bigint,
|
balance: balanceResult.status === 'success' ? (balanceResult.result as bigint) : BigInt(0),
|
||||||
index: i,
|
index: i,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
// Add token with fallback values if individual call fails
|
|
||||||
details.push({
|
|
||||||
address: tokenAddress,
|
|
||||||
name: 'Unknown',
|
|
||||||
symbol: '???',
|
|
||||||
decimals: 18,
|
|
||||||
balance: BigInt(0),
|
|
||||||
index: i,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTokenDetails(details);
|
setTokenDetails(details);
|
||||||
@@ -332,6 +442,9 @@ export interface PoolDetails {
|
|||||||
name: string;
|
name: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
tokens: readonly `0x${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) {
|
export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
||||||
@@ -360,12 +473,13 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Get chain ID and contract address
|
// Get chain ID and contract addresses
|
||||||
const chainId = await publicClient.getChainId();
|
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) {
|
if (!plannerAddress || !partyInfoAddress) {
|
||||||
setError('IPartyPlanner contract not found for current chain');
|
setError('IPartyPlanner or PartyInfo contract not found for current chain');
|
||||||
setPools([]);
|
setPools([]);
|
||||||
setPoolDetails([]);
|
setPoolDetails([]);
|
||||||
return;
|
return;
|
||||||
@@ -373,7 +487,7 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
|||||||
|
|
||||||
// Call getAllPools function
|
// Call getAllPools function
|
||||||
const result = await publicClient.readContract({
|
const result = await publicClient.readContract({
|
||||||
address: address as `0x${string}`,
|
address: plannerAddress as `0x${string}`,
|
||||||
abi: IPartyPlannerABI,
|
abi: IPartyPlannerABI,
|
||||||
functionName: 'getAllPools',
|
functionName: 'getAllPools',
|
||||||
args: [BigInt(offset), BigInt(limit)],
|
args: [BigInt(offset), BigInt(limit)],
|
||||||
@@ -381,11 +495,11 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
|||||||
|
|
||||||
setPools(result);
|
setPools(result);
|
||||||
|
|
||||||
// Fetch details for each pool
|
// Fetch details for each pool and check if it's working
|
||||||
const details: PoolDetails[] = [];
|
const details: PoolDetails[] = [];
|
||||||
for (const poolAddress of result) {
|
for (const poolAddress of result) {
|
||||||
try {
|
try {
|
||||||
const [name, symbol, tokens] = await Promise.all([
|
const [name, symbol, tokens, isWorking] = await Promise.all([
|
||||||
publicClient.readContract({
|
publicClient.readContract({
|
||||||
address: poolAddress,
|
address: poolAddress,
|
||||||
abi: ERC20ABI,
|
abi: ERC20ABI,
|
||||||
@@ -401,23 +515,83 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
|||||||
abi: IPartyPoolABI,
|
abi: IPartyPoolABI,
|
||||||
functionName: 'allTokens',
|
functionName: 'allTokens',
|
||||||
}).catch(() => [] as readonly `0x${string}`[]),
|
}).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({
|
details.push({
|
||||||
address: poolAddress,
|
address: poolAddress,
|
||||||
name: name as string,
|
name: name as string,
|
||||||
symbol: symbol as string,
|
symbol: symbol as string,
|
||||||
tokens: tokens as readonly `0x${string}`[],
|
tokens: tokens as readonly `0x${string}`[],
|
||||||
|
price: priceStr,
|
||||||
|
tvl: tvlStr,
|
||||||
|
isKilled: !isWorking,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching pool details for', poolAddress, err);
|
console.error('Error fetching pool details for', poolAddress, err);
|
||||||
// Add pool with fallback values
|
// Skip pools that fail to load
|
||||||
details.push({
|
|
||||||
address: poolAddress,
|
|
||||||
name: 'Unknown Pool',
|
|
||||||
symbol: 'POOL',
|
|
||||||
tokens: [],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,17 +620,20 @@ export interface SwapMintAmounts {
|
|||||||
amountInUsed: bigint;
|
amountInUsed: bigint;
|
||||||
fee: bigint;
|
fee: bigint;
|
||||||
lpMinted: bigint;
|
lpMinted: bigint;
|
||||||
|
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BurnSwapAmounts {
|
export interface BurnSwapAmounts {
|
||||||
amountOut: bigint;
|
amountOut: bigint;
|
||||||
outFee: bigint;
|
outFee: bigint;
|
||||||
|
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSwapMintAmounts(
|
export function useSwapMintAmounts(
|
||||||
poolAddress: `0x${string}` | undefined,
|
poolAddress: `0x${string}` | undefined,
|
||||||
inputTokenIndex: number | undefined,
|
inputTokenIndex: number | undefined,
|
||||||
maxAmountIn: bigint | undefined
|
maxAmountIn: bigint | undefined,
|
||||||
|
inputTokenDecimals: number | undefined // Decimals of the input token
|
||||||
) {
|
) {
|
||||||
const publicClient = usePublicClient();
|
const publicClient = usePublicClient();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -477,8 +654,11 @@ export function useSwapMintAmounts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchSwapMintAmounts = async () => {
|
const fetchSwapMintAmounts = async () => {
|
||||||
if (!publicClient) {
|
if (!publicClient) return;
|
||||||
setLoading(false);
|
|
||||||
|
// Early validation
|
||||||
|
if (inputTokenDecimals === undefined) {
|
||||||
|
setError('inputTokenDecimals is required but was undefined');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,32 +666,58 @@ export function useSwapMintAmounts(
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Get chain ID and contract address
|
|
||||||
const chainId = await publicClient.getChainId();
|
const chainId = await publicClient.getChainId();
|
||||||
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) {
|
if (!partyInfoAddress) {
|
||||||
setError('IPartyPoolViewer contract not found for current chain');
|
setError('PartyInfo contract not found for current chain');
|
||||||
setSwapMintAmounts(null);
|
setSwapMintAmounts(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call swapMintAmounts function
|
|
||||||
const result = await publicClient.readContract({
|
const result = await publicClient.readContract({
|
||||||
address: viewerAddress as `0x${string}`,
|
address: partyInfoAddress as `0x${string}`,
|
||||||
abi: IPartyPoolViewerABI,
|
abi: IPartyPoolViewerABI,
|
||||||
functionName: 'swapMintAmounts',
|
functionName: 'swapMintAmounts',
|
||||||
args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn],
|
args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn],
|
||||||
}) as readonly [bigint, bigint, bigint];
|
}) as readonly [bigint, bigint, bigint];
|
||||||
|
|
||||||
|
// Fetch and calculate pool price
|
||||||
|
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({
|
setSwapMintAmounts({
|
||||||
amountInUsed: result[0],
|
amountInUsed: result[0],
|
||||||
fee: result[1],
|
fee: result[2],
|
||||||
lpMinted: result[2],
|
lpMinted: result[1],
|
||||||
|
calculatedSlippage,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error calling swapMintAmounts:', 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);
|
setSwapMintAmounts(null);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -519,7 +725,7 @@ export function useSwapMintAmounts(
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchSwapMintAmounts();
|
fetchSwapMintAmounts();
|
||||||
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn]);
|
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, inputTokenDecimals]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
swapMintAmounts,
|
swapMintAmounts,
|
||||||
@@ -532,7 +738,8 @@ export function useSwapMintAmounts(
|
|||||||
export function useBurnSwapAmounts(
|
export function useBurnSwapAmounts(
|
||||||
poolAddress: `0x${string}` | undefined,
|
poolAddress: `0x${string}` | undefined,
|
||||||
lpAmount: bigint | undefined,
|
lpAmount: bigint | undefined,
|
||||||
inputTokenIndex: number | undefined
|
inputTokenIndex: number | undefined,
|
||||||
|
tokenDecimals: number | undefined // Decimals of the output token
|
||||||
) {
|
) {
|
||||||
const publicClient = usePublicClient();
|
const publicClient = usePublicClient();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -561,57 +768,66 @@ export function useBurnSwapAmounts(
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Get chain ID and contract address
|
// Get chain ID and contract address
|
||||||
const chainId = await publicClient.getChainId();
|
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) {
|
if (!partyInfoAddress) {
|
||||||
setError('IPartyPoolViewer contract not found for current chain');
|
setError('PartyInfo contract not found for current chain');
|
||||||
setBurnSwapAmounts(null);
|
setBurnSwapAmounts(null);
|
||||||
return;
|
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]
|
// Call burnSwapAmounts function - returns [amountOut, outFee]
|
||||||
const result = await publicClient.readContract({
|
const result = await publicClient.readContract({
|
||||||
address: viewerAddress as `0x${string}`,
|
address: partyInfoAddress as `0x${string}`,
|
||||||
abi: IPartyPoolViewerABI,
|
abi: IPartyPoolViewerABI,
|
||||||
functionName: 'burnSwapAmounts',
|
functionName: 'burnSwapAmounts',
|
||||||
args: [poolAddress, lpAmount, BigInt(inputTokenIndex)],
|
args: [poolAddress, lpAmount, BigInt(inputTokenIndex)],
|
||||||
}) as readonly [bigint, bigint];
|
}) as readonly [bigint, bigint];
|
||||||
|
|
||||||
// Log raw result
|
// Calculate slippage for burnSwap using poolPrice
|
||||||
console.log('📊 burnSwapAmounts RAW RESULT:', {
|
let calculatedSlippage: number | undefined;
|
||||||
resultArray: result,
|
if (tokenDecimals !== undefined) {
|
||||||
amountOut: result[0].toString(),
|
try {
|
||||||
outFee: result[1].toString(),
|
// 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 = {
|
const parsedAmounts = {
|
||||||
amountOut: result[0],
|
amountOut: result[0],
|
||||||
outFee: result[1],
|
outFee: result[1],
|
||||||
|
calculatedSlippage,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log parsed result
|
|
||||||
console.log('✅ burnSwapAmounts PARSED:', {
|
|
||||||
amountOut: parsedAmounts.amountOut.toString(),
|
|
||||||
outFee: parsedAmounts.outFee.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
setBurnSwapAmounts(parsedAmounts);
|
setBurnSwapAmounts(parsedAmounts);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error calling burnSwapAmounts:', 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);
|
setBurnSwapAmounts(null);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -619,7 +835,7 @@ export function useBurnSwapAmounts(
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchBurnSwapAmounts();
|
fetchBurnSwapAmounts();
|
||||||
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex]);
|
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, tokenDecimals]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
burnSwapAmounts,
|
burnSwapAmounts,
|
||||||
@@ -689,3 +905,73 @@ export function useLPTokenBalance(
|
|||||||
isReady: mounted,
|
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 { useState, useEffect, useCallback } from 'react';
|
||||||
import { usePublicClient, useWalletClient } from 'wagmi';
|
import { usePublicClient, useWalletClient } from 'wagmi';
|
||||||
import { decodeEventLog } from 'viem';
|
import { decodeEventLog, parseUnits } from 'viem';
|
||||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
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';
|
import type { AvailableToken } from './usePartyPlanner';
|
||||||
|
|
||||||
// Q96 constant for price calculations
|
// Q96 constant for price calculations
|
||||||
const Q96 = 1n << 96n;
|
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 {
|
export interface SwapAmountResult {
|
||||||
tokenAddress: `0x${string}`;
|
tokenAddress: `0x${string}`;
|
||||||
tokenSymbol: string;
|
tokenSymbol: string;
|
||||||
@@ -20,6 +43,7 @@ export interface SwapAmountResult {
|
|||||||
kappa: bigint;
|
kappa: bigint;
|
||||||
inputTokenIndex: number;
|
inputTokenIndex: number;
|
||||||
outputTokenIndex: number;
|
outputTokenIndex: number;
|
||||||
|
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,22 +119,11 @@ export function useSwapAmounts(
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const amountInWei = BigInt(Math.floor(parseFloat(fromAmount) * Math.pow(10, fromTokenDecimals)));
|
const amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals);
|
||||||
|
|
||||||
// Calculate limit price based on slippage tolerance
|
|
||||||
// limitPrice in Q96 format = Q96 * (100 + slippage%) / 100
|
|
||||||
// This represents the maximum acceptable price ratio (1 + slippage%)
|
|
||||||
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100)); // Convert to basis points (0.5% = 50)
|
|
||||||
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
|
|
||||||
|
|
||||||
console.log('Limit Price Calculation:', {
|
|
||||||
slippagePercent,
|
|
||||||
slippageBasisPoints: slippageBasisPoints.toString(),
|
|
||||||
limitPriceQ96: limitPrice.toString(),
|
|
||||||
Q96: Q96.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const results: SwapAmountResult[] = [];
|
const results: SwapAmountResult[] = [];
|
||||||
|
const chainId = await publicClient.getChainId();
|
||||||
|
const partyInfoAddress = (chainInfo as any)[chainId]?.v1?.PartyInfo as `0x${string}` | undefined;
|
||||||
|
|
||||||
// Calculate swap amounts for ALL routes of each token
|
// Calculate swap amounts for ALL routes of each token
|
||||||
for (const token of availableTokens) {
|
for (const token of availableTokens) {
|
||||||
@@ -121,21 +134,19 @@ export function useSwapAmounts(
|
|||||||
// Evaluate ALL routes for this token
|
// Evaluate ALL routes for this token
|
||||||
for (const route of token.swapRoutes) {
|
for (const route of token.swapRoutes) {
|
||||||
try {
|
try {
|
||||||
// Get swap amounts
|
// Get swap amounts with NO LIMIT
|
||||||
const swapResult = await publicClient.readContract({
|
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({
|
||||||
address: route.poolAddress,
|
address: route.poolAddress,
|
||||||
abi: IPartyPoolABI,
|
abi: IPartyPoolABI,
|
||||||
functionName: 'swapAmounts',
|
functionName: 'swapAmounts',
|
||||||
args: [
|
args: [
|
||||||
BigInt(route.inputTokenIndex),
|
BigInt(route.inputTokenIndex),
|
||||||
BigInt(route.outputTokenIndex),
|
BigInt(route.outputTokenIndex),
|
||||||
amountInWei,
|
amountInTokenUnits,
|
||||||
limitPrice,
|
0n, // NO LIMIT
|
||||||
],
|
],
|
||||||
}) as readonly [bigint, bigint, bigint];
|
}) as readonly [bigint, bigint, bigint];
|
||||||
|
|
||||||
const [amountIn, amountOut, fee] = swapResult;
|
|
||||||
|
|
||||||
// Get kappa for this pool
|
// Get kappa for this pool
|
||||||
const kappa = await publicClient.readContract({
|
const kappa = await publicClient.readContract({
|
||||||
address: route.poolAddress,
|
address: route.poolAddress,
|
||||||
@@ -143,16 +154,46 @@ export function useSwapAmounts(
|
|||||||
functionName: 'kappa',
|
functionName: 'kappa',
|
||||||
}) as bigint;
|
}) 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({
|
routeResults.push({
|
||||||
tokenAddress: token.address,
|
tokenAddress: token.address,
|
||||||
tokenSymbol: token.symbol,
|
tokenSymbol: token.symbol,
|
||||||
amountIn,
|
amountIn: swapInputAmount,
|
||||||
amountOut,
|
amountOut: swapOutputAmount,
|
||||||
fee,
|
fee: inFee,
|
||||||
poolAddress: route.poolAddress,
|
poolAddress: route.poolAddress,
|
||||||
kappa,
|
kappa,
|
||||||
inputTokenIndex: route.inputTokenIndex,
|
inputTokenIndex: route.inputTokenIndex,
|
||||||
outputTokenIndex: route.outputTokenIndex,
|
outputTokenIndex: route.outputTokenIndex,
|
||||||
|
calculatedSlippage,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error calculating swap for ${token.symbol} via ${route.poolAddress}:`, 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 });
|
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
|
||||||
console.log('✅ Approval confirmed');
|
console.log('✅ Approval confirmed');
|
||||||
|
|
||||||
// STEP 2: Calculate limit price and deadline
|
// STEP 2: Calculate deadline
|
||||||
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100));
|
|
||||||
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
|
|
||||||
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200);
|
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200);
|
||||||
|
|
||||||
console.log('🚀 Executing swap with params:', {
|
console.log('🚀 Executing swap with params:', {
|
||||||
@@ -331,25 +370,27 @@ export function useSwap() {
|
|||||||
inputTokenIndex,
|
inputTokenIndex,
|
||||||
outputTokenIndex,
|
outputTokenIndex,
|
||||||
maxAmountIn: maxAmountIn.toString(),
|
maxAmountIn: maxAmountIn.toString(),
|
||||||
limitPrice: limitPrice.toString(),
|
limitPrice: '0 (no limit)',
|
||||||
deadline: deadline.toString(),
|
deadline: deadline.toString(),
|
||||||
unwrap: false,
|
unwrap: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// STEP 3: Execute the swap transaction
|
// STEP 3: Execute the swap transaction with no limit price
|
||||||
const hash = await walletClient.writeContract({
|
const hash = await walletClient.writeContract({
|
||||||
address: poolAddress,
|
address: poolAddress,
|
||||||
abi: IPartyPoolABI,
|
abi: IPartyPoolABI,
|
||||||
functionName: 'swap',
|
functionName: 'swap',
|
||||||
args: [
|
args: [
|
||||||
userAddress, // payer
|
userAddress, // payer
|
||||||
|
'0x00000000', // selector (bytes4(0))
|
||||||
userAddress, // receiver
|
userAddress, // receiver
|
||||||
BigInt(inputTokenIndex),
|
BigInt(inputTokenIndex),
|
||||||
BigInt(outputTokenIndex),
|
BigInt(outputTokenIndex),
|
||||||
maxAmountIn,
|
maxAmountIn,
|
||||||
limitPrice,
|
0n, // no limit price
|
||||||
deadline,
|
deadline,
|
||||||
false, // unwrap
|
false, // unwrap
|
||||||
|
'0x', // cbData (empty bytes)
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -813,6 +854,7 @@ export function useBurn() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (decodedLog.eventName === 'Burn') {
|
if (decodedLog.eventName === 'Burn') {
|
||||||
|
// @ts-ignore
|
||||||
const { amounts, lpBurned } = decodedLog.args as {
|
const { amounts, lpBurned } = decodedLog.args as {
|
||||||
amounts: bigint[];
|
amounts: bigint[];
|
||||||
lpBurned: 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