Compare commits
80 Commits
e2198c9b31
...
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 | |||
| 4ecda7de14 | |||
| 14bad3d11e | |||
| f690799b50 | |||
| 0a518d31f9 | |||
| 9707c4892b | |||
| 1d6ebadcb2 | |||
| ed6426fad7 | |||
| 292f675039 | |||
|
|
41d0336aed | ||
| a30c8f866c | |||
| e08506e72c | |||
| 66e28ed08d | |||
| dbfdfbd4ab | |||
| a0ff77435b | |||
|
|
6e18711de9 | ||
|
|
d1986b3c5f | ||
|
|
c319482a4c | ||
| 6c3fdba8fc | |||
| bf6bef6a58 | |||
| 78cb00f9f5 | |||
| c9ce0d065e | |||
|
|
a1e73df30a | ||
| 380c77b37e | |||
| b5da8ce080 | |||
| caf6bff469 | |||
| cdbf2a57e6 | |||
| f543b27620 | |||
| 7ead103f86 |
4
.gitignore
vendored
@@ -1,9 +1,13 @@
|
||||
/src/contracts/liqp-deployments.json
|
||||
/.idea/
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
*secret*
|
||||
.env
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
|
||||
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
43
bin/deploy
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$1" = "dev" ]; then
|
||||
DEV=1
|
||||
shift
|
||||
else
|
||||
DEV=0
|
||||
fi
|
||||
|
||||
PROJECT=liquidity-party
|
||||
REMOTE=git.dxod.org/dexorder/dexorder
|
||||
|
||||
if [ "$BUILD" != "1" ]; then
|
||||
if [ "$DEV" = "1" ]; then
|
||||
TAG="dev$(date +%Y%m%d%H%M%S)"
|
||||
else
|
||||
DIRTY="$( git status | grep "Changes " )"
|
||||
if [ "$DIRTY" != "" ]; then
|
||||
echo This project has uncommited changes: deployment aborted.
|
||||
exit 1
|
||||
fi
|
||||
TAG="$( git log --oneline | head -1 | cut -d ' ' -f 1 )"
|
||||
fi
|
||||
echo Deploying $TAG
|
||||
else
|
||||
echo Building
|
||||
fi
|
||||
|
||||
npm run build || exit 1
|
||||
|
||||
if [ "$BUILD" != "1" ]; then
|
||||
echo Deploying with tag $TAG
|
||||
docker buildx build --platform linux/amd64 --no-cache -f deploy/Dockerfile -t dexorder/$PROJECT:latest . || exit 1
|
||||
docker tag dexorder/$PROJECT:latest dexorder/$PROJECT:$TAG
|
||||
docker tag dexorder/$PROJECT:$TAG $REMOTE/$PROJECT:$TAG
|
||||
docker tag $REMOTE/$PROJECT:$TAG $REMOTE/$PROJECT:latest
|
||||
docker push $REMOTE/$PROJECT:$TAG
|
||||
YAML=$(sed "s#image: dexorder/$PROJECT*#image: $REMOTE/$PROJECT:$TAG#" deploy/liquidity-party.k8s.yaml)
|
||||
echo "$YAML" | kubectl apply -f - || echo "$YAML" "\nkubectl apply failed" && exit 1
|
||||
echo "$(date)" deployed $REMOTE/$PROJECT:$TAG
|
||||
else
|
||||
echo "$(date)" built
|
||||
fi
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
generate() {
|
||||
ABI=$(jq '.abi' ../lmsr-amm/out/$1.sol/$1.json)
|
||||
# echo "import {useReadContract} from \"wagmi\";\n\nconst {data} = useReadContract({abi: ${ABI}});\n\nexport default data;" > ./src/contracts/$1.ts
|
||||
echo "/* GENERATED FILE: DO NOT EDIT! */\n\nconst ${1}ABI = ${ABI} as const;\n\nexport default ${1}ABI;" > ./src/contracts/$1ABI.ts
|
||||
}
|
||||
|
||||
generate IPartyPlanner
|
||||
generate IPartyPool
|
||||
generate IPartyPoolViewer
|
||||
43
bin/generate-contracts
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
CHAIN_ID=${1:-31337}
|
||||
|
||||
case "$CHAIN_ID" in
|
||||
"sepolia")
|
||||
CHAIN_ID=11155111
|
||||
;;
|
||||
"mockchain")
|
||||
CHAIN_ID=31337
|
||||
;;
|
||||
"mainnet")
|
||||
CHAIN_ID=1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$CHAIN_ID" = "31337" ]; then
|
||||
ABI_PATH=../lmsr-amm/out
|
||||
METADATA_PATH=../lmsr-amm/liqp-deployments.json
|
||||
else
|
||||
ABI_PATH=../lmsr-amm/deployment/$CHAIN_ID/v1/out
|
||||
METADATA_PATH=../lmsr-amm/deployment/liqp-deployments.json
|
||||
fi
|
||||
|
||||
if [ ! -f "$ABI_PATH/IPartyPool.sol/IPartyPool.json" ]; then
|
||||
echo "Invalid chain ID $CHAIN_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo Chain $CHAIN_ID
|
||||
|
||||
generate() {
|
||||
ABI=$(jq '.abi' $ABI_PATH/$1.sol/$1.json)
|
||||
echo "/* GENERATED FILE: DO NOT EDIT! */\n\nconst ${1}ABI = ${ABI} as const;\n\nexport default ${1}ABI;" > ./src/contracts/$1ABI.ts
|
||||
echo "src/contracts/$1ABI.ts"
|
||||
}
|
||||
|
||||
generate IPartyPlanner
|
||||
generate IPartyPool
|
||||
generate IPartyInfo
|
||||
|
||||
cp "$METADATA_PATH" src/contracts/
|
||||
echo src/contracts/liqp-deployments.json
|
||||
10
deploy/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM nginx:stable-alpine
|
||||
|
||||
RUN apk update && apk upgrade
|
||||
|
||||
WORKDIR /liquidity.party/web
|
||||
COPY out ./out
|
||||
RUN sed -i '1idaemon off;' /etc/nginx/nginx.conf
|
||||
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
CMD ["nginx"]
|
||||
103
deploy/liquidity-party.k8s.yaml
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: liquidity-party
|
||||
labels:
|
||||
app: liquidity-party
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: liquidity-party
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: liquidity-party
|
||||
annotations:
|
||||
prometheus.io/scrape: "false"
|
||||
spec:
|
||||
containers:
|
||||
- name: liquidity-party
|
||||
image: dexorder/liquidity-party
|
||||
ports:
|
||||
- name: www
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 100M
|
||||
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: liquidity-party
|
||||
labels:
|
||||
app: liquidity-party
|
||||
spec:
|
||||
selector:
|
||||
app: liquidity-party
|
||||
ports:
|
||||
- name: liquidity-party
|
||||
port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
|
||||
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: liquidity-party
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true" # Redirects HTTP to HTTPS
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- secretName: liquidity-party-tls
|
||||
hosts:
|
||||
- liquidity.party
|
||||
rules:
|
||||
- host: liquidity.party
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: liquidity-party
|
||||
port:
|
||||
number: 80
|
||||
|
||||
|
||||
# www redirects to apex domain
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: www-liquidity-party
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true" # Redirects HTTP to HTTPS
|
||||
nginx.ingress.kubernetes.io/permanent-redirect: "https://liquidity.party/"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- secretName: www-liquidity-party-tls
|
||||
hosts:
|
||||
- www.liquidity.party
|
||||
rules:
|
||||
- host: www.liquidity.party
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: liquidity-party
|
||||
port:
|
||||
number: 80
|
||||
23
deploy/nginx.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
|
||||
root /liquidity.party/web/out;
|
||||
gzip on;
|
||||
gzip_min_length 1000;
|
||||
gzip_types text/plain text/xml application/javascript text/css;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri.html $uri/ /index.html;
|
||||
}
|
||||
|
||||
# This is necessary when `trailingSlash: false`.
|
||||
# You can omit this when `trailingSlash: true`.
|
||||
location /blog/ {
|
||||
rewrite ^/blog/(.*)$ /blog/$1.html break;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
location = /404.html {
|
||||
internal;
|
||||
}
|
||||
}
|
||||
80
package-lock.json
generated
@@ -17,7 +17,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^23.15.0",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "^15.1.3",
|
||||
"next": "15.5.7",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
@@ -1405,15 +1405,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz",
|
||||
"integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
|
||||
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz",
|
||||
"integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
||||
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1427,9 +1427,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz",
|
||||
"integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
||||
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1443,9 +1443,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz",
|
||||
"integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
||||
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1459,9 +1459,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz",
|
||||
"integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1475,9 +1475,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz",
|
||||
"integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz",
|
||||
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1491,9 +1491,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz",
|
||||
"integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1507,9 +1507,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz",
|
||||
"integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1523,9 +1523,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz",
|
||||
"integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -6179,12 +6179,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.5.4",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz",
|
||||
"integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==",
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
|
||||
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.4",
|
||||
"@next/env": "15.5.7",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
@@ -6197,14 +6197,14 @@
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.5.4",
|
||||
"@next/swc-darwin-x64": "15.5.4",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.4",
|
||||
"@next/swc-linux-arm64-musl": "15.5.4",
|
||||
"@next/swc-linux-x64-gnu": "15.5.4",
|
||||
"@next/swc-linux-x64-musl": "15.5.4",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.4",
|
||||
"@next/swc-win32-x64-msvc": "15.5.4",
|
||||
"@next/swc-darwin-arm64": "15.5.7",
|
||||
"@next/swc-darwin-x64": "15.5.7",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.7",
|
||||
"@next/swc-linux-arm64-musl": "15.5.7",
|
||||
"@next/swc-linux-x64-gnu": "15.5.7",
|
||||
"@next/swc-linux-x64-musl": "15.5.7",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.7",
|
||||
"@next/swc-win32-x64-msvc": "15.5.7",
|
||||
"sharp": "^0.34.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^23.15.0",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "^15.1.3",
|
||||
"next": "15.5.7",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
version="1.1"
|
||||
viewBox="0 0 86.435997 86.435997"
|
||||
id="svg585"
|
||||
sodipodi:docname="logo-flower.svg"
|
||||
sodipodi:docname="logo-splash.svg"
|
||||
width="86.435997"
|
||||
height="86.435997"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
@@ -27,24 +27,17 @@
|
||||
inkscape:cx="218.22034"
|
||||
inkscape:cy="48.516949"
|
||||
inkscape:window-width="1864"
|
||||
inkscape:window-height="1131"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg585" />
|
||||
<!-- Flower -->
|
||||
<g
|
||||
fill="#000000"
|
||||
id="g583"
|
||||
transform="translate(-1.782,-1.782)">
|
||||
<g
|
||||
id="g581">
|
||||
<circle
|
||||
fill="#000000"
|
||||
cx="45"
|
||||
cy="44.999001"
|
||||
r="5.277"
|
||||
id="circle545" />
|
||||
<g
|
||||
id="g579">
|
||||
<g
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
public/slippage-comparison.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/social-card-dark.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/social-card-light.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
476
scripts/create_pool_from_prices.js
Normal file
@@ -0,0 +1,476 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Create Pool from Real-Time Prices Script
|
||||
* Fetches real-time prices from CoinGecko and creates a new pool on the chain specified (could be mockchain, sepolia or prod)
|
||||
*/
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load environment variables from .env-secret
|
||||
config({ path: new URL('../.env-secret', import.meta.url).pathname });
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
// Network flag: 'mockchain' or 'mainnet'
|
||||
const NETWORK = process.env.NETWORK || 'mainnet';
|
||||
|
||||
// Network-specific configuration
|
||||
const NETWORK_CONFIG = {
|
||||
mockchain: {
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
privateKey: '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a', // Dev wallet #4 (sender)
|
||||
receiverPrivateKey: '0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356', // Receiver wallet
|
||||
receiverAddress: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // Receiver public key
|
||||
chainId: '31337',
|
||||
tokens: {
|
||||
USDT: {
|
||||
address: '0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6', // USXD on mockchain
|
||||
coingeckoId: 'tether',
|
||||
decimals: 6,
|
||||
feePpm: 400
|
||||
},
|
||||
USDC: {
|
||||
address: '0x2910E325cf29dd912E3476B61ef12F49cb931096', // FUSD on mockchain
|
||||
coingeckoId: 'usd-coin',
|
||||
decimals: 6,
|
||||
feePpm: 400
|
||||
}
|
||||
}
|
||||
},
|
||||
mainnet: {
|
||||
rpcUrl: process.env.MAINNET_RPC_URL,
|
||||
privateKey: process.env.PRIVATE_KEY,
|
||||
receiverAddress: '0xd3b310bd32d782f89eea49cb79656bcaccde7213', // Same as payer for mainnet
|
||||
chainId: '1',
|
||||
tokens: {
|
||||
USDT: {
|
||||
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
coingeckoId: 'tether',
|
||||
decimals: 6,
|
||||
feePpm: 40 // 0.0004%
|
||||
},
|
||||
USDC: {
|
||||
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
coingeckoId: 'usd-coin',
|
||||
decimals: 6,
|
||||
feePpm: 40 // 0.0004%
|
||||
},
|
||||
WBTC: {
|
||||
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
||||
coingeckoId: 'wrapped-bitcoin',
|
||||
decimals: 8,
|
||||
feePpm: 300 // 0.00030%
|
||||
},
|
||||
WETH: {
|
||||
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
coingeckoId: 'weth',
|
||||
decimals: 18,
|
||||
feePpm: 350 // 0.0035%
|
||||
},
|
||||
UNI: {
|
||||
address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
|
||||
coingeckoId: 'uniswap',
|
||||
decimals: 18,
|
||||
feePpm: 1450 // 0.00145%
|
||||
},
|
||||
WSOL: {
|
||||
address: '0xD31a59c85aE9D8edEFeC411D448f90841571b89c', // Wormhole Wrapped SOL
|
||||
coingeckoId: 'solana',
|
||||
decimals: 9,
|
||||
feePpm: 950 // 0.00095%
|
||||
},
|
||||
TRX: {
|
||||
address: '0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5',
|
||||
coingeckoId: 'tron',
|
||||
decimals: 6,
|
||||
feePpm: 950 // 0.00095%
|
||||
},
|
||||
AAVE: {
|
||||
address: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9',
|
||||
coingeckoId: 'aave',
|
||||
decimals: 18,
|
||||
feePpm: 1450 // 0.00145%
|
||||
},
|
||||
PEPE: {
|
||||
address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
|
||||
coingeckoId: 'pepe',
|
||||
decimals: 18,
|
||||
feePpm: 2150 // 0.00215%
|
||||
},
|
||||
SHIB: {
|
||||
address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE',
|
||||
coingeckoId: 'shiba-inu',
|
||||
decimals: 18,
|
||||
feePpm: 2150 // 0.00215%
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Get current network config
|
||||
const currentConfig = NETWORK_CONFIG[NETWORK];
|
||||
if (!currentConfig) {
|
||||
console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const RPC_URL = currentConfig.rpcUrl;
|
||||
const PRIVATE_KEY = currentConfig.privateKey;
|
||||
const RECEIVER_ADDRESS = currentConfig.receiverAddress || process.env.RECEIVER_ADDRESS;
|
||||
|
||||
// Validate required config for mainnet
|
||||
if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) {
|
||||
console.error('[!] Missing required environment variables for mainnet');
|
||||
console.error(' Required: MAINNET_RPC_URL, PRIVATE_KEY');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!RECEIVER_ADDRESS) {
|
||||
console.error('[!] Missing RECEIVER_ADDRESS in .env-secret file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Use network-specific tokens
|
||||
const TEST_TOKENS = currentConfig.tokens;
|
||||
|
||||
// Default pool parameters
|
||||
const DEFAULT_POOL_PARAMS = {
|
||||
name: 'Original Genesis of Liquidity Party',
|
||||
symbol: 'OG.LP',
|
||||
kappa: ethers.BigNumber.from('184467440737095520'), //0.01 * 2^64
|
||||
swapFeesPpm: Object.values(TEST_TOKENS).map(t => t.feePpm),
|
||||
flashFeePpm: 5, // 0.0005%
|
||||
stable: false,
|
||||
initialLpAmount: ethers.utils.parseUnits('1', 18) // 100 USD in 18 decimals
|
||||
};
|
||||
|
||||
// Input amount in USD
|
||||
const INPUT_USD_AMOUNT = 1;
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// LOAD ABIs AND CONFIG
|
||||
// ============================================================================
|
||||
|
||||
const chainInfoData = JSON.parse(await readFile(new URL('../src/contracts/liqp-deployments.json', import.meta.url), 'utf-8'));
|
||||
const PARTY_PLANNER_ADDRESS = chainInfoData[currentConfig.chainId].v1.PartyPlanner;
|
||||
|
||||
const ERC20ABI = [
|
||||
{ "type": "function", "name": "balanceOf", "stateMutability": "view", "inputs": [{ "name": "account", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] },
|
||||
{ "type": "function", "name": "decimals", "stateMutability": "view", "inputs": [], "outputs": [{ "name": "", "type": "uint8" }] },
|
||||
{ "type": "function", "name": "approve", "stateMutability": "nonpayable", "inputs": [{ "name": "spender", "type": "address" }, { "name": "amount", "type": "uint256" }], "outputs": [{ "name": "", "type": "bool" }] }
|
||||
];
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch real-time prices from CoinGecko API
|
||||
*/
|
||||
async function fetchCoinGeckoPrices() {
|
||||
try {
|
||||
const ids = Object.values(TEST_TOKENS).map(t => t.coingeckoId).join(',');
|
||||
const url = `https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`;
|
||||
|
||||
console.log(`[~] Fetching prices from CoinGecko...`);
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`CoinGecko API request failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const prices = {};
|
||||
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
prices[symbol] = data[tokenInfo.coingeckoId]?.usd || 0;
|
||||
if (prices[symbol] === 0) {
|
||||
throw new Error(`Failed to fetch valid price for ${symbol}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[+] Prices fetched successfully:`);
|
||||
for (const [symbol, price] of Object.entries(prices)) {
|
||||
console.log(` ${symbol.padEnd(6)}: $${price.toLocaleString()}`);
|
||||
}
|
||||
|
||||
return prices;
|
||||
} catch (error) {
|
||||
console.error(`[!] Error fetching prices:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate token amounts based on equal USD distribution
|
||||
*/
|
||||
function calculateTokenAmounts(prices, usdAmount) {
|
||||
const tokenCount = Object.keys(TEST_TOKENS).length;
|
||||
const usdPerToken = usdAmount / tokenCount; // Equally distribute among all tokens
|
||||
|
||||
const tokenAmounts = {};
|
||||
|
||||
console.log(`\n[~] Calculated token amounts for $${usdAmount} ($${usdPerToken.toFixed(2)} per token):`);
|
||||
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
// Calculate raw amount
|
||||
const rawAmount = usdPerToken / prices[symbol];
|
||||
|
||||
// Convert to BigNumber with proper decimals
|
||||
const amountBN = ethers.utils.parseUnits(rawAmount.toFixed(tokenInfo.decimals), tokenInfo.decimals);
|
||||
|
||||
tokenAmounts[symbol] = amountBN;
|
||||
console.log(` ${symbol.padEnd(6)}: ${rawAmount.toFixed(tokenInfo.decimals)} (${amountBN.toString()} wei)`);
|
||||
}
|
||||
|
||||
return tokenAmounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check token balances
|
||||
*/
|
||||
async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
|
||||
console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`);
|
||||
|
||||
const balances = {};
|
||||
let hasEnoughBalance = true;
|
||||
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, provider);
|
||||
const balance = await tokenContract.balanceOf(receiverAddress);
|
||||
const requiredAmount = tokenAmounts[symbol];
|
||||
|
||||
balances[symbol] = balance;
|
||||
|
||||
const balanceFormatted = ethers.utils.formatUnits(balance, tokenInfo.decimals);
|
||||
const requiredFormatted = ethers.utils.formatUnits(requiredAmount, tokenInfo.decimals);
|
||||
const sufficient = balance.gte(requiredAmount);
|
||||
|
||||
console.log(` ${symbol}: ${balanceFormatted} (required: ${requiredFormatted}) ${sufficient ? '✓' : '✗'}`);
|
||||
|
||||
if (!sufficient) {
|
||||
hasEnoughBalance = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEnoughBalance) {
|
||||
console.log(`\n[!] Insufficient token balance. Please ensure receiver wallet has enough tokens.`);
|
||||
throw new Error('Insufficient token balance');
|
||||
}
|
||||
|
||||
console.log(`[+] All balances sufficient`);
|
||||
return balances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve tokens for the PartyPlanner contract
|
||||
*/
|
||||
async function approveTokens(provider, tokenAmounts, receiverPrivateKey) {
|
||||
console.log(`\n[~] Approving tokens for PartyPlanner contract...`);
|
||||
|
||||
// Connect with receiver wallet for approvals
|
||||
const receiverWallet = new ethers.Wallet(receiverPrivateKey, provider);
|
||||
console.log(`[~] Using receiver wallet for approvals: ${receiverWallet.address}`);
|
||||
|
||||
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
|
||||
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, receiverWallet);
|
||||
|
||||
// Approve 1% more than needed to account for fees/slippage
|
||||
const requiredAmount = tokenAmounts[symbol];
|
||||
const approvalAmount = requiredAmount.mul(101).div(100); // 1% buffer
|
||||
|
||||
console.log(` [~] Approving ${symbol} ${tokenInfo.address} ${approvalAmount} (1% buffer)...`);
|
||||
|
||||
try {
|
||||
// USDT and some tokens require setting allowance to 0 before setting a new value
|
||||
// Skip for BNB as it has a broken approve function
|
||||
if (symbol == 'USDT') {
|
||||
const resetTx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, 0);
|
||||
await resetTx.wait();
|
||||
console.log(` [+] ${symbol} allowance reset to 0`);
|
||||
}
|
||||
|
||||
const tx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, approvalAmount);
|
||||
await tx.wait();
|
||||
console.log(` [+] ${symbol} approved (tx: ${tx.hash})`);
|
||||
} catch (error) {
|
||||
console.error(` [!] Failed to approve ${symbol}:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[+] All tokens approved`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pool using cast send
|
||||
*/
|
||||
async function createPool(wallet, tokenAmounts) {
|
||||
console.log(`\n[~] Creating new pool...`);
|
||||
|
||||
// Prepare parameters
|
||||
const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address);
|
||||
const initialDeposits = Object.keys(TEST_TOKENS).map(symbol => tokenAmounts[symbol].toString());
|
||||
|
||||
// Set deadline to 5 minutes from now
|
||||
const deadline = Math.floor(Date.now() / 1000) + 300;
|
||||
|
||||
console.log(`[~] Pool parameters:`);
|
||||
console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`);
|
||||
console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`);
|
||||
console.log(` Tokens: ${tokenAddresses.join(', ')}`);
|
||||
console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`);
|
||||
console.log(` Payer (provides tokens): ${RECEIVER_ADDRESS}`);
|
||||
console.log(` Receiver (gets LP tokens): ${wallet.address}`);
|
||||
console.log(` Deadline: ${new Date(deadline * 1000).toISOString()}`);
|
||||
|
||||
// Build cast send command
|
||||
const castCommand = `cast send ${PARTY_PLANNER_ADDRESS} \
|
||||
"newPool(string,string,address[],int128,uint256[],uint256,bool,address,address,uint256[],uint256,uint256)" \
|
||||
"${DEFAULT_POOL_PARAMS.name}" \
|
||||
"${DEFAULT_POOL_PARAMS.symbol}" \
|
||||
"[${tokenAddresses.join(',')}]" \
|
||||
${DEFAULT_POOL_PARAMS.kappa.toString()} \
|
||||
"[${DEFAULT_POOL_PARAMS.swapFeesPpm.join(',')}]" \
|
||||
${DEFAULT_POOL_PARAMS.flashFeePpm} \
|
||||
${DEFAULT_POOL_PARAMS.stable} \
|
||||
${RECEIVER_ADDRESS} \
|
||||
${wallet.address} \
|
||||
"[${initialDeposits.join(',')}]" \
|
||||
${DEFAULT_POOL_PARAMS.initialLpAmount.toString()} \
|
||||
${deadline} \
|
||||
--rpc-url '${RPC_URL}' \
|
||||
--from 0x12db90820dafed100e40e21128e40dcd4ff6b331 \
|
||||
--trezor --mnemonic-index 0`
|
||||
|
||||
console.log(`\n[~] Cast command:\n${castCommand}\n`);
|
||||
|
||||
try {
|
||||
// Execute cast command
|
||||
const { execSync } = await import('child_process');
|
||||
const output = execSync(castCommand, { encoding: 'utf-8' });
|
||||
|
||||
console.log(`[+] Pool created successfully!`);
|
||||
console.log(output);
|
||||
|
||||
return output;
|
||||
} catch (error) {
|
||||
console.error(`[!] Failed to create pool:`, error.message);
|
||||
if (error.stderr) {
|
||||
console.error(` Error output: ${error.stderr.toString()}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print help message
|
||||
*/
|
||||
function printHelp() {
|
||||
console.log(`
|
||||
Usage: node create_pool_from_prices.js [OPTIONS]
|
||||
|
||||
Options:
|
||||
--amount <usd> USD amount to distribute (default: ${INPUT_USD_AMOUNT})
|
||||
--name <name> Pool name (default: "${DEFAULT_POOL_PARAMS.name}")
|
||||
--symbol <symbol> Pool symbol (default: "${DEFAULT_POOL_PARAMS.symbol}")
|
||||
--help, -h Show this help message
|
||||
|
||||
Example:
|
||||
node create_pool_from_prices.js
|
||||
node create_pool_from_prices.js --amount 200 --name "My Pool" --symbol "MP"
|
||||
`);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN FUNCTION
|
||||
// ============================================================================
|
||||
|
||||
async function main() {
|
||||
console.log(`${'='.repeat(70)}`);
|
||||
console.log(`Create Pool from Real-Time Prices`);
|
||||
console.log(`Network: ${NETWORK}`);
|
||||
console.log(`${'='.repeat(70)}\n`);
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let usdAmount = INPUT_USD_AMOUNT;
|
||||
let poolName = DEFAULT_POOL_PARAMS.name;
|
||||
let poolSymbol = DEFAULT_POOL_PARAMS.symbol;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--amount' && i + 1 < args.length) {
|
||||
usdAmount = parseFloat(args[i + 1]);
|
||||
i++;
|
||||
} else if (args[i] === '--name' && i + 1 < args.length) {
|
||||
poolName = args[i + 1];
|
||||
i++;
|
||||
} else if (args[i] === '--symbol' && i + 1 < args.length) {
|
||||
poolSymbol = args[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update pool params with parsed values
|
||||
DEFAULT_POOL_PARAMS.name = poolName;
|
||||
DEFAULT_POOL_PARAMS.symbol = poolSymbol;
|
||||
|
||||
try {
|
||||
// Step 1: Fetch prices
|
||||
const prices = await fetchCoinGeckoPrices();
|
||||
|
||||
// Step 2: Calculate token amounts
|
||||
const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
|
||||
//
|
||||
// // Step 3: Connect to wallet
|
||||
console.log(`\n[~] Connecting to sender wallet at ${RPC_URL}...`);
|
||||
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
||||
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
||||
console.log(`[+] Connected. Using sender wallet: ${wallet.address}`);
|
||||
console.log(`[+] Receiver wallet: ${RECEIVER_ADDRESS}`);
|
||||
//
|
||||
// Step 4: Check balances
|
||||
await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS);
|
||||
|
||||
// // Step 5: Approve tokens
|
||||
if (NETWORK === 'mockchain' && currentConfig.receiverPrivateKey) {
|
||||
// On mockchain, use receiver wallet for approvals
|
||||
await approveTokens(provider, tokenAmounts, currentConfig.receiverPrivateKey);
|
||||
} else if (NETWORK === 'mainnet') {
|
||||
// On mainnet, use the main wallet (payer and receiver are the same)
|
||||
await approveTokens(provider, tokenAmounts, PRIVATE_KEY);
|
||||
}
|
||||
|
||||
// Step 6: Create pool
|
||||
await createPool(wallet, tokenAmounts);
|
||||
|
||||
console.log(`\n${'='.repeat(70)}`);
|
||||
console.log(`Success! Pool created with real-time price-based deposits.`);
|
||||
console.log(`${'='.repeat(70)}\n`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`\n${'='.repeat(70)}`);
|
||||
console.error(`[!] Error: ${error.message}`);
|
||||
console.error(`${'='.repeat(70)}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main().catch(error => {
|
||||
console.error('[!] Unexpected error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
14
scripts/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "liquidity-party-scripts",
|
||||
"version": "1.0.0",
|
||||
"description": "Standalone scripts for Liquidity Party DEX development and testing",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"create-pool": "node create_pool_from_prices.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.3",
|
||||
"ethers": "^5.7.2"
|
||||
}
|
||||
}
|
||||
245
src/app/about/page.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import {useTheme} from "next-themes";
|
||||
import {useState} from "react";
|
||||
|
||||
export default function AboutPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { theme, setTheme } = useTheme();
|
||||
const logoSrc = theme === 'dark' ? '/logo-dark.svg' : '/logo-light.svg';
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl mx-auto space-y-8">
|
||||
{/* Header */}
|
||||
<div className="text-center space-y-4">
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="Liquidity Party Logo"
|
||||
className="mx-auto h-24 w-auto"
|
||||
/>
|
||||
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
Multi-asset AMM based on Party Game Theory
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Introduction */}
|
||||
<Card className="p-8 space-y-4">
|
||||
<h2 className="text-2xl font-semibold">Introduction</h2>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Liquidity Party is a new game-theoretic multi-asset AMM based on the groundbreaking paper:{' '}
|
||||
<a
|
||||
href="https://mason.gmu.edu/~rhanson/mktscore.pdf"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline italic"
|
||||
>
|
||||
Logarithmic Market Scoring Rules for Modular Combinatorial Information Aggregation (R. Hanson, 2002)
|
||||
</a>.
|
||||
</p>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Our formulation and implementation is described in the{' '}
|
||||
<a href="https://github.com/Liquidity-Party/lmsr-amm/blob/main/doc/whitepaper.md"
|
||||
target='liqp_whitepaper'
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline italic"
|
||||
>
|
||||
Liquidity Party whitepaper.
|
||||
</a>
|
||||
</p>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
A Logarithmic Market Scoring Rule (LMSR) is a pricing formula for AMMs that know only their current asset
|
||||
inventories and no other information, naturally supporting multi-asset pools.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
{/* Key Advantages */}
|
||||
<Card className="p-8 space-y-4">
|
||||
<h2 className="text-2xl font-semibold">Advantages Over Constant Product Markets</h2>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<h3 className="font-semibold text-primary mb-2">Less Slippage</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Reduced slippage for small and medium trade sizes
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<h3 className="font-semibold text-primary mb-2">Multi-Asset Pools</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Trade long-tail pairs in a single hop with multi-asset support
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<h3 className="font-semibold text-primary mb-2">Lower Fees</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Smaller spread means more cost-effective trading
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<h3 className="font-semibold text-primary mb-2">Deeper Liquidity</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enhanced liquidity depth for better trading execution
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-muted-foreground leading-relaxed mt-4">
|
||||
According to game theory, the initial price slope of a Constant Product AMM is too steep, overcharging takers
|
||||
with too much slippage at small and medium trade sizes. LMSR pools offer less slippage and cheaper
|
||||
liquidity for the small and medium trade sizes used by real traders.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<img
|
||||
src="/slippage-comparison.png"
|
||||
alt="Slippage Comparison Chart"
|
||||
className="w-full rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Multi-Asset */}
|
||||
<Card className="p-8 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold">Multi-Asset Capabilities</h2>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Naturally multi-asset, Liquidity Party altcoin pools provide direct, one-hop swaps on otherwise
|
||||
illiquid multi-hop pairs. Pools will quote any pair combination available in the pool:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Performance Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b">
|
||||
<th className="text-left p-3 font-semibold">Assets</th>
|
||||
<th className="text-left p-3 font-semibold">Pairs</th>
|
||||
<th className="text-left p-3 font-semibold">Swap Gas</th>
|
||||
<th className="text-left p-3 font-semibold">Mint Gas</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-muted-foreground">
|
||||
<tr className="border-b hover:bg-muted/50 transition-colors">
|
||||
<td className="p-3">2</td>
|
||||
<td className="p-3">1</td>
|
||||
<td className="p-3">131,000</td>
|
||||
<td className="p-3">143,000</td>
|
||||
</tr>
|
||||
<tr className="border-b hover:bg-muted/50 transition-colors">
|
||||
<td className="p-3">2*</td>
|
||||
<td className="p-3">1</td>
|
||||
<td className="p-3">118,000</td>
|
||||
<td className="p-3">143,000</td>
|
||||
</tr>
|
||||
<tr className="border-b hover:bg-muted/50 transition-colors">
|
||||
<td className="p-3">10</td>
|
||||
<td className="p-3">45</td>
|
||||
<td className="p-3">142,000</td>
|
||||
<td className="p-3">412,000</td>
|
||||
</tr>
|
||||
<tr className="border-b hover:bg-muted/50 transition-colors">
|
||||
<td className="p-3">20</td>
|
||||
<td className="p-3">190</td>
|
||||
<td className="p-3">157,000</td>
|
||||
<td className="p-3">749,000</td>
|
||||
</tr>
|
||||
<tr className="border-b hover:bg-muted/50 transition-colors">
|
||||
<td className="p-3">50</td>
|
||||
<td className="p-3">1,225</td>
|
||||
<td className="p-3">199,000</td>
|
||||
<td className="p-3">1,760,000</td>
|
||||
</tr>
|
||||
<tr className="hover:bg-muted/50 transition-colors">
|
||||
<td className="p-3">100</td>
|
||||
<td className="p-3">4,950</td>
|
||||
<td className="p-3">269,000</td>
|
||||
<td className="p-3">2,684,000</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="text-xs text-muted-foreground mt-2">* Stablecoin pair optimization</p>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Liquidity Party aggregates scarce, low market cap assets into a single pool, providing one-hop liquidity
|
||||
for exotic pairs without fragmenting LP assets. Constant Product pools would need 190x the LP assets to provide the
|
||||
same pairwise liquidity as a single 20-asset Liquidity Party pool, due to asset fragmentation.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
{/* Lower Fees & Minimized IL */}
|
||||
<Card className="p-8 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold">Lower Fees</h2>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Since market makers offer the option to take either side of the market, they must receive a subsidy
|
||||
or charge a fee (spread) to compensate for adverse selection (impermanent loss). By protecting LPs
|
||||
against common value-extraction scenarios, LMSR pools have a reduced risk premium resulting in lower
|
||||
fees for takers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 border-t pt-6">
|
||||
<h2 className="text-2xl font-semibold">Minimized Impermanent Loss</h2>
|
||||
<h3 className="text-xl font-semibold text-primary">Adverse Selection Protection</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
All AMM's suffer from Impermanent Loss (IL), also known as adverse selection or toxic order flow. Liquidity
|
||||
Party uses game theory to minimize IL for LPs, by charging lower fees to small legitimate traders, and
|
||||
higher fees to large adversarial traders during market dislocation. This means a higher effective rate
|
||||
for LP's and cheaper swaps for legitimate small traders.
|
||||
</p>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Liquidity Party swaps guarantee a bounded maximum loss of <code>κ*S*ln(N)</code> where <code>κ</code> is
|
||||
the pool's liquidity parameter, <code>S</code> is the total size of the pool, and <code>N</code> is the
|
||||
number of assets in the pool.
|
||||
</p>
|
||||
<h3 className="text-xl font-semibold text-primary">No Intra-Pool Arbitrage</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Other multi-asset systems can provide inconsistent price quotes, allowing arbitragers to
|
||||
extract value from LP's by <i>trading assets inside the same pool against each other.</i> With Liquidity
|
||||
Party, no intra-pool arbitrage is possible, because the mathematics guarantee fully consistent price
|
||||
quotes on all pairs in the pool.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Open Source Development */}
|
||||
<Card className="p-8 space-y-4">
|
||||
<h2 className="text-2xl font-semibold">Transparent Open Source</h2>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
The Liquidity Party contract source code fully transparent to the public and verified by Etherscan.
|
||||
</p>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
View our contracts on{' '}
|
||||
<a
|
||||
href="https://github.com/Liquidity-Party/lmsr-amm"
|
||||
target="liqp_github"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</p>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Verify our contracts on{' '}
|
||||
<a
|
||||
href="https://etherscan.io/address/0x42977f565971F6D288a05ddEbC87A17276F71A29#code"
|
||||
target="liqp_etherscan"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Etherscan (Sepolia)
|
||||
</a>
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="text-center py-8 border-t">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Liquidity Party is offered by Dexorder Trading Services, Ltd. (British Virgin Islands)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1209)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(9.25540316264299,0,0,9.25540316264299,100,100)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="86.435997" height="86.435997"><svg version="1.1" viewBox="0 0 86.435997 86.435997" id="svg188" sodipodi:docname="logo-flower.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1209)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(9.25540316264299,0,0,9.25540316264299,100,100)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="86.435997" height="86.435997"><svg version="1.1" viewBox="0 0 86.435997 86.435997" id="svg188" sodipodi:docname="logo-splash.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs192"><clipPath id="SvgjsClipPath1209"><rect width="1000" height="1000" x="0" y="0" rx="500" ry="500"></rect></clipPath></defs>
|
||||
<sodipodi:namedview id="namedview190" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" inkscape:zoom="5.6893399" inkscape:cx="83.489475" inkscape:cy="69.515973" inkscape:window-width="1864" inkscape:window-height="1131" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg188"></sodipodi:namedview>
|
||||
<!-- Flower -->
|
||||
<g fill="#000000" id="g186" transform="translate(-1.782,-1.782)">
|
||||
<g id="g184">
|
||||
<circle fill="#000000" cx="45" cy="44.999001" r="5.277" id="circle148"></circle>
|
||||
<g id="g182">
|
||||
<g id="g152">
|
||||
<path fill="#0d47a1" class="blue1" d="m 45.214,33.672 c 24.117,-31.271 0.369,-31.89 0.369,-31.89 0,0 -23.756,0.069 -0.369,31.89 z" id="path150"></path>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -1,6 +1,34 @@
|
||||
import '@rainbow-me/rainbowkit/styles.css';
|
||||
import '@/app/globals.css';
|
||||
import { Providers } from '@/components/providers';
|
||||
import { Metadata } from 'next';
|
||||
import Script from "next/script";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://liquidity.party'),
|
||||
title: 'Liquidity Party',
|
||||
description: 'Game-Theoretic Multi-asset DEX',
|
||||
openGraph: {
|
||||
url: 'https://liquidity.party/',
|
||||
type: 'website',
|
||||
title: 'Liquidity Party',
|
||||
description: 'Game-Theoretic Multi-asset DEX',
|
||||
images: [
|
||||
{
|
||||
url: '/social-card-light.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Liquidity Party',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Liquidity Party',
|
||||
description: 'Game-Theoretic Multi-asset DEX',
|
||||
images: ['/social-card-light.png'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
@@ -9,11 +37,20 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<title>Liquidity Party</title>
|
||||
<meta name="description" content="Decentralized Exchange for Multi-Asset AMM Pools" />
|
||||
</head>
|
||||
<body className="min-h-screen">
|
||||
{/* Google tag (gtag.js) */}
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-GH2R6NTLC3"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script id="gtag-init" strategy="afterInteractive">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){window.dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-GH2R6NTLC3');
|
||||
`}
|
||||
</Script>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"31337": {
|
||||
"IPartyPlanner": "0x536F14E49e1Bb927003E83aDEBF295F3682ff121",
|
||||
"IPartyPoolViewer": "0xd85BdcdaE4db1FAEB8eF93331525FE68D7C8B3f0"
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { SwapForm } from '@/components/swap-form';
|
||||
|
||||
export default function HomePage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<Tabs defaultValue="swap" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2 mb-8">
|
||||
<TabsTrigger value="swap">{t('nav.swap')}</TabsTrigger>
|
||||
<TabsTrigger value="stake">{t('nav.stake')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="swap">
|
||||
<SwapForm />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="stake">
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<h2 className="text-2xl font-semibold mb-2">{t('stake.title')}</h2>
|
||||
<p>{t('stake.comingSoon')}</p>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<SwapForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
11
src/app/stake/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { StakeForm } from '@/components/stake-form';
|
||||
|
||||
export default function StakePage() {
|
||||
return (
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<StakeForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
5
src/app/terms/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import TosCard from '@/components/tos-card';
|
||||
|
||||
export default function TermsPage() {
|
||||
return <TosCard />;
|
||||
}
|
||||
11
src/app/unstake/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { StakeForm } from '@/components/stake-form';
|
||||
|
||||
export default function UnstakePage() {
|
||||
return (
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<StakeForm defaultMode="unstake" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,18 +4,27 @@ import { useTheme } from 'next-themes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { LanguageSelector } from '@/components/language-selector';
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
import { Moon, Sun, Menu, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export function Header() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Close mobile menu when route changes
|
||||
setMobileMenuOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
const toggleTheme = () => {
|
||||
console.log('Toggle clicked, current theme:', theme);
|
||||
const newTheme = theme === 'dark' ? 'light' : 'dark';
|
||||
@@ -24,33 +33,118 @@ export function Header() {
|
||||
};
|
||||
|
||||
const logoSrc = !mounted ? '/logo-dark.svg' : theme === 'dark' ? '/logo-dark.svg' : '/logo-light.svg';
|
||||
const mobileLogoSrc = '/logo-splash.svg';
|
||||
|
||||
const navLinks = [
|
||||
{ href: '/', label: 'Swap' },
|
||||
{ href: '/stake', label: 'Stake' },
|
||||
{ href: '/unstake', label: 'Unstake' },
|
||||
{ href: '/about', label: 'About' },
|
||||
];
|
||||
|
||||
return (
|
||||
<header className="border-b">
|
||||
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="Liquidity Party"
|
||||
className="h-8 w-auto"
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Desktop logo - just a link */}
|
||||
<Link href="/" className="hidden sm:block">
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="Liquidity Party"
|
||||
className="h-8 w-auto cursor-pointer"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Mobile logo - toggles menu */}
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="sm:hidden focus:outline-none"
|
||||
>
|
||||
<img
|
||||
src={mobileLogoSrc}
|
||||
alt="Liquidity Party"
|
||||
className="h-8 w-8 cursor-pointer"
|
||||
/>
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</button>
|
||||
|
||||
<span className="bg-yellow-500/20 text-yellow-500 text-xs font-bold px-2 py-1 rounded border border-yellow-500/50">
|
||||
BETA
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Language Selector */}
|
||||
<LanguageSelector />
|
||||
{/* Desktop Navigation Links */}
|
||||
<nav className="hidden md:flex items-center gap-1 mr-2">
|
||||
{navLinks.map((link) => (
|
||||
<Link key={link.href} href={link.href}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={`text-sm font-medium ${pathname === link.href ? 'bg-accent' : ''}`}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Theme Toggle */}
|
||||
<Button variant="ghost" size="icon" onClick={toggleTheme} type="button">
|
||||
{/* Language Selector - Hidden on small screens */}
|
||||
<div className="hidden sm:block">
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
|
||||
{/* Theme Toggle - Hidden on mobile */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
type="button"
|
||||
className="hidden sm:flex"
|
||||
>
|
||||
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
|
||||
{/* Wallet Connect */}
|
||||
{/* Wallet Connect - Always visible */}
|
||||
<ConnectButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden border-t">
|
||||
<nav className="container mx-auto px-4 py-4 flex flex-col gap-2">
|
||||
{navLinks.map((link) => (
|
||||
<Link key={link.href} href={link.href}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={`w-full justify-start text-base font-medium ${pathname === link.href ? 'bg-accent' : ''}`}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
</Link>
|
||||
))}
|
||||
<div className="flex items-center gap-2 pt-2 border-t mt-2">
|
||||
<div className="flex-1">
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
type="button"
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { getDefaultConfig, RainbowKitProvider, darkTheme, lightTheme } from '@rainbow-me/rainbowkit';
|
||||
import { WagmiProvider } from 'wagmi';
|
||||
import { mainnet, polygon, optimism, arbitrum, base } from 'wagmi/chains';
|
||||
import { mainnet, polygon, optimism, arbitrum, base, sepolia } from 'wagmi/chains';
|
||||
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
|
||||
import { ThemeProvider, useTheme } from 'next-themes';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -10,29 +10,37 @@ import { defineChain } from 'viem';
|
||||
|
||||
import { TranslationsProvider } from '@/providers/translations-provider';
|
||||
import { Header } from '@/components/header';
|
||||
import { ToastProvider } from '@/components/ui/toast';
|
||||
|
||||
import deployments from '@/contracts/liqp-deployments.json';
|
||||
|
||||
const mockchain = defineChain({
|
||||
id: 31337,
|
||||
name: 'Mockchain',
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
name: 'Test Ether',
|
||||
symbol: 'TETH',
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ['http://localhost:8545'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: 'Explorer', url: 'http://localhost:8545' },
|
||||
},
|
||||
testnet: true,
|
||||
});
|
||||
|
||||
const allChains = [mainnet, polygon, optimism, arbitrum, base, sepolia, mockchain];
|
||||
const chains = allChains.filter(chain =>
|
||||
Object.keys(deployments || {}).includes(chain.id.toString())
|
||||
);
|
||||
if (chains.length === 0) {
|
||||
console.error('No chains found with deployed contracts.');
|
||||
}
|
||||
|
||||
const config = getDefaultConfig({
|
||||
appName: 'Liquidity Party',
|
||||
projectId: 'YOUR_PROJECT_ID', // Get this from https://cloud.walletconnect.com
|
||||
chains: [mainnet, polygon, optimism, arbitrum, base, mockchain],
|
||||
ssr: false,
|
||||
appName: 'Liquidity Party',
|
||||
projectId: 'YOUR_PROJECT_ID', // Get this from https://cloud.walletconnect.com
|
||||
chains: chains as any,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
@@ -87,12 +95,24 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||
storageKey="liquidity-party-theme"
|
||||
>
|
||||
<Web3Provider>
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Header />
|
||||
<main className="flex-1 container mx-auto px-4 py-8">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<ToastProvider>
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<Header />
|
||||
<main className="flex-1 container mx-auto px-4 py-8">
|
||||
{children}
|
||||
</main>
|
||||
<footer className="py-4 text-center text-sm text-muted-foreground border-t">
|
||||
<div className="mb-2">
|
||||
©{new Date().getFullYear()} Dexorder Trading Services, Ltd. (BVI)
|
||||
</div>
|
||||
<div>
|
||||
<a href="/terms" className="hover:text-foreground underline transition-colors">
|
||||
Terms of Service
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</ToastProvider>
|
||||
</Web3Provider>
|
||||
</ThemeProvider>
|
||||
</TranslationsProvider>
|
||||
|
||||
1015
src/components/stake-form.tsx
Normal file
@@ -1,42 +1,105 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef, 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 { ArrowDownUp, ChevronDown } from 'lucide-react';
|
||||
import { useAccount } from 'wagmi';
|
||||
import { ArrowDownUp, ChevronDown, Settings, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||
import { useAccount, useChainId } from 'wagmi';
|
||||
import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner';
|
||||
import { formatUnits } from 'viem';
|
||||
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
|
||||
import { formatUnits, parseUnits } from 'viem';
|
||||
import { SwapReviewModal } from './swap-review-modal';
|
||||
import UniswapQuote from './uniswap-quote';
|
||||
|
||||
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
||||
|
||||
export function SwapForm() {
|
||||
const { t } = useTranslation();
|
||||
const { isConnected, address } = useAccount();
|
||||
const chainId = useChainId();
|
||||
const [fromAmount, setFromAmount] = useState('');
|
||||
const [toAmount, setToAmount] = useState('');
|
||||
const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null);
|
||||
const [selectedToToken, setSelectedToToken] = useState<TokenDetails | null>(null);
|
||||
const [isFromDropdownOpen, setIsFromDropdownOpen] = useState(false);
|
||||
const [isToDropdownOpen, setIsToDropdownOpen] = useState(false);
|
||||
const [slippage, setSlippage] = useState<number>(0.5); // Default 0.5%
|
||||
const [customSlippage, setCustomSlippage] = useState<string>('');
|
||||
const [isCustomSlippage, setIsCustomSlippage] = useState(false);
|
||||
const [maxSlippage, setMaxSlippage] = useState<string>('1'); // Default 1%
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
|
||||
const [transactionStatus, setTransactionStatus] = useState<TransactionStatus>('idle');
|
||||
const [transactionError, setTransactionError] = useState<string | null>(null);
|
||||
const [actualSwapAmounts, setActualSwapAmounts] = useState<ActualSwapAmounts | null>(null);
|
||||
const fromDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const toDropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Use the custom hook to get all token details with a single batched RPC call
|
||||
const { tokenDetails, loading, error } = useTokenDetails(address);
|
||||
const { tokenDetails, loading } = useTokenDetails(address);
|
||||
|
||||
// Get available tokens for the selected "from" token
|
||||
const { availableTokens } = useGetPoolsByToken(selectedFromToken?.address);
|
||||
const { availableTokens, error: poolsError } = useGetPoolsByToken(selectedFromToken?.address);
|
||||
|
||||
// Trigger the hook to fetch data when address is available
|
||||
useEffect(() => {
|
||||
if (tokenDetails) {
|
||||
console.log('Token details loaded in swap-form');
|
||||
// Only calculate swap amounts when both tokens are selected
|
||||
// Use useMemo to prevent creating a new array reference on every render
|
||||
const filteredAvailableTokens = useMemo(() => {
|
||||
if (selectedFromToken && selectedToToken && availableTokens) {
|
||||
return availableTokens.filter(token =>
|
||||
token.address.toLowerCase() === selectedToToken.address.toLowerCase()
|
||||
);
|
||||
}
|
||||
}, [tokenDetails]);
|
||||
return null;
|
||||
}, [selectedFromToken, selectedToToken, availableTokens]);
|
||||
|
||||
// Get the current slippage value
|
||||
const currentSlippage = parseFloat(maxSlippage) || 1;
|
||||
|
||||
// Calculate swap amounts for the selected token pair only
|
||||
const { swapAmounts } = useSwapAmounts(
|
||||
filteredAvailableTokens,
|
||||
fromAmount,
|
||||
selectedFromToken?.decimals || 18,
|
||||
currentSlippage
|
||||
);
|
||||
|
||||
// Check if user has insufficient balance
|
||||
const hasInsufficientBalance = useMemo(() => {
|
||||
if (!selectedFromToken || !fromAmount || fromAmount === '') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const inputAmount = parseUnits(fromAmount, selectedFromToken.decimals);
|
||||
return inputAmount > selectedFromToken.balance;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, [selectedFromToken, fromAmount]);
|
||||
|
||||
// Check if calculated slippage exceeds 5%
|
||||
const slippageExceedsLimit = useMemo(() => {
|
||||
if (!swapAmounts || swapAmounts.length === 0 || swapAmounts[0].calculatedSlippage === undefined) {
|
||||
return false;
|
||||
}
|
||||
return Math.abs(swapAmounts[0].calculatedSlippage) > 5;
|
||||
}, [swapAmounts]);
|
||||
|
||||
// Initialize swap hook
|
||||
const { executeSwap, estimateGas, isSwapping, gasEstimate, isEstimatingGas } = useSwap();
|
||||
|
||||
// Update "You Receive" amount when swap calculation completes
|
||||
useEffect(() => {
|
||||
if (hasInsufficientBalance) {
|
||||
setToAmount('');
|
||||
return;
|
||||
}
|
||||
if (swapAmounts && swapAmounts.length > 0 && selectedToToken) {
|
||||
const swapResult = swapAmounts[0]; // Get the first (and should be only) result
|
||||
const formattedAmount = formatUnits(swapResult.amountOut, selectedToToken.decimals);
|
||||
setToAmount(formattedAmount);
|
||||
} else {
|
||||
setToAmount('');
|
||||
}
|
||||
}, [swapAmounts, selectedToToken, hasInsufficientBalance]);
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
useEffect(() => {
|
||||
@@ -53,43 +116,127 @@ export function SwapForm() {
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// Calculate and log limit price when amount or slippage changes
|
||||
useEffect(() => {
|
||||
if (fromAmount && parseFloat(fromAmount) > 0) {
|
||||
const amount = parseFloat(fromAmount);
|
||||
const slippagePercent = isCustomSlippage ? parseFloat(customSlippage) || 0 : slippage;
|
||||
const limitPrice = amount * (1 + slippagePercent / 100);
|
||||
console.log('Limit Price:', limitPrice);
|
||||
console.log('From Amount:', amount);
|
||||
console.log('Slippage %:', slippagePercent);
|
||||
console.log('Additional Amount from Slippage:', limitPrice - amount);
|
||||
const handleSwap = async () => {
|
||||
if (!swapAmounts || swapAmounts.length === 0) {
|
||||
console.error('No swap amounts available');
|
||||
return;
|
||||
}
|
||||
}, [fromAmount, slippage, customSlippage, isCustomSlippage]);
|
||||
|
||||
const handleSwap = () => {
|
||||
// Swap logic will be implemented later
|
||||
console.log('Swap clicked');
|
||||
if (!selectedFromToken || !selectedToToken) {
|
||||
console.error('Tokens not selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setTransactionStatus('pending');
|
||||
setTransactionError(null);
|
||||
|
||||
try {
|
||||
// Use the shared helper to select the best swap route
|
||||
const bestRoute = selectBestSwapRoute(swapAmounts);
|
||||
|
||||
if (!bestRoute) {
|
||||
console.error('No valid swap route found');
|
||||
setTransactionError('No valid swap route found');
|
||||
setTransactionStatus('error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the user's input directly as maxAmountIn
|
||||
const maxAmountIn = parseUnits(fromAmount, selectedFromToken.decimals);
|
||||
|
||||
// Execute the swap and capture actual amounts from the transaction
|
||||
const result = await executeSwap(
|
||||
bestRoute.poolAddress,
|
||||
selectedFromToken.address,
|
||||
bestRoute.inputTokenIndex,
|
||||
bestRoute.outputTokenIndex,
|
||||
maxAmountIn,
|
||||
currentSlippage
|
||||
);
|
||||
|
||||
// Store the actual swap amounts if available
|
||||
if (result?.actualSwapAmounts) {
|
||||
setActualSwapAmounts(result.actualSwapAmounts);
|
||||
}
|
||||
|
||||
setTransactionStatus('success');
|
||||
} catch (err) {
|
||||
console.error('Swap failed:', err);
|
||||
setTransactionError(err instanceof Error ? err.message : 'Transaction failed');
|
||||
setTransactionStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
if (transactionStatus === 'success') {
|
||||
// Clear the form after successful swap
|
||||
setFromAmount('');
|
||||
setToAmount('');
|
||||
setSelectedFromToken(null);
|
||||
setSelectedToToken(null);
|
||||
}
|
||||
setTransactionStatus('idle');
|
||||
setTransactionError(null);
|
||||
};
|
||||
|
||||
const switchTokens = () => {
|
||||
// Switch tokens logic
|
||||
// Switch both tokens and amounts
|
||||
const tempFromAmount = fromAmount;
|
||||
const tempFromToken = selectedFromToken;
|
||||
|
||||
setFromAmount(toAmount);
|
||||
setToAmount(fromAmount);
|
||||
setToAmount(tempFromAmount);
|
||||
setSelectedFromToken(selectedToToken);
|
||||
setSelectedToToken(tempFromToken);
|
||||
};
|
||||
|
||||
// Estimate gas when swap parameters change
|
||||
useEffect(() => {
|
||||
const estimateSwapGas = async () => {
|
||||
if (!swapAmounts || swapAmounts.length === 0 || !selectedFromToken || !selectedToToken || !fromAmount || !isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
await estimateGas();
|
||||
};
|
||||
|
||||
estimateSwapGas();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [swapAmounts, selectedFromToken, selectedToToken, fromAmount, currentSlippage, isConnected]);
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-md mx-auto">
|
||||
<Card className="w-full max-w-md mx-auto relative">
|
||||
<CardHeader>
|
||||
<CardTitle>{t('swap.title')}</CardTitle>
|
||||
<div className="flex justify-between items-center">
|
||||
<CardTitle>Swap</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsSettingsOpen(true)}
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* From Token */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<label className="text-muted-foreground">{t('swap.youPay')}</label>
|
||||
<span className="text-muted-foreground">
|
||||
{t('swap.balance')}: {selectedFromToken ? formatUnits(selectedFromToken.balance, selectedFromToken.decimals) : '0.00'}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => {
|
||||
if (selectedFromToken) {
|
||||
setFromAmount(formatUnits(selectedFromToken.balance, selectedFromToken.decimals));
|
||||
}
|
||||
}}
|
||||
disabled={!selectedFromToken}
|
||||
>
|
||||
MAX
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
@@ -99,7 +246,7 @@ export function SwapForm() {
|
||||
onChange={(e) => setFromAmount(e.target.value)}
|
||||
className="text-2xl h-16"
|
||||
/>
|
||||
<div className="relative min-w-[160px]" ref={fromDropdownRef}>
|
||||
<div className="relative min-w-[160px] space-y-1" ref={fromDropdownRef}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full h-16 justify-between"
|
||||
@@ -112,22 +259,22 @@ export function SwapForm() {
|
||||
)}
|
||||
<ChevronDown className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
<div className="text-xs text-muted-foreground text-right">
|
||||
{t('swap.balance')}: {selectedFromToken ? formatUnits(selectedFromToken.balance, selectedFromToken.decimals) : '0.00'}
|
||||
</div>
|
||||
{isFromDropdownOpen && (
|
||||
<div className="absolute z-50 w-full mt-2 bg-popover border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
||||
{tokenDetails && tokenDetails.length > 0 ? (
|
||||
tokenDetails.map((token) => (
|
||||
<button
|
||||
key={token.address}
|
||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none flex items-center justify-between"
|
||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none"
|
||||
onClick={() => {
|
||||
setSelectedFromToken(token);
|
||||
setIsFromDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="font-medium">{token.symbol}</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatUnits(token.balance, token.decimals)}
|
||||
</span>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
@@ -155,11 +302,8 @@ export function SwapForm() {
|
||||
|
||||
{/* To Token */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<div className="text-sm">
|
||||
<label className="text-muted-foreground">{t('swap.youReceive')}</label>
|
||||
<span className="text-muted-foreground">
|
||||
{t('swap.balance')}: {selectedToToken ? formatUnits(selectedToToken.balance, selectedToToken.decimals) : '0.00'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
@@ -169,8 +313,9 @@ export function SwapForm() {
|
||||
onChange={(e) => setToAmount(e.target.value)}
|
||||
className="text-2xl h-16"
|
||||
disabled={!selectedFromToken}
|
||||
readOnly
|
||||
/>
|
||||
<div className="relative min-w-[160px]" ref={toDropdownRef}>
|
||||
<div className="relative min-w-[160px] space-y-1" ref={toDropdownRef}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full h-16 justify-between"
|
||||
@@ -184,6 +329,9 @@ export function SwapForm() {
|
||||
)}
|
||||
<ChevronDown className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
<div className="text-xs text-muted-foreground text-right">
|
||||
{t('swap.balance')}: {selectedToToken ? formatUnits(selectedToToken.balance, selectedToToken.decimals) : '0.00'}
|
||||
</div>
|
||||
{isToDropdownOpen && (
|
||||
<div className="absolute z-50 w-full mt-2 bg-popover border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
||||
{availableTokens && availableTokens.length > 0 && tokenDetails ? (
|
||||
@@ -191,27 +339,24 @@ export function SwapForm() {
|
||||
tokenDetails
|
||||
.filter((token) =>
|
||||
availableTokens.some((availToken) =>
|
||||
availToken.toLowerCase() === token.address.toLowerCase()
|
||||
availToken.address.toLowerCase() === token.address.toLowerCase()
|
||||
)
|
||||
)
|
||||
.map((token) => (
|
||||
<button
|
||||
key={token.address}
|
||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none flex items-center justify-between"
|
||||
className="w-full px-4 py-3 text-left hover:bg-accent focus:bg-accent focus:outline-none"
|
||||
onClick={() => {
|
||||
setSelectedToToken(token);
|
||||
setIsToDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="font-medium">{token.symbol}</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatUnits(token.balance, token.decimals)}
|
||||
</span>
|
||||
</button>
|
||||
))
|
||||
) : selectedFromToken ? (
|
||||
<div className="px-4 py-3 text-sm text-muted-foreground">
|
||||
{loading ? 'Loading available tokens...' : 'No tokens available for swap'}
|
||||
{loading ? 'Loading available tokens...' : poolsError || 'No tokens available for swap'}
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-4 py-3 text-sm text-muted-foreground">
|
||||
@@ -224,60 +369,254 @@ export function SwapForm() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Slippage Tolerance */}
|
||||
<div className="space-y-3 pt-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<label className="text-sm font-medium">Slippage Tolerance</label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{isCustomSlippage ? customSlippage || '0' : slippage}%
|
||||
</span>
|
||||
{/* 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>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{[0.1, 0.2, 0.3, 1, 2, 3].map((percent) => (
|
||||
<Button
|
||||
key={percent}
|
||||
variant={!isCustomSlippage && slippage === percent ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSlippage(percent);
|
||||
setIsCustomSlippage(false);
|
||||
}}
|
||||
className="flex-1 min-w-[60px]"
|
||||
>
|
||||
{percent}%
|
||||
</Button>
|
||||
))}
|
||||
<div className="flex-1 min-w-[80px] relative">
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Custom"
|
||||
value={customSlippage}
|
||||
onChange={(e) => {
|
||||
setCustomSlippage(e.target.value);
|
||||
setIsCustomSlippage(true);
|
||||
}}
|
||||
onFocus={() => setIsCustomSlippage(true)}
|
||||
className={`h-9 pr-6 ${isCustomSlippage ? 'border-primary' : ''}`}
|
||||
step="0.01"
|
||||
/>
|
||||
{isCustomSlippage && (
|
||||
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
||||
%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Swap Button */}
|
||||
{/* Error message for insufficient balance */}
|
||||
{hasInsufficientBalance && (
|
||||
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||
<p className="text-sm text-destructive">Insufficient balance</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error message for slippage exceeding 5% */}
|
||||
{slippageExceedsLimit && (
|
||||
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
||||
<p className="text-sm text-destructive font-medium">⚠️ Slippage Exceeds 5%</p>
|
||||
<p className="text-xs text-destructive/80 mt-1">
|
||||
The estimated slippage for this swap is {Math.abs(swapAmounts![0].calculatedSlippage!).toFixed(2)}%.
|
||||
We cannot process this swap as you may lose too much money due to the high slippage.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* High slippage warning - show if calculated slippage exceeds max slippage but is under 5% */}
|
||||
{!slippageExceedsLimit && swapAmounts && swapAmounts.length > 0 && swapAmounts[0].calculatedSlippage !== undefined && (
|
||||
Math.abs(swapAmounts[0].calculatedSlippage) > currentSlippage
|
||||
) && (
|
||||
<div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
|
||||
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium">⚠️ Slippage Exceeds Your Tolerance</p>
|
||||
<p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1">
|
||||
The estimated slippage for this swap is {Math.abs(swapAmounts[0].calculatedSlippage).toFixed(2)}%, which exceeds your maximum slippage setting of {currentSlippage}%. This swap may result in less favorable pricing than expected due to low liquidity.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/*/!* Uniswap Quote - Hidden (under construction) *!/*/}
|
||||
{/*{false && fromAmount && selectedFromToken && selectedToToken && (*/}
|
||||
{/* <UniswapQuote*/}
|
||||
{/* amountIn={fromAmount}*/}
|
||||
{/* tokenInAddress={selectedFromToken.address}*/}
|
||||
{/* tokenOutAddress={selectedToToken.address}*/}
|
||||
{/* tokenInDecimals={selectedFromToken.decimals}*/}
|
||||
{/* tokenOutDecimals={selectedToToken.decimals}*/}
|
||||
{/* tokenOutSymbol={selectedToToken.symbol}*/}
|
||||
{/* chainId={chainId || 1}*/}
|
||||
{/* />*/}
|
||||
{/*)}*/}
|
||||
|
||||
{/* Gas Estimate, Slippage, and Fees */}
|
||||
{isConnected && fromAmount && toAmount && (
|
||||
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Estimated Gas Cost:</span>
|
||||
<span className="font-medium">
|
||||
{isEstimatingGas ? 'Calculating...' : gasEstimate ? (gasEstimate.estimatedCostUsd.startsWith('<') ? gasEstimate.estimatedCostUsd : `$${gasEstimate.estimatedCostUsd}`) : '-'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Max Slippage:</span>
|
||||
<span className="font-medium">{maxSlippage}%</span>
|
||||
</div>
|
||||
{swapAmounts && swapAmounts.length > 0 && selectedFromToken && fromAmount && (
|
||||
<>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Total Amount In:</span>
|
||||
<span className="font-medium">
|
||||
{formatUnits(swapAmounts[0].amountIn, selectedFromToken.decimals)} {selectedFromToken.symbol}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Fee:</span>
|
||||
<span className="font-medium">
|
||||
{formatUnits(swapAmounts[0].fee, selectedFromToken.decimals)} {selectedFromToken.symbol}
|
||||
{' '}
|
||||
({((Number(swapAmounts[0].fee) / Number(swapAmounts[0].amountIn)) * 100).toFixed(2)}%)
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Review Button */}
|
||||
<Button
|
||||
className="w-full h-14 text-lg"
|
||||
onClick={handleSwap}
|
||||
disabled={!isConnected || !fromAmount || !toAmount}
|
||||
onClick={() => setIsReviewModalOpen(true)}
|
||||
disabled={!isConnected || !fromAmount || !toAmount || !!poolsError || hasInsufficientBalance || slippageExceedsLimit}
|
||||
>
|
||||
{!isConnected ? t('swap.connectWalletToSwap') : t('swap.swapButton')}
|
||||
{!isConnected
|
||||
? t('swap.connectWalletToSwap')
|
||||
: hasInsufficientBalance
|
||||
? 'Insufficient Balance'
|
||||
: slippageExceedsLimit
|
||||
? 'Slippage Too High'
|
||||
: 'Review'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
|
||||
{/* Settings Modal */}
|
||||
{isSettingsOpen && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-black/80 animate-in fade-in-0"
|
||||
onClick={() => setIsSettingsOpen(false)}
|
||||
/>
|
||||
<div className="fixed left-[50%] top-[50%] z-50 w-full max-w-sm translate-x-[-50%] translate-y-[-50%] animate-in fade-in-0 zoom-in-95">
|
||||
<div className="bg-background border rounded-lg shadow-lg p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold">Settings</h2>
|
||||
<button
|
||||
onClick={() => setIsSettingsOpen(false)}
|
||||
className="rounded-sm opacity-70 hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium block mb-2">
|
||||
Max Slippage
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="number"
|
||||
value={maxSlippage}
|
||||
onChange={(e) => setMaxSlippage(e.target.value)}
|
||||
className="pr-8"
|
||||
step="0.1"
|
||||
min="0"
|
||||
max="100"
|
||||
/>
|
||||
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground">
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
You will be warned if the slippage exceeds this setting, and you can choose whether to proceed with the trade.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Review Modal */}
|
||||
<SwapReviewModal
|
||||
open={isReviewModalOpen}
|
||||
onOpenChange={setIsReviewModalOpen}
|
||||
fromToken={selectedFromToken}
|
||||
toToken={selectedToToken}
|
||||
fromAmount={fromAmount}
|
||||
toAmount={toAmount}
|
||||
slippage={currentSlippage}
|
||||
gasEstimate={gasEstimate}
|
||||
fee={swapAmounts && swapAmounts.length > 0 ? swapAmounts[0].fee : null}
|
||||
onConfirm={async () => {
|
||||
await handleSwap();
|
||||
setIsReviewModalOpen(false);
|
||||
}}
|
||||
isSwapping={isSwapping}
|
||||
/>
|
||||
|
||||
{/* 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-sm w-full mx-4 shadow-lg">
|
||||
{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">Approving Swap</h3>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Swapping {fromAmount} {selectedFromToken?.symbol} → {toAmount} {selectedToToken?.symbol}
|
||||
</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">Swap Confirmed!</h3>
|
||||
|
||||
{/* Display actual amounts or estimates */}
|
||||
<div className="w-full space-y-3">
|
||||
{actualSwapAmounts && selectedFromToken && selectedToToken ? (
|
||||
// Show actual amounts from transaction
|
||||
<>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Swapped:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapAmounts.amountIn, selectedFromToken.decimals)} {selectedFromToken.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Received:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapAmounts.amountOut, selectedToToken.decimals)} {selectedToToken.symbol}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Fee:</span>
|
||||
<span className="font-medium">{formatUnits(actualSwapAmounts.lpFee + actualSwapAmounts.protocolFee, selectedFromToken.decimals)} {selectedFromToken.symbol}</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
// Fallback to estimates
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Successfully swapped {fromAmount} {selectedFromToken?.symbol} to {toAmount} {selectedToToken?.symbol}
|
||||
<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>
|
||||
|
||||
<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">Swap 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>
|
||||
);
|
||||
}
|
||||
|
||||
140
src/components/swap-review-modal.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowDown, X } from 'lucide-react';
|
||||
import type { TokenDetails } from '@/hooks/usePartyPlanner';
|
||||
import type { GasEstimate } from '@/hooks/usePartyPool';
|
||||
import { useToast } from '@/components/ui/toast';
|
||||
|
||||
interface SwapReviewModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
fromToken: TokenDetails | null;
|
||||
toToken: TokenDetails | null;
|
||||
fromAmount: string;
|
||||
toAmount: string;
|
||||
slippage: number;
|
||||
gasEstimate: GasEstimate | null;
|
||||
fee: bigint | null;
|
||||
onConfirm: () => void;
|
||||
isSwapping: boolean;
|
||||
}
|
||||
|
||||
export function SwapReviewModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
fromToken,
|
||||
toToken,
|
||||
fromAmount,
|
||||
toAmount,
|
||||
slippage,
|
||||
gasEstimate,
|
||||
fee,
|
||||
onConfirm,
|
||||
isSwapping,
|
||||
}: SwapReviewModalProps) {
|
||||
if (!open) return null;
|
||||
|
||||
// Calculate exchange rate
|
||||
const exchangeRate = fromAmount && toAmount && parseFloat(fromAmount) > 0
|
||||
? (parseFloat(toAmount) / parseFloat(fromAmount)).toFixed(6)
|
||||
: '0';
|
||||
|
||||
// Format fee
|
||||
const feeFormatted = fee && toToken
|
||||
? (Number(fee) / Math.pow(10, toToken.decimals)).toFixed(6)
|
||||
: '0';
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-black/80 animate-in fade-in-0"
|
||||
onClick={() => onOpenChange(false)}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="fixed left-[50%] top-[50%] z-50 w-full max-w-md translate-x-[-50%] translate-y-[-50%] animate-in fade-in-0 zoom-in-95 slide-in-from-left-1/2 slide-in-from-top-[48%]">
|
||||
<div className="bg-background border rounded-lg shadow-lg p-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold">Review Swap</h2>
|
||||
<button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="rounded-sm opacity-70 hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* From Token */}
|
||||
<div className="flex items-center justify-between p-4 bg-muted/30 rounded-lg">
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground">You pay</div>
|
||||
<div className="text-2xl font-semibold">{fromAmount}</div>
|
||||
</div>
|
||||
<div className="text-xl font-medium">{fromToken?.symbol}</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow */}
|
||||
<div className="flex justify-center">
|
||||
<ArrowDown className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
{/* To Token */}
|
||||
<div className="flex items-center justify-between p-4 bg-muted/30 rounded-lg">
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground">You receive</div>
|
||||
<div className="text-2xl font-semibold">{toAmount}</div>
|
||||
</div>
|
||||
<div className="text-xl font-medium">{toToken?.symbol}</div>
|
||||
</div>
|
||||
|
||||
{/* Details */}
|
||||
<div className="space-y-2 pt-2">
|
||||
{/* Exchange Rate */}
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Rate</span>
|
||||
<span className="font-medium">
|
||||
1 {fromToken?.symbol} = {exchangeRate} {toToken?.symbol}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Fee */}
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Fee</span>
|
||||
<span className="font-medium">{feeFormatted} {toToken?.symbol}</span>
|
||||
</div>
|
||||
|
||||
{/* Slippage */}
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Slippage Tolerance</span>
|
||||
<span className="font-medium">{slippage}%</span>
|
||||
</div>
|
||||
|
||||
{/* Network Fee */}
|
||||
{gasEstimate && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Network Fee</span>
|
||||
<span className="font-medium">
|
||||
{gasEstimate.estimatedCostUsd.startsWith('<') ? gasEstimate.estimatedCostUsd : `$${gasEstimate.estimatedCostUsd}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Approve and Swap Button */}
|
||||
<Button
|
||||
className="w-full h-12 text-lg"
|
||||
onClick={onConfirm}
|
||||
disabled={isSwapping}
|
||||
>
|
||||
{isSwapping ? 'Swapping...' : 'Approve and Swap'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
102
src/components/ui/toast.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||||
import { X, Loader2, CheckCircle2 } from 'lucide-react';
|
||||
|
||||
interface Toast {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
type?: 'loading' | 'success' | 'error';
|
||||
}
|
||||
|
||||
interface ToastContextType {
|
||||
toasts: Toast[];
|
||||
addToast: (toast: Omit<Toast, 'id'>) => string;
|
||||
removeToast: (id: string) => void;
|
||||
updateToast: (id: string, toast: Partial<Omit<Toast, 'id'>>) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
||||
|
||||
export function ToastProvider({ children }: { children: ReactNode }) {
|
||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||
|
||||
const addToast = useCallback((toast: Omit<Toast, 'id'>) => {
|
||||
const id = Math.random().toString(36).substr(2, 9);
|
||||
setToasts((prev) => [...prev, { ...toast, id }]);
|
||||
|
||||
// Auto remove after 5 seconds for success/error toasts
|
||||
if (toast.type === 'success' || toast.type === 'error') {
|
||||
setTimeout(() => {
|
||||
removeToast(id);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
return id;
|
||||
}, []);
|
||||
|
||||
const removeToast = useCallback((id: string) => {
|
||||
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
||||
}, []);
|
||||
|
||||
const updateToast = useCallback((id: string, updates: Partial<Omit<Toast, 'id'>>) => {
|
||||
setToasts((prev) =>
|
||||
prev.map((toast) =>
|
||||
toast.id === id ? { ...toast, ...updates } : toast
|
||||
)
|
||||
);
|
||||
|
||||
// Auto remove if updated to success/error
|
||||
if (updates.type === 'success' || updates.type === 'error') {
|
||||
setTimeout(() => {
|
||||
removeToast(id);
|
||||
}, 5000);
|
||||
}
|
||||
}, [removeToast]);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ toasts, addToast, removeToast, updateToast }}>
|
||||
{children}
|
||||
<div className="fixed bottom-4 right-4 z-[100] flex flex-col gap-2 max-w-md">
|
||||
{toasts.map((toast) => (
|
||||
<div
|
||||
key={toast.id}
|
||||
className="bg-background border rounded-lg shadow-lg p-4 flex items-start gap-3 animate-in slide-in-from-right"
|
||||
>
|
||||
{toast.type === 'loading' && (
|
||||
<Loader2 className="h-5 w-5 animate-spin text-primary mt-0.5" />
|
||||
)}
|
||||
{toast.type === 'success' && (
|
||||
<CheckCircle2 className="h-5 w-5 text-green-500 mt-0.5" />
|
||||
)}
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="font-semibold text-sm">{toast.title}</div>
|
||||
{toast.description && (
|
||||
<div className="text-sm text-muted-foreground mt-1">
|
||||
{toast.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => removeToast(toast.id)}
|
||||
className="rounded-sm opacity-70 hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useToast() {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error('useToast must be used within ToastProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
30
src/contracts/ERC20ABI.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const ERC20ABI = [
|
||||
{
|
||||
type: 'function',
|
||||
name: 'name',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ name: '', type: 'string' }],
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'symbol',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ name: '', type: 'string' }],
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'decimals',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ name: '', type: 'uint8' }],
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'balanceOf',
|
||||
stateMutability: 'view',
|
||||
inputs: [{ name: 'account', type: 'address' }],
|
||||
outputs: [{ name: '', type: 'uint256' }],
|
||||
},
|
||||
] as const;
|
||||
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;
|
||||
@@ -115,42 +115,37 @@ const IPartyPlannerABI = [
|
||||
"name": "newPool",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "name_",
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "symbol_",
|
||||
"name": "symbol",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "_tokens",
|
||||
"name": "tokens",
|
||||
"type": "address[]",
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "_bases",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "_kappa",
|
||||
"name": "kappa",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "_swapFeePpm",
|
||||
"name": "swapFeesPpm",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "flashFeePpm",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_flashFeePpm",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_stable",
|
||||
"name": "stable",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
@@ -199,47 +194,42 @@ const IPartyPlannerABI = [
|
||||
"name": "newPool",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "name_",
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "symbol_",
|
||||
"name": "symbol",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "_tokens",
|
||||
"name": "tokens",
|
||||
"type": "address[]",
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "_bases",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "_tradeFrac",
|
||||
"name": "tradeFrac",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "_targetSlippage",
|
||||
"name": "targetSlippage",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "_swapFeePpm",
|
||||
"name": "swapFeePpm",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_flashFeePpm",
|
||||
"name": "flashFeePpm",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_stable",
|
||||
"name": "stable",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
@@ -283,6 +273,98 @@ const IPartyPlannerABI = [
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "owner",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "poolCount",
|
||||
@@ -315,6 +397,13 @@ const IPartyPlannerABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "renounceOwnership",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapImpl",
|
||||
@@ -341,6 +430,38 @@ const IPartyPlannerABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "transferOwnership",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "OwnershipTransferred",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "previousOwner",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "PartyStarted",
|
||||
@@ -371,6 +492,28 @@ const IPartyPlannerABI = [
|
||||
}
|
||||
],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "error",
|
||||
"name": "OwnableInvalidOwner",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "error",
|
||||
"name": "OwnableUnauthorizedAccount",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "account",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
]
|
||||
}
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -11,11 +11,6 @@ const IPartyPoolABI = [
|
||||
"type": "tuple",
|
||||
"internalType": "struct LMSRStabilized.State",
|
||||
"components": [
|
||||
{
|
||||
"name": "nAssets",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "kappa",
|
||||
"type": "int128",
|
||||
@@ -183,7 +178,7 @@ const IPartyPoolABI = [
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputTokenIndex",
|
||||
"name": "outputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
@@ -200,7 +195,12 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "amountOutUint",
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -240,6 +240,43 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "fee",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "i",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "j",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "fees",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "flashFeePpm",
|
||||
@@ -287,25 +324,6 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "getToken",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract IERC20"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "initialMint",
|
||||
@@ -343,6 +361,26 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "kill",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "killed",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "mint",
|
||||
@@ -377,6 +415,19 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "payable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "mintImpl",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "name",
|
||||
@@ -403,6 +454,19 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "owner",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "protocolFeeAddress",
|
||||
@@ -429,6 +493,13 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "renounceOwnership",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swap",
|
||||
@@ -438,6 +509,11 @@ const IPartyPoolABI = [
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "fundingSelector",
|
||||
"type": "bytes4",
|
||||
"internalType": "bytes4"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
@@ -472,6 +548,11 @@ const IPartyPoolABI = [
|
||||
"name": "unwrap",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "cbData",
|
||||
"type": "bytes",
|
||||
"internalType": "bytes"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -486,7 +567,7 @@ const IPartyPoolABI = [
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -530,7 +611,7 @@ const IPartyPoolABI = [
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -539,13 +620,13 @@ const IPartyPoolABI = [
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapFeePpm",
|
||||
"name": "swapImpl",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
@@ -581,10 +662,20 @@ const IPartyPoolABI = [
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "amountInUsed",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpMinted",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable"
|
||||
@@ -598,6 +689,11 @@ const IPartyPoolABI = [
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "fundingSelector",
|
||||
"type": "bytes4",
|
||||
"internalType": "bytes4"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
@@ -627,6 +723,11 @@ const IPartyPoolABI = [
|
||||
"name": "unwrap",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "cbData",
|
||||
"type": "bytes",
|
||||
"internalType": "bytes"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -641,7 +742,7 @@ const IPartyPoolABI = [
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -661,6 +762,25 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "token",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract IERC20"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "totalSupply",
|
||||
@@ -727,6 +847,19 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "transferOwnership",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "wrapperToken",
|
||||
@@ -735,7 +868,7 @@ const IPartyPoolABI = [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract IWETH9"
|
||||
"internalType": "contract NativeWrapper"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
@@ -813,13 +946,31 @@ const IPartyPoolABI = [
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "targetTokenIndex",
|
||||
"type": "uint256",
|
||||
"name": "tokenOut",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "contract IERC20"
|
||||
},
|
||||
{
|
||||
"name": "amountIn",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "payoutUint",
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
@@ -827,6 +978,55 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "Flash",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "initiator",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "contract IERC3156FlashBorrower"
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "contract IERC20"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "Killed",
|
||||
"inputs": [],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "Mint",
|
||||
@@ -858,6 +1058,31 @@ const IPartyPoolABI = [
|
||||
],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "OwnershipTransferred",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "previousOwner",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "ProtocolFeesCollected",
|
||||
"inputs": [],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"name": "Swap",
|
||||
@@ -897,6 +1122,18 @@ const IPartyPoolABI = [
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"anonymous": false
|
||||
@@ -918,25 +1155,31 @@ const IPartyPoolABI = [
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "inputTokenIndex",
|
||||
"type": "uint256",
|
||||
"name": "tokenIn",
|
||||
"type": "address",
|
||||
"indexed": true,
|
||||
"internalType": "uint256"
|
||||
"internalType": "contract IERC20"
|
||||
},
|
||||
{
|
||||
"name": "grossTransfer",
|
||||
"name": "amountIn",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "netInput",
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "feeTaken",
|
||||
"name": "lpFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFee",
|
||||
"type": "uint256",
|
||||
"indexed": false,
|
||||
"internalType": "uint256"
|
||||
@@ -968,6 +1211,28 @@ const IPartyPoolABI = [
|
||||
}
|
||||
],
|
||||
"anonymous": false
|
||||
},
|
||||
{
|
||||
"type": "error",
|
||||
"name": "OwnableInvalidOwner",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "error",
|
||||
"name": "OwnableUnauthorizedAccount",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "account",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
]
|
||||
}
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const IPartyPoolViewerABI = [
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputTokenIndex",
|
||||
"name": "outputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -50,6 +50,11 @@ const IPartyPoolViewerABI = [
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
@@ -235,12 +240,12 @@ const IPartyPoolViewerABI = [
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "lpMinted",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpMinted",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -284,7 +289,7 @@ const IPartyPoolViewerABI = [
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
|
||||
@@ -2,11 +2,26 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { usePublicClient } from 'wagmi';
|
||||
import chainInfo from '@/app/liquidity-party.json';
|
||||
import chainInfo from '@/contracts/liqp-deployments.json';
|
||||
import IPartyPlannerABI from '@/contracts/IPartyPlannerABI';
|
||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
|
||||
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
|
||||
import { ERC20ABI } from '@/contracts/ERC20ABI';
|
||||
|
||||
// Helper function to format large numbers with K, M, B suffixes
|
||||
function formatTVL(value: number): string {
|
||||
if (value >= 1_000_000_000) {
|
||||
return `$${(value / 1_000_000_000).toFixed(2)}B`;
|
||||
} else if (value >= 1_000_000) {
|
||||
return `$${(value / 1_000_000).toFixed(2)}M`;
|
||||
} else if (value >= 1_000) {
|
||||
return `$${(value / 1_000).toFixed(2)}K`;
|
||||
} else {
|
||||
return `$${value.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function useGetAllTokens(offset: number = 0, limit: number = 100) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -34,7 +49,8 @@ export function useGetAllTokens(offset: number = 0, limit: number = 100) {
|
||||
|
||||
// Get chain ID and contract address
|
||||
const chainId = await publicClient.getChainId();
|
||||
const address = (chainInfo as Record<string, { IPartyPlanner: string; IPartyPoolViewer: string }>)[chainId.toString()]?.IPartyPlanner;
|
||||
// @ts-ignore
|
||||
const address = (chainInfo as Record<string, { v1: { PartyPlanner: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
|
||||
if (!address) {
|
||||
setError('IPartyPlanner contract not found for current chain');
|
||||
@@ -52,6 +68,7 @@ export function useGetAllTokens(offset: number = 0, limit: number = 100) {
|
||||
|
||||
setTokens(result);
|
||||
} catch (err) {
|
||||
console.error('Error calling getAllTokens:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch tokens');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -78,10 +95,24 @@ export interface TokenDetails {
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface SwapRoute {
|
||||
poolAddress: `0x${string}`;
|
||||
inputTokenIndex: number;
|
||||
outputTokenIndex: number;
|
||||
inputTokenDecimal: number;
|
||||
outputTokenDecimal: number;
|
||||
}
|
||||
|
||||
export interface AvailableToken {
|
||||
address: `0x${string}`;
|
||||
symbol: string;
|
||||
swapRoutes: SwapRoute[];
|
||||
}
|
||||
|
||||
export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offset: number = 0, limit: number = 100) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [availableTokens, setAvailableTokens] = useState<`0x${string}`[] | null>(null);
|
||||
const [availableTokens, setAvailableTokens] = useState<AvailableToken[] | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -106,26 +137,25 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and contract address
|
||||
// Get chain ID and contract addresses
|
||||
const chainId = await publicClient.getChainId();
|
||||
const address = (chainInfo as Record<string, { IPartyPlanner: string; IPartyPoolViewer: string }>)[chainId.toString()]?.IPartyPlanner;
|
||||
const plannerAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!address) {
|
||||
setError('IPartyPlanner contract not found for current chain');
|
||||
if (!plannerAddress || !partyInfoAddress) {
|
||||
setError('IPartyPlanner or PartyInfo contract not found for current chain');
|
||||
setAvailableTokens([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call getPoolsByToken function
|
||||
const poolsResult = await publicClient.readContract({
|
||||
address: address as `0x${string}`,
|
||||
address: plannerAddress as `0x${string}`,
|
||||
abi: IPartyPlannerABI,
|
||||
functionName: 'getPoolsByToken',
|
||||
args: [tokenAddress, BigInt(offset), BigInt(limit)],
|
||||
});
|
||||
|
||||
console.log('Pools for token', tokenAddress, ':', poolsResult);
|
||||
|
||||
// Get the symbol of the originally selected token
|
||||
const selectedTokenSymbol = await publicClient.readContract({
|
||||
address: tokenAddress,
|
||||
@@ -138,47 +168,166 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
|
||||
return;
|
||||
}
|
||||
|
||||
// For each pool, fetch all tokens in that pool
|
||||
const allTokensInPools: `0x${string}`[] = [];
|
||||
// Filter pools to only working ones
|
||||
const workingPools: `0x${string}`[] = [];
|
||||
for (const poolAddress of poolsResult) {
|
||||
try {
|
||||
const tokensInPool = await publicClient.readContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'allTokens',
|
||||
}) as readonly `0x${string}`[];
|
||||
const isWorking = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'working',
|
||||
args: [poolAddress],
|
||||
}) as boolean;
|
||||
|
||||
// Add all tokens from this pool
|
||||
allTokensInPools.push(...tokensInPool);
|
||||
} catch (err) {
|
||||
console.error('Error fetching tokens from pool', poolAddress, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates by address
|
||||
const uniqueTokenAddresses = Array.from(new Set(allTokensInPools));
|
||||
|
||||
// Fetch symbols for all tokens and filter out those matching the selected token's symbol
|
||||
const filteredTokens: `0x${string}`[] = [];
|
||||
for (const token of uniqueTokenAddresses) {
|
||||
try {
|
||||
const tokenSymbol = await publicClient.readContract({
|
||||
address: token,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'symbol',
|
||||
}).catch(() => null);
|
||||
|
||||
// Only include tokens with different symbols
|
||||
if (tokenSymbol && tokenSymbol !== selectedTokenSymbol) {
|
||||
filteredTokens.push(token);
|
||||
if (isWorking) {
|
||||
workingPools.push(poolAddress);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching symbol for token', token, err);
|
||||
console.error(`Error checking if pool ${poolAddress} is working:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Available tokens to swap to (excluding', selectedTokenSymbol, '):', filteredTokens);
|
||||
setAvailableTokens(filteredTokens);
|
||||
// If no working pools found, set error message
|
||||
if (workingPools.length === 0 && poolsResult.length > 0) {
|
||||
setError('This token is no longer supported. All pools containing this token are currently inactive.');
|
||||
setAvailableTokens([]);
|
||||
return;
|
||||
} else if (workingPools.length === 0) {
|
||||
setError('No pools found for this token.');
|
||||
setAvailableTokens([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// First, fetch all tokens from all working pools
|
||||
const poolTokensContracts = workingPools.map(poolAddress => ({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'allTokens',
|
||||
}));
|
||||
|
||||
const poolTokensResults = await publicClient.multicall({
|
||||
contracts: poolTokensContracts as any,
|
||||
allowFailure: true,
|
||||
});
|
||||
|
||||
// Build a flat list of all unique token addresses we need to query
|
||||
const uniqueTokenAddresses = new Set<`0x${string}`>();
|
||||
uniqueTokenAddresses.add(tokenAddress); // Add input token
|
||||
|
||||
poolTokensResults.forEach((result) => {
|
||||
if (result.status === 'success') {
|
||||
const tokens = result.result as readonly `0x${string}`[];
|
||||
tokens.forEach(token => uniqueTokenAddresses.add(token));
|
||||
}
|
||||
});
|
||||
|
||||
const tokenAddressesArray = Array.from(uniqueTokenAddresses);
|
||||
|
||||
// Build multicall for all token symbols and decimals
|
||||
const tokenDataContracts = tokenAddressesArray.flatMap(addr => [
|
||||
{
|
||||
address: addr,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'symbol',
|
||||
},
|
||||
{
|
||||
address: addr,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'decimals',
|
||||
},
|
||||
]);
|
||||
|
||||
const tokenDataResults = await publicClient.multicall({
|
||||
contracts: tokenDataContracts as any,
|
||||
allowFailure: true,
|
||||
});
|
||||
|
||||
// Parse token data into a map
|
||||
const tokenDataMap = new Map<string, { symbol: string | null; decimals: number | null }>();
|
||||
for (let i = 0; i < tokenAddressesArray.length; i++) {
|
||||
const symbolResult = tokenDataResults[i * 2];
|
||||
const decimalsResult = tokenDataResults[i * 2 + 1];
|
||||
tokenDataMap.set(tokenAddressesArray[i].toLowerCase(), {
|
||||
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : null,
|
||||
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : null,
|
||||
});
|
||||
}
|
||||
|
||||
// Map to store available tokens with their swap routes
|
||||
const tokenRoutesMap = new Map<string, AvailableToken>();
|
||||
|
||||
// For each working pool, process tokens
|
||||
for (let poolIdx = 0; poolIdx < workingPools.length; poolIdx++) {
|
||||
const poolAddress = workingPools[poolIdx];
|
||||
const poolTokensResult = poolTokensResults[poolIdx];
|
||||
|
||||
if (poolTokensResult.status !== 'success') {
|
||||
console.error('Failed to fetch tokens for pool', poolAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tokensInPool = poolTokensResult.result as readonly `0x${string}`[];
|
||||
|
||||
// Find the input token index in this pool
|
||||
const inputTokenIndex = tokensInPool.findIndex(
|
||||
(token) => token.toLowerCase() === tokenAddress.toLowerCase()
|
||||
);
|
||||
|
||||
if (inputTokenIndex === -1) {
|
||||
console.error('Input token not found in pool', poolAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
const inputTokenData = tokenDataMap.get(tokenAddress.toLowerCase());
|
||||
const inputTokenDecimal = inputTokenData?.decimals ?? null;
|
||||
|
||||
// Process each token in the pool
|
||||
for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) {
|
||||
const outputTokenAddress = tokensInPool[outputTokenIndex];
|
||||
|
||||
// Skip if it's the same as the input token
|
||||
if (outputTokenIndex === inputTokenIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase());
|
||||
const outputTokenSymbol = outputTokenData?.symbol ?? null;
|
||||
const outputTokenDecimal = outputTokenData?.decimals ?? null;
|
||||
|
||||
// Skip tokens with the same symbol as the selected token
|
||||
if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip tokens if decimals failed to load
|
||||
if (inputTokenDecimal === null || outputTokenDecimal === null) {
|
||||
console.error(`Failed to load decimals for token ${outputTokenAddress} or ${tokenAddress}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create or update the available token entry
|
||||
const tokenKey = outputTokenAddress.toLowerCase();
|
||||
if (!tokenRoutesMap.has(tokenKey)) {
|
||||
tokenRoutesMap.set(tokenKey, {
|
||||
address: outputTokenAddress,
|
||||
symbol: outputTokenSymbol,
|
||||
swapRoutes: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Add this swap route
|
||||
tokenRoutesMap.get(tokenKey)!.swapRoutes.push({
|
||||
poolAddress,
|
||||
inputTokenIndex,
|
||||
outputTokenIndex,
|
||||
inputTokenDecimal,
|
||||
outputTokenDecimal,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const availableTokensList = Array.from(tokenRoutesMap.values());
|
||||
setAvailableTokens(availableTokensList);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch pools and tokens');
|
||||
} finally {
|
||||
@@ -220,55 +369,54 @@ export function useTokenDetails(userAddress: `0x${string}` | undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build multicall contracts array - 4 calls per token (name, symbol, decimals, balanceOf)
|
||||
const contracts = tokens.flatMap((tokenAddress) => [
|
||||
{
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'name',
|
||||
},
|
||||
{
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'symbol',
|
||||
},
|
||||
{
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'decimals',
|
||||
},
|
||||
{
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'balanceOf',
|
||||
args: [userAddress],
|
||||
},
|
||||
]);
|
||||
|
||||
// Execute multicall
|
||||
const results = await publicClient.multicall({
|
||||
contracts: contracts as any,
|
||||
allowFailure: true,
|
||||
});
|
||||
|
||||
// Parse results
|
||||
const details: TokenDetails[] = [];
|
||||
|
||||
// Make individual calls for each token
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const tokenAddress = tokens[i];
|
||||
try {
|
||||
const [name, symbol, decimals, balance] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'name',
|
||||
}).catch(() => 'Unknown'),
|
||||
publicClient.readContract({
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'symbol',
|
||||
}).catch(() => '???'),
|
||||
publicClient.readContract({
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'decimals',
|
||||
}).catch(() => 18),
|
||||
publicClient.readContract({
|
||||
address: tokenAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'balanceOf',
|
||||
args: [userAddress],
|
||||
}).catch(() => BigInt(0)),
|
||||
]);
|
||||
const baseIndex = i * 4;
|
||||
const nameResult = results[baseIndex];
|
||||
const symbolResult = results[baseIndex + 1];
|
||||
const decimalsResult = results[baseIndex + 2];
|
||||
const balanceResult = results[baseIndex + 3];
|
||||
|
||||
details.push({
|
||||
address: tokenAddress,
|
||||
name: name as string,
|
||||
symbol: symbol as string,
|
||||
decimals: Number(decimals),
|
||||
balance: balance as bigint,
|
||||
index: i,
|
||||
});
|
||||
} catch (err) {
|
||||
// Add token with fallback values if individual call fails
|
||||
details.push({
|
||||
address: tokenAddress,
|
||||
name: 'Unknown',
|
||||
symbol: '???',
|
||||
decimals: 18,
|
||||
balance: BigInt(0),
|
||||
index: i,
|
||||
});
|
||||
}
|
||||
details.push({
|
||||
address: tokens[i],
|
||||
name: nameResult.status === 'success' ? (nameResult.result as string) : 'Unknown',
|
||||
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : '???',
|
||||
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18,
|
||||
balance: balanceResult.status === 'success' ? (balanceResult.result as bigint) : BigInt(0),
|
||||
index: i,
|
||||
});
|
||||
}
|
||||
|
||||
setTokenDetails(details);
|
||||
@@ -287,4 +435,543 @@ export function useTokenDetails(userAddress: `0x${string}` | undefined) {
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export interface PoolDetails {
|
||||
address: `0x${string}`;
|
||||
name: string;
|
||||
symbol: string;
|
||||
tokens: readonly `0x${string}`[];
|
||||
price?: string; // Formatted price string
|
||||
tvl?: string; // Formatted TVL string (e.g., "$1.2M")
|
||||
isKilled: boolean; // Whether the pool has been killed
|
||||
}
|
||||
|
||||
export function useGetAllPools(offset: number = 0, limit: number = 100) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [pools, setPools] = useState<readonly `0x${string}`[] | null>(null);
|
||||
const [poolDetails, setPoolDetails] = useState<PoolDetails[] | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Handle hydration for Next.js static export
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted) return;
|
||||
|
||||
const fetchPools = async () => {
|
||||
if (!publicClient) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and contract addresses
|
||||
const chainId = await publicClient.getChainId();
|
||||
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 (!plannerAddress || !partyInfoAddress) {
|
||||
setError('IPartyPlanner or PartyInfo contract not found for current chain');
|
||||
setPools([]);
|
||||
setPoolDetails([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call getAllPools function
|
||||
const result = await publicClient.readContract({
|
||||
address: plannerAddress as `0x${string}`,
|
||||
abi: IPartyPlannerABI,
|
||||
functionName: 'getAllPools',
|
||||
args: [BigInt(offset), BigInt(limit)],
|
||||
});
|
||||
|
||||
setPools(result);
|
||||
|
||||
// Fetch details for each pool and check if it's working
|
||||
const details: PoolDetails[] = [];
|
||||
for (const poolAddress of result) {
|
||||
try {
|
||||
const [name, symbol, tokens, isWorking] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
address: poolAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'name',
|
||||
}).catch(() => 'Unknown Pool'),
|
||||
publicClient.readContract({
|
||||
address: poolAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'symbol',
|
||||
}).catch(() => 'POOL'),
|
||||
publicClient.readContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'allTokens',
|
||||
}).catch(() => [] as readonly `0x${string}`[]),
|
||||
publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'working',
|
||||
args: [poolAddress],
|
||||
}).catch(() => false),
|
||||
]);
|
||||
|
||||
// Fetch pool price and TVL (only for working pools)
|
||||
let priceStr: string | undefined;
|
||||
let tvlStr: string | undefined;
|
||||
|
||||
if (isWorking) {
|
||||
// Fetch token decimals and balance first (needed for both price and TVL)
|
||||
try {
|
||||
if (tokens && tokens.length > 0) {
|
||||
const firstTokenAddress = tokens[0];
|
||||
// Get token decimals, balance, and pool price in parallel
|
||||
const [decimals, balance, priceRaw] = await Promise.all([
|
||||
publicClient.readContract({
|
||||
address: firstTokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'decimals',
|
||||
}) as Promise<number>,
|
||||
publicClient.readContract({
|
||||
address: firstTokenAddress as `0x${string}`,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'balanceOf',
|
||||
args: [poolAddress],
|
||||
}) as Promise<bigint>,
|
||||
publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'poolPrice',
|
||||
args: [poolAddress, BigInt(0)],
|
||||
}),
|
||||
]);
|
||||
|
||||
// Calculate pool price using actual token decimals
|
||||
const price = BigInt(priceRaw as bigint | number);
|
||||
if (price === 0n) {
|
||||
priceStr = undefined;
|
||||
} else {
|
||||
// Convert Q64 format to decimal (price = priceValue / 2^64)
|
||||
const Q64 = 2n ** 64n;
|
||||
const priceFloat = Number(price) / Number(Q64);
|
||||
|
||||
// Adjust for token decimals (poolPrice assumes 18 decimals, adjust based on actual token decimals)
|
||||
const finalPrice = priceFloat * (Math.pow(10, 18) / Math.pow(10, decimals));
|
||||
priceStr = `$${finalPrice.toFixed(4)}`;
|
||||
}
|
||||
|
||||
// Calculate TVL (approximate by getting first token balance and multiplying by number of tokens)
|
||||
const tokenBalance = Number(balance) / Math.pow(10, decimals);
|
||||
const approximateTVL = tokenBalance * tokens.length;
|
||||
tvlStr = formatTVL(approximateTVL);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error fetching pool price/TVL for ${poolAddress}:`, err);
|
||||
priceStr = undefined;
|
||||
tvlStr = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Add all pools (both working and killed)
|
||||
details.push({
|
||||
address: poolAddress,
|
||||
name: name as string,
|
||||
symbol: symbol as string,
|
||||
tokens: tokens as readonly `0x${string}`[],
|
||||
price: priceStr,
|
||||
tvl: tvlStr,
|
||||
isKilled: !isWorking,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching pool details for', poolAddress, err);
|
||||
// Skip pools that fail to load
|
||||
}
|
||||
}
|
||||
|
||||
setPoolDetails(details);
|
||||
} catch (err) {
|
||||
console.error('Error calling getAllPools:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch pools');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPools();
|
||||
}, [publicClient, mounted, offset, limit]);
|
||||
|
||||
return {
|
||||
pools,
|
||||
poolDetails,
|
||||
loading,
|
||||
error,
|
||||
isReady: mounted,
|
||||
};
|
||||
}
|
||||
|
||||
export interface SwapMintAmounts {
|
||||
amountInUsed: bigint;
|
||||
fee: bigint;
|
||||
lpMinted: bigint;
|
||||
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||
}
|
||||
|
||||
export interface BurnSwapAmounts {
|
||||
amountOut: bigint;
|
||||
outFee: bigint;
|
||||
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||
}
|
||||
|
||||
export function useSwapMintAmounts(
|
||||
poolAddress: `0x${string}` | undefined,
|
||||
inputTokenIndex: number | undefined,
|
||||
maxAmountIn: bigint | undefined,
|
||||
inputTokenDecimals: number | undefined // Decimals of the input token
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [swapMintAmounts, setSwapMintAmounts] = useState<SwapMintAmounts | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Handle hydration for Next.js static export
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted || !poolAddress || inputTokenIndex === undefined || !maxAmountIn || maxAmountIn === BigInt(0)) {
|
||||
setLoading(false);
|
||||
setSwapMintAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchSwapMintAmounts = async () => {
|
||||
if (!publicClient) return;
|
||||
|
||||
// Early validation
|
||||
if (inputTokenDecimals === undefined) {
|
||||
setError('inputTokenDecimals is required but was undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
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');
|
||||
setSwapMintAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyPoolViewerABI,
|
||||
functionName: 'swapMintAmounts',
|
||||
args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn],
|
||||
}) as readonly [bigint, bigint, bigint];
|
||||
|
||||
// Fetch and calculate pool price
|
||||
let poolPrice: number | undefined;
|
||||
let calculatedSlippage: number | undefined;
|
||||
|
||||
try {
|
||||
const poolPriceInt128 = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'poolPrice',
|
||||
args: [poolAddress, BigInt(inputTokenIndex)],
|
||||
}) as bigint;
|
||||
|
||||
const basePrice = Number(poolPriceInt128) / (2 ** 64);
|
||||
poolPrice = 1 / (basePrice * Math.pow(10, 18 - inputTokenDecimals));
|
||||
// Calculate slippage
|
||||
const decimalsMultiplier = Math.pow(10, inputTokenDecimals);
|
||||
const lpMinted = Number(result[1]) / Math.pow(10, 18);
|
||||
const amountIn = Number(result[0]) / decimalsMultiplier;
|
||||
const fee = Number(result[2]) / decimalsMultiplier;
|
||||
|
||||
const swapPrice = lpMinted / (amountIn - fee);
|
||||
calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100;
|
||||
} catch (priceErr) {
|
||||
console.error('Error fetching poolPrice or calculating slippage:', priceErr);
|
||||
}
|
||||
|
||||
setSwapMintAmounts({
|
||||
amountInUsed: result[0],
|
||||
fee: result[2],
|
||||
lpMinted: result[1],
|
||||
calculatedSlippage,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error calling swapMintAmounts:', err);
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch swap mint amounts';
|
||||
setError(errorMessage);
|
||||
setSwapMintAmounts(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSwapMintAmounts();
|
||||
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, inputTokenDecimals]);
|
||||
|
||||
return {
|
||||
swapMintAmounts,
|
||||
loading,
|
||||
error,
|
||||
isReady: mounted,
|
||||
};
|
||||
}
|
||||
|
||||
export function useBurnSwapAmounts(
|
||||
poolAddress: `0x${string}` | undefined,
|
||||
lpAmount: bigint | undefined,
|
||||
inputTokenIndex: number | undefined,
|
||||
tokenDecimals: number | undefined // Decimals of the output token
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [burnSwapAmounts, setBurnSwapAmounts] = useState<BurnSwapAmounts | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Handle hydration for Next.js static export
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted || !poolAddress || !lpAmount || lpAmount === BigInt(0) || inputTokenIndex === undefined) {
|
||||
setLoading(false);
|
||||
setBurnSwapAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchBurnSwapAmounts = async () => {
|
||||
if (!publicClient) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
// Get chain ID and contract address
|
||||
const chainId = await publicClient.getChainId();
|
||||
// @ts-ignore
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!partyInfoAddress) {
|
||||
setError('PartyInfo contract not found for current chain');
|
||||
setBurnSwapAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call burnSwapAmounts function - returns [amountOut, outFee]
|
||||
const result = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyPoolViewerABI,
|
||||
functionName: 'burnSwapAmounts',
|
||||
args: [poolAddress, lpAmount, BigInt(inputTokenIndex)],
|
||||
}) as readonly [bigint, bigint];
|
||||
|
||||
// Calculate slippage for burnSwap using poolPrice
|
||||
let calculatedSlippage: number | undefined;
|
||||
if (tokenDecimals !== undefined) {
|
||||
try {
|
||||
// Get the market price from poolPrice (quoteTokenIndex = 0)
|
||||
const poolPriceInt128 = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'poolPrice',
|
||||
args: [poolAddress, BigInt(inputTokenIndex)],
|
||||
}) as bigint;
|
||||
|
||||
// Convert Q64 format to decimal (price = priceValue / 2^64)
|
||||
let poolPrice = Number(poolPriceInt128) / 2 ** 64;
|
||||
poolPrice = (poolPrice * Math.pow(10, 18 - tokenDecimals));
|
||||
|
||||
// For burnSwap: swapPrice = (outAmount + fee) / lpAmount
|
||||
// Need to apply decimal corrections: outAmount and fee are in token decimals, lpAmount is in 18 decimals
|
||||
const outAmountDecimal = Number(result[0]) / Math.pow(10, tokenDecimals);
|
||||
const feeDecimal = Number(result[1]) / Math.pow(10, tokenDecimals);
|
||||
const lpAmountDecimal = Number(lpAmount) / Math.pow(10, 18); // LP tokens have 18 decimals
|
||||
|
||||
const swapPrice = (outAmountDecimal + feeDecimal) / lpAmountDecimal;
|
||||
calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100;
|
||||
|
||||
} catch (slippageErr) {
|
||||
console.error(`Error calculating slippage for burnSwap:`, slippageErr);
|
||||
}
|
||||
}
|
||||
|
||||
const parsedAmounts = {
|
||||
amountOut: result[0],
|
||||
outFee: result[1],
|
||||
calculatedSlippage,
|
||||
};
|
||||
|
||||
setBurnSwapAmounts(parsedAmounts);
|
||||
} catch (err) {
|
||||
console.error('Error calling burnSwapAmounts:', err);
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch burn swap amounts';
|
||||
setError(errorMessage);
|
||||
setBurnSwapAmounts(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchBurnSwapAmounts();
|
||||
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, tokenDecimals]);
|
||||
|
||||
return {
|
||||
burnSwapAmounts,
|
||||
loading,
|
||||
error,
|
||||
isReady: mounted,
|
||||
};
|
||||
}
|
||||
|
||||
export function useLPTokenBalance(
|
||||
poolAddress: `0x${string}` | undefined,
|
||||
userAddress: `0x${string}` | undefined
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [lpBalance, setLpBalance] = useState<bigint | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Handle hydration for Next.js static export
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted || !poolAddress || !userAddress) {
|
||||
setLoading(false);
|
||||
setLpBalance(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchLPBalance = async () => {
|
||||
if (!publicClient) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Call balanceOf on the pool (which is an ERC20 LP token)
|
||||
const balance = await publicClient.readContract({
|
||||
address: poolAddress,
|
||||
abi: ERC20ABI,
|
||||
functionName: 'balanceOf',
|
||||
args: [userAddress],
|
||||
}) as bigint;
|
||||
|
||||
setLpBalance(balance);
|
||||
} catch (err) {
|
||||
console.error('Error fetching LP token balance:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch LP balance');
|
||||
setLpBalance(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchLPBalance();
|
||||
}, [publicClient, mounted, poolAddress, userAddress]);
|
||||
|
||||
return {
|
||||
lpBalance,
|
||||
loading,
|
||||
error,
|
||||
isReady: mounted,
|
||||
};
|
||||
}
|
||||
|
||||
export function useBurnAmounts(
|
||||
poolAddress: `0x${string}` | undefined,
|
||||
lpTokenAmount: bigint | undefined
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [burnAmounts, setBurnAmounts] = useState<bigint[] | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted || !poolAddress || !lpTokenAmount || lpTokenAmount === 0n) {
|
||||
setBurnAmounts(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchBurnAmounts = async () => {
|
||||
if (!publicClient) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Get chain ID and PartyInfo contract address
|
||||
const chainId = await publicClient.getChainId();
|
||||
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
|
||||
|
||||
if (!partyInfoAddress) {
|
||||
setError('PartyInfo contract not found for current chain');
|
||||
setBurnAmounts(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call burnAmounts function
|
||||
const amounts = await publicClient.readContract({
|
||||
address: partyInfoAddress as `0x${string}`,
|
||||
abi: IPartyPoolViewerABI,
|
||||
functionName: 'burnAmounts',
|
||||
args: [poolAddress, lpTokenAmount],
|
||||
}) as bigint[];
|
||||
|
||||
setBurnAmounts(amounts);
|
||||
} catch (err) {
|
||||
console.error('Error fetching burn amounts:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch burn amounts');
|
||||
setBurnAmounts(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchBurnAmounts();
|
||||
}, [publicClient, mounted, poolAddress, lpTokenAmount]);
|
||||
|
||||
return {
|
||||
burnAmounts,
|
||||
loading,
|
||||
error,
|
||||
isReady: mounted,
|
||||
};
|
||||
}
|
||||
897
src/hooks/usePartyPool.ts
Normal file
@@ -0,0 +1,897 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { usePublicClient, useWalletClient } from 'wagmi';
|
||||
import { decodeEventLog, parseUnits } from 'viem';
|
||||
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
|
||||
import chainInfo from '../contracts/liqp-deployments.json';
|
||||
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
|
||||
import type { AvailableToken } from './usePartyPlanner';
|
||||
|
||||
// Q96 constant for price calculations
|
||||
const Q96 = 1n << 96n;
|
||||
|
||||
/**
|
||||
* Calculate slippage percentage based on market price and actual swap execution price
|
||||
* @param marketPrice The current market price from the pool (in Q64 format, already converted to decimal)
|
||||
* @param swapOutputAmount The output amount from the swap
|
||||
* @param swapInputAmount The input amount for the swap
|
||||
* @param swapFee The fee charged for the swap
|
||||
* @returns Slippage as a percentage (e.g., 5.5 means 5.5%)
|
||||
*/
|
||||
export function calculateSlippage(
|
||||
marketPrice: number,
|
||||
swapOutputAmount: number,
|
||||
swapInputAmount: number,
|
||||
swapFee: number
|
||||
): number {
|
||||
// Calculate actual swap price with decimal correction
|
||||
const swapPrice = swapOutputAmount / ((swapInputAmount) - (swapFee));
|
||||
// Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100
|
||||
const slippage = ((marketPrice - swapPrice) / marketPrice) * 100;
|
||||
|
||||
return slippage;
|
||||
}
|
||||
|
||||
export interface SwapAmountResult {
|
||||
tokenAddress: `0x${string}`;
|
||||
tokenSymbol: string;
|
||||
amountIn: bigint;
|
||||
amountOut: bigint;
|
||||
fee: bigint;
|
||||
poolAddress: `0x${string}`;
|
||||
kappa: bigint;
|
||||
inputTokenIndex: number;
|
||||
outputTokenIndex: number;
|
||||
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the best swap route from an array of routes
|
||||
* Primary criterion: lowest fee
|
||||
* Secondary criterion: highest kappa (if fees are equal)
|
||||
*/
|
||||
export function selectBestSwapRoute(routes: SwapAmountResult[]): SwapAmountResult | null {
|
||||
console.log('selectBestSwapRoute called with', routes.length, 'routes');
|
||||
|
||||
if (routes.length === 0) {
|
||||
console.log('No routes available');
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('All routes:', routes.map(r => ({
|
||||
token: r.tokenSymbol,
|
||||
pool: r.poolAddress,
|
||||
fee: r.fee.toString(),
|
||||
kappa: r.kappa.toString(),
|
||||
amountOut: r.amountOut.toString(),
|
||||
})));
|
||||
|
||||
const bestRoute = routes.reduce((best, current) => {
|
||||
// Primary: lowest fee
|
||||
if (current.fee < best.fee) return current;
|
||||
if (current.fee > best.fee) return best;
|
||||
|
||||
// Secondary: if fees are equal, highest kappa
|
||||
if (current.kappa > best.kappa) return current;
|
||||
return best;
|
||||
});
|
||||
|
||||
console.log('Selected best route:', {
|
||||
token: bestRoute.tokenSymbol,
|
||||
pool: bestRoute.poolAddress,
|
||||
fee: bestRoute.fee.toString(),
|
||||
kappa: bestRoute.kappa.toString(),
|
||||
amountOut: bestRoute.amountOut.toString(),
|
||||
});
|
||||
|
||||
return bestRoute;
|
||||
}
|
||||
|
||||
export function useSwapAmounts(
|
||||
availableTokens: AvailableToken[] | null,
|
||||
fromAmount: string,
|
||||
fromTokenDecimals: number,
|
||||
slippagePercent: number
|
||||
) {
|
||||
const publicClient = usePublicClient();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [swapAmounts, setSwapAmounts] = useState<SwapAmountResult[] | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted || !availableTokens || !fromAmount || parseFloat(fromAmount) <= 0) {
|
||||
setSwapAmounts(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const calculateSwapAmounts = async () => {
|
||||
if (!publicClient) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals);
|
||||
|
||||
const results: SwapAmountResult[] = [];
|
||||
const chainId = await publicClient.getChainId();
|
||||
const partyInfoAddress = (chainInfo as any)[chainId]?.v1?.PartyInfo as `0x${string}` | undefined;
|
||||
|
||||
// Calculate swap amounts for ALL routes of each token
|
||||
for (const token of availableTokens) {
|
||||
if (token.swapRoutes.length === 0) continue;
|
||||
|
||||
const routeResults: SwapAmountResult[] = [];
|
||||
|
||||
// Evaluate ALL routes for this token
|
||||
for (const route of token.swapRoutes) {
|
||||
try {
|
||||
// Get swap amounts with NO LIMIT
|
||||
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({
|
||||
address: route.poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'swapAmounts',
|
||||
args: [
|
||||
BigInt(route.inputTokenIndex),
|
||||
BigInt(route.outputTokenIndex),
|
||||
amountInTokenUnits,
|
||||
0n, // NO LIMIT
|
||||
],
|
||||
}) as readonly [bigint, bigint, bigint];
|
||||
|
||||
// Get kappa for this pool
|
||||
const kappa = await publicClient.readContract({
|
||||
address: route.poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'kappa',
|
||||
}) as bigint;
|
||||
|
||||
// Calculate slippage for this route
|
||||
let calculatedSlippage: number | undefined;
|
||||
if (partyInfoAddress) {
|
||||
try {
|
||||
// Get the current market price from PoolInfo
|
||||
const priceInt128 = await publicClient.readContract({
|
||||
address: partyInfoAddress,
|
||||
abi: IPartyInfoABI,
|
||||
functionName: 'price',
|
||||
args: [route.poolAddress, BigInt(route.inputTokenIndex), BigInt(route.outputTokenIndex)],
|
||||
}) as bigint;
|
||||
|
||||
// Convert Q128 format to decimal (price = priceValue / 2^128)
|
||||
// Then apply decimal conversion: 10^18 / 10^outputTokenDecimals
|
||||
const priceQ128 = Number(priceInt128) / 2 ** 128;
|
||||
const decimalAdjustment = 10 ** (route.outputTokenDecimal - route.inputTokenDecimal);
|
||||
const marketPrice = priceQ128 / decimalAdjustment;
|
||||
const swapOutputAmountDecimal = Number(swapOutputAmount) / 10 ** route.outputTokenDecimal;
|
||||
const swapInputAmountDecimal = Number(swapInputAmount) / 10 ** route.inputTokenDecimal;
|
||||
const swapFeeDecimal = Number(inFee) / 10 ** route.inputTokenDecimal;
|
||||
// Calculate slippage using the reusable function
|
||||
|
||||
calculatedSlippage = calculateSlippage(marketPrice, swapOutputAmountDecimal, swapInputAmountDecimal, swapFeeDecimal);
|
||||
console.log('calculatedSlippage', calculatedSlippage)
|
||||
} catch (slippageErr) {
|
||||
console.error(`Error calculating slippage for ${token.symbol}:`, slippageErr);
|
||||
}
|
||||
}
|
||||
|
||||
routeResults.push({
|
||||
tokenAddress: token.address,
|
||||
tokenSymbol: token.symbol,
|
||||
amountIn: swapInputAmount,
|
||||
amountOut: swapOutputAmount,
|
||||
fee: inFee,
|
||||
poolAddress: route.poolAddress,
|
||||
kappa,
|
||||
inputTokenIndex: route.inputTokenIndex,
|
||||
outputTokenIndex: route.outputTokenIndex,
|
||||
calculatedSlippage,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error calculating swap for ${token.symbol} via ${route.poolAddress}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Select the best route for this token using the shared helper
|
||||
const bestRoute = selectBestSwapRoute(routeResults);
|
||||
if (bestRoute) {
|
||||
results.push(bestRoute);
|
||||
}
|
||||
}
|
||||
|
||||
setSwapAmounts(results);
|
||||
} catch (err) {
|
||||
console.error('Error calculating swap amounts:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
calculateSwapAmounts();
|
||||
}, [publicClient, mounted, availableTokens, fromAmount, fromTokenDecimals, slippagePercent]);
|
||||
|
||||
return {
|
||||
swapAmounts,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
export interface GasEstimate {
|
||||
totalGas: bigint;
|
||||
gasPrice: bigint;
|
||||
estimatedCostEth: string;
|
||||
estimatedCostUsd: string;
|
||||
}
|
||||
|
||||
export interface ActualSwapAmounts {
|
||||
amountIn: bigint;
|
||||
amountOut: bigint;
|
||||
lpFee: bigint;
|
||||
protocolFee: bigint;
|
||||
}
|
||||
|
||||
export interface ActualSwapMintAmounts {
|
||||
amountInUsed: bigint;
|
||||
lpMinted: bigint;
|
||||
lpFee: bigint;
|
||||
protocolFee: bigint;
|
||||
}
|
||||
|
||||
export interface ActualBurnSwapAmounts {
|
||||
amountIn: bigint;
|
||||
amountOut: bigint;
|
||||
lpFee: bigint;
|
||||
protocolFee: bigint;
|
||||
}
|
||||
|
||||
export function useSwap() {
|
||||
const { data: walletClient } = useWalletClient();
|
||||
const publicClient = usePublicClient();
|
||||
const [isSwapping, setIsSwapping] = useState(false);
|
||||
const [swapHash, setSwapHash] = useState<`0x${string}` | null>(null);
|
||||
const [swapError, setSwapError] = useState<string | null>(null);
|
||||
const [gasEstimate, setGasEstimate] = useState<GasEstimate | null>(null);
|
||||
const [isEstimatingGas, setIsEstimatingGas] = useState(false);
|
||||
|
||||
const estimateGas = useCallback(async () => {
|
||||
if (!publicClient) {
|
||||
console.error('Public client not available');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsEstimatingGas(true);
|
||||
|
||||
// Get current gas price from the network
|
||||
const gasPrice = await publicClient.getGasPrice();
|
||||
|
||||
// Use fixed, typical gas amounts for AMM operations
|
||||
const approvalGas = 50000n; // ERC20 approval typically uses ~50k gas
|
||||
const swapGas = 150000n; // AMM swap typically uses ~150k gas
|
||||
|
||||
const totalGas = approvalGas + swapGas;
|
||||
const estimatedCostWei = totalGas * gasPrice;
|
||||
// Use more decimal places for testnets with very low gas prices
|
||||
const estimatedCostEth = (Number(estimatedCostWei) / 1e18).toFixed(9);
|
||||
|
||||
// Fetch ETH price in USD
|
||||
let estimatedCostUsd = '0.00';
|
||||
try {
|
||||
const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
|
||||
const data = await response.json();
|
||||
const ethPriceUsd = data.ethereum?.usd || 0;
|
||||
const costInUsd = parseFloat(estimatedCostEth) * ethPriceUsd;
|
||||
// Show "< $0.01" for amounts less than a cent, otherwise show 2 decimal places
|
||||
estimatedCostUsd = costInUsd < 0.01 && costInUsd > 0 ? '< $0.01' : costInUsd.toFixed(2);
|
||||
} catch (priceErr) {
|
||||
console.error('Error fetching ETH price:', priceErr);
|
||||
}
|
||||
|
||||
const estimate: GasEstimate = {
|
||||
totalGas,
|
||||
gasPrice,
|
||||
estimatedCostEth,
|
||||
estimatedCostUsd,
|
||||
};
|
||||
|
||||
setGasEstimate(estimate);
|
||||
return estimate;
|
||||
} catch (err) {
|
||||
console.error('Error estimating gas:', err);
|
||||
return null;
|
||||
} finally {
|
||||
setIsEstimatingGas(false);
|
||||
}
|
||||
}, [publicClient]);
|
||||
|
||||
const executeSwap = async (
|
||||
poolAddress: `0x${string}`,
|
||||
inputTokenAddress: `0x${string}`,
|
||||
inputTokenIndex: number,
|
||||
outputTokenIndex: number,
|
||||
maxAmountIn: bigint,
|
||||
slippagePercent: number
|
||||
) => {
|
||||
if (!walletClient || !publicClient) {
|
||||
setSwapError('Wallet not connected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSwapping(true);
|
||||
setSwapError(null);
|
||||
setSwapHash(null);
|
||||
|
||||
const userAddress = walletClient.account.address;
|
||||
|
||||
// STEP 1: Approve the pool to spend the input token
|
||||
console.log('🔐 Approving token spend...');
|
||||
console.log('Token to approve:', inputTokenAddress);
|
||||
console.log('Spender (pool):', poolAddress);
|
||||
console.log('Amount:', maxAmountIn.toString());
|
||||
|
||||
const approvalHash = await walletClient.writeContract({
|
||||
address: inputTokenAddress,
|
||||
abi: [
|
||||
{
|
||||
name: 'approve',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'spender', type: 'address' },
|
||||
{ name: 'amount', type: 'uint256' }
|
||||
],
|
||||
outputs: [{ name: '', type: 'bool' }]
|
||||
}
|
||||
],
|
||||
functionName: 'approve',
|
||||
args: [poolAddress, maxAmountIn],
|
||||
});
|
||||
|
||||
console.log('✅ Approval transaction submitted:', approvalHash);
|
||||
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
|
||||
console.log('✅ Approval confirmed');
|
||||
|
||||
// STEP 2: Calculate deadline
|
||||
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200);
|
||||
|
||||
console.log('🚀 Executing swap with params:', {
|
||||
pool: poolAddress,
|
||||
payer: userAddress,
|
||||
receiver: userAddress,
|
||||
inputTokenIndex,
|
||||
outputTokenIndex,
|
||||
maxAmountIn: maxAmountIn.toString(),
|
||||
limitPrice: '0 (no limit)',
|
||||
deadline: deadline.toString(),
|
||||
unwrap: false,
|
||||
});
|
||||
|
||||
// STEP 3: Execute the swap transaction with no limit price
|
||||
const hash = await walletClient.writeContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'swap',
|
||||
args: [
|
||||
userAddress, // payer
|
||||
'0x00000000', // selector (bytes4(0))
|
||||
userAddress, // receiver
|
||||
BigInt(inputTokenIndex),
|
||||
BigInt(outputTokenIndex),
|
||||
maxAmountIn,
|
||||
0n, // no limit price
|
||||
deadline,
|
||||
false, // unwrap
|
||||
'0x', // cbData (empty bytes)
|
||||
],
|
||||
});
|
||||
|
||||
setSwapHash(hash);
|
||||
console.log('✅ Swap transaction submitted:', hash);
|
||||
|
||||
// Wait for transaction confirmation
|
||||
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
||||
console.log('✅ Swap transaction confirmed:', receipt);
|
||||
|
||||
// Parse the Swap event from the receipt logs
|
||||
let actualSwapAmounts: ActualSwapAmounts | null = null;
|
||||
for (const log of receipt.logs) {
|
||||
try {
|
||||
const decodedLog = decodeEventLog({
|
||||
abi: IPartyPoolABI,
|
||||
data: log.data,
|
||||
topics: log.topics,
|
||||
});
|
||||
|
||||
if (decodedLog.eventName === 'Swap') {
|
||||
const { amountIn, amountOut, lpFee, protocolFee } = decodedLog.args as {
|
||||
amountIn: bigint;
|
||||
amountOut: bigint;
|
||||
lpFee: bigint;
|
||||
protocolFee: bigint;
|
||||
};
|
||||
|
||||
actualSwapAmounts = {
|
||||
amountIn,
|
||||
amountOut,
|
||||
lpFee,
|
||||
protocolFee,
|
||||
};
|
||||
|
||||
console.log('📊 Actual swap amounts from event:', {
|
||||
amountIn: amountIn.toString(),
|
||||
amountOut: amountOut.toString(),
|
||||
lpFee: lpFee.toString(),
|
||||
protocolFee: protocolFee.toString(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip logs that don't match our ABI
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return { receipt, actualSwapAmounts };
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Swap failed';
|
||||
setSwapError(errorMessage);
|
||||
console.error('❌ Swap error:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
setIsSwapping(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
executeSwap,
|
||||
estimateGas,
|
||||
isSwapping,
|
||||
swapHash,
|
||||
swapError,
|
||||
gasEstimate,
|
||||
isEstimatingGas,
|
||||
};
|
||||
}
|
||||
|
||||
export function useSwapMint() {
|
||||
const publicClient = usePublicClient();
|
||||
const { data: walletClient } = useWalletClient();
|
||||
const [isSwapMinting, setIsSwapMinting] = useState(false);
|
||||
const [swapMintHash, setSwapMintHash] = useState<`0x${string}` | null>(null);
|
||||
const [swapMintError, setSwapMintError] = useState<string | null>(null);
|
||||
|
||||
const executeSwapMint = async (
|
||||
poolAddress: `0x${string}`,
|
||||
inputTokenAddress: `0x${string}`,
|
||||
inputTokenIndex: number,
|
||||
maxAmountIn: bigint
|
||||
) => {
|
||||
if (!walletClient || !publicClient) {
|
||||
setSwapMintError('Wallet not connected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSwapMinting(true);
|
||||
setSwapMintError(null);
|
||||
setSwapMintHash(null);
|
||||
|
||||
const userAddress = walletClient.account.address;
|
||||
|
||||
// STEP 1: Approve the pool to spend the input token
|
||||
console.log('🔐 Approving token spend for swap mint...');
|
||||
console.log('Token to approve:', inputTokenAddress);
|
||||
console.log('Spender (pool):', poolAddress);
|
||||
console.log('Amount:', maxAmountIn.toString());
|
||||
|
||||
const approvalHash = await walletClient.writeContract({
|
||||
address: inputTokenAddress,
|
||||
abi: [
|
||||
{
|
||||
name: 'approve',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'spender', type: 'address' },
|
||||
{ name: 'amount', type: 'uint256' }
|
||||
],
|
||||
outputs: [{ name: '', type: 'bool' }]
|
||||
}
|
||||
],
|
||||
functionName: 'approve',
|
||||
args: [poolAddress, maxAmountIn],
|
||||
});
|
||||
|
||||
console.log('✅ Approval transaction submitted:', approvalHash);
|
||||
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
|
||||
console.log('✅ Approval confirmed');
|
||||
|
||||
// STEP 2: Calculate deadline (5 minutes from now)
|
||||
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); // 5 minutes = 300 seconds
|
||||
|
||||
console.log('🚀 Executing swapMint with params:', {
|
||||
pool: poolAddress,
|
||||
payer: userAddress,
|
||||
receiver: userAddress,
|
||||
inputTokenIndex,
|
||||
maxAmountIn: maxAmountIn.toString(),
|
||||
deadline: deadline.toString(),
|
||||
});
|
||||
|
||||
// STEP 3: Execute the swapMint transaction
|
||||
const hash = await walletClient.writeContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'swapMint',
|
||||
args: [
|
||||
userAddress, // payer
|
||||
userAddress, // receiver
|
||||
BigInt(inputTokenIndex),
|
||||
maxAmountIn,
|
||||
deadline,
|
||||
],
|
||||
});
|
||||
|
||||
setSwapMintHash(hash);
|
||||
console.log('✅ SwapMint transaction submitted:', hash);
|
||||
|
||||
// Wait for transaction confirmation
|
||||
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
||||
console.log('✅ SwapMint transaction confirmed:', receipt);
|
||||
|
||||
// Parse the SwapMint event from the receipt logs
|
||||
let actualSwapMintAmounts: ActualSwapMintAmounts | null = null;
|
||||
for (const log of receipt.logs) {
|
||||
try {
|
||||
const decodedLog = decodeEventLog({
|
||||
abi: IPartyPoolABI,
|
||||
data: log.data,
|
||||
topics: log.topics,
|
||||
});
|
||||
|
||||
if (decodedLog.eventName === 'SwapMint') {
|
||||
const { amountIn, amountOut, lpFee, protocolFee } = decodedLog.args as {
|
||||
amountIn: bigint;
|
||||
amountOut: bigint;
|
||||
lpFee: bigint;
|
||||
protocolFee: bigint;
|
||||
};
|
||||
|
||||
actualSwapMintAmounts = {
|
||||
amountInUsed: amountIn,
|
||||
lpMinted: amountOut,
|
||||
lpFee,
|
||||
protocolFee,
|
||||
};
|
||||
|
||||
console.log('📊 Actual swap mint amounts from event:', {
|
||||
amountInUsed: amountIn.toString(),
|
||||
lpMinted: amountOut.toString(),
|
||||
lpFee: lpFee.toString(),
|
||||
protocolFee: protocolFee.toString(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip logs that don't match our ABI
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return { receipt, actualSwapMintAmounts };
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'SwapMint failed';
|
||||
setSwapMintError(errorMessage);
|
||||
console.error('❌ SwapMint error:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
setIsSwapMinting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
executeSwapMint,
|
||||
isSwapMinting,
|
||||
swapMintHash,
|
||||
swapMintError,
|
||||
};
|
||||
}
|
||||
|
||||
export function useBurnSwap() {
|
||||
const publicClient = usePublicClient();
|
||||
const { data: walletClient } = useWalletClient();
|
||||
const [isBurnSwapping, setIsBurnSwapping] = useState(false);
|
||||
const [burnSwapHash, setBurnSwapHash] = useState<`0x${string}` | null>(null);
|
||||
const [burnSwapError, setBurnSwapError] = useState<string | null>(null);
|
||||
|
||||
const executeBurnSwap = async (
|
||||
poolAddress: `0x${string}`,
|
||||
lpAmount: bigint,
|
||||
inputTokenIndex: number,
|
||||
unwrap: boolean = false
|
||||
) => {
|
||||
if (!walletClient || !publicClient) {
|
||||
setBurnSwapError('Wallet not connected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsBurnSwapping(true);
|
||||
setBurnSwapError(null);
|
||||
setBurnSwapHash(null);
|
||||
|
||||
const userAddress = walletClient.account.address;
|
||||
|
||||
// STEP 1: Approve the pool to spend the LP tokens
|
||||
console.log('🔐 Approving LP token spend for burn swap...');
|
||||
console.log('LP token (pool) to approve:', poolAddress);
|
||||
console.log('Spender (pool):', poolAddress);
|
||||
console.log('Amount:', lpAmount.toString());
|
||||
|
||||
const approvalHash = await walletClient.writeContract({
|
||||
address: poolAddress,
|
||||
abi: [
|
||||
{
|
||||
name: 'approve',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'spender', type: 'address' },
|
||||
{ name: 'amount', type: 'uint256' }
|
||||
],
|
||||
outputs: [{ name: '', type: 'bool' }]
|
||||
}
|
||||
],
|
||||
functionName: 'approve',
|
||||
args: [poolAddress, lpAmount],
|
||||
});
|
||||
|
||||
console.log('✅ Approval transaction submitted:', approvalHash);
|
||||
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
|
||||
console.log('✅ Approval confirmed');
|
||||
|
||||
// STEP 2: Calculate deadline (5 minutes from now)
|
||||
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); // 5 minutes = 300 seconds
|
||||
|
||||
console.log('🚀 Executing burnSwap with params:', {
|
||||
pool: poolAddress,
|
||||
payer: userAddress,
|
||||
receiver: userAddress,
|
||||
lpAmount: lpAmount.toString(),
|
||||
inputTokenIndex,
|
||||
deadline: deadline.toString(),
|
||||
unwrap,
|
||||
});
|
||||
// STEP 3: Execute the burnSwap transaction
|
||||
const hash = await walletClient.writeContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'burnSwap',
|
||||
args: [
|
||||
userAddress, // payer
|
||||
userAddress, // receiver
|
||||
lpAmount,
|
||||
BigInt(inputTokenIndex),
|
||||
deadline,
|
||||
unwrap,
|
||||
],
|
||||
});
|
||||
|
||||
setBurnSwapHash(hash);
|
||||
console.log('✅ BurnSwap transaction submitted:', hash);
|
||||
|
||||
// Wait for transaction confirmation
|
||||
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
||||
console.log('✅ BurnSwap transaction confirmed:', receipt);
|
||||
|
||||
// Parse the BurnSwap event from the receipt logs
|
||||
let actualBurnSwapAmounts: ActualBurnSwapAmounts | null = null;
|
||||
for (const log of receipt.logs) {
|
||||
try {
|
||||
const decodedLog = decodeEventLog({
|
||||
abi: IPartyPoolABI,
|
||||
data: log.data,
|
||||
topics: log.topics,
|
||||
});
|
||||
|
||||
if (decodedLog.eventName === 'BurnSwap') {
|
||||
const { amountIn, amountOut, lpFee, protocolFee } = decodedLog.args as {
|
||||
amountIn: bigint;
|
||||
amountOut: bigint;
|
||||
lpFee: bigint;
|
||||
protocolFee: bigint;
|
||||
};
|
||||
|
||||
actualBurnSwapAmounts = {
|
||||
amountIn,
|
||||
amountOut,
|
||||
lpFee,
|
||||
protocolFee,
|
||||
};
|
||||
|
||||
console.log('📊 Actual burn swap amounts from event:', {
|
||||
amountIn: amountIn.toString(),
|
||||
amountOut: amountOut.toString(),
|
||||
lpFee: lpFee.toString(),
|
||||
protocolFee: protocolFee.toString(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip logs that don't match our ABI
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return { receipt, actualBurnSwapAmounts };
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'BurnSwap failed';
|
||||
setBurnSwapError(errorMessage);
|
||||
console.error('❌ BurnSwap error:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
setIsBurnSwapping(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
executeBurnSwap,
|
||||
isBurnSwapping,
|
||||
burnSwapHash,
|
||||
burnSwapError,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ActualBurnAmounts {
|
||||
lpBurned: bigint;
|
||||
withdrawAmounts: bigint[];
|
||||
}
|
||||
|
||||
export function useBurn() {
|
||||
const publicClient = usePublicClient();
|
||||
const { data: walletClient } = useWalletClient();
|
||||
const [isBurning, setIsBurning] = useState(false);
|
||||
const [burnHash, setBurnHash] = useState<`0x${string}` | null>(null);
|
||||
const [burnError, setBurnError] = useState<string | null>(null);
|
||||
|
||||
const executeBurn = async (
|
||||
poolAddress: `0x${string}`,
|
||||
lpAmount: bigint,
|
||||
unwrap: boolean = false
|
||||
) => {
|
||||
if (!walletClient || !publicClient) {
|
||||
setBurnError('Wallet not connected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsBurning(true);
|
||||
setBurnError(null);
|
||||
setBurnHash(null);
|
||||
|
||||
const userAddress = walletClient.account.address;
|
||||
|
||||
// STEP 1: Approve the pool to spend the LP tokens
|
||||
console.log('🔐 Approving LP token spend for burn...');
|
||||
console.log('LP token (pool) to approve:', poolAddress);
|
||||
console.log('Spender (pool):', poolAddress);
|
||||
console.log('Amount:', lpAmount.toString());
|
||||
|
||||
const approvalHash = await walletClient.writeContract({
|
||||
address: poolAddress,
|
||||
abi: [
|
||||
{
|
||||
name: 'approve',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'spender', type: 'address' },
|
||||
{ name: 'amount', type: 'uint256' }
|
||||
],
|
||||
outputs: [{ name: '', type: 'bool' }]
|
||||
}
|
||||
],
|
||||
functionName: 'approve',
|
||||
args: [poolAddress, lpAmount],
|
||||
account: userAddress,
|
||||
});
|
||||
|
||||
console.log('✅ Approval transaction submitted:', approvalHash);
|
||||
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
|
||||
console.log('✅ Approval confirmed');
|
||||
|
||||
// STEP 2: Calculate deadline (20 minutes from now)
|
||||
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200); // 20 minutes = 1200 seconds
|
||||
|
||||
console.log('🚀 Executing burn with params:', {
|
||||
pool: poolAddress,
|
||||
payer: userAddress,
|
||||
receiver: userAddress,
|
||||
lpAmount: lpAmount.toString(),
|
||||
deadline: deadline.toString(),
|
||||
unwrap,
|
||||
});
|
||||
|
||||
// STEP 3: Execute the burn transaction
|
||||
const hash = await walletClient.writeContract({
|
||||
address: poolAddress,
|
||||
abi: IPartyPoolABI,
|
||||
functionName: 'burn',
|
||||
args: [
|
||||
userAddress, // payer
|
||||
userAddress, // receiver
|
||||
lpAmount,
|
||||
deadline,
|
||||
unwrap,
|
||||
],
|
||||
account: userAddress,
|
||||
});
|
||||
|
||||
setBurnHash(hash);
|
||||
console.log('✅ Burn transaction submitted:', hash);
|
||||
|
||||
// Wait for transaction confirmation
|
||||
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
||||
console.log('✅ Burn transaction confirmed:', receipt);
|
||||
|
||||
// Parse the Burn event from the receipt logs
|
||||
let actualBurnAmounts: ActualBurnAmounts | null = null;
|
||||
for (const log of receipt.logs) {
|
||||
try {
|
||||
const decodedLog = decodeEventLog({
|
||||
abi: IPartyPoolABI,
|
||||
data: log.data,
|
||||
topics: log.topics,
|
||||
});
|
||||
|
||||
if (decodedLog.eventName === 'Burn') {
|
||||
// @ts-ignore
|
||||
const { amounts, lpBurned } = decodedLog.args as {
|
||||
amounts: bigint[];
|
||||
lpBurned: bigint;
|
||||
};
|
||||
|
||||
actualBurnAmounts = {
|
||||
lpBurned,
|
||||
withdrawAmounts: amounts,
|
||||
};
|
||||
|
||||
console.log('📊 Actual burn amounts from event:', {
|
||||
lpBurned: lpBurned.toString(),
|
||||
withdrawAmounts: amounts.map(a => a.toString()),
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip logs that don't match our ABI
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return { receipt, actualBurnAmounts };
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Burn failed';
|
||||
setBurnError(errorMessage);
|
||||
console.error('❌ Burn error:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
setIsBurning(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
executeBurn,
|
||||
isBurning,
|
||||
burnHash,
|
||||
burnError,
|
||||
};
|
||||
}
|
||||
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);
|
||||
});
|
||||
@@ -8,11 +8,11 @@
|
||||
"stake": "Staken"
|
||||
},
|
||||
"swap": {
|
||||
"title": "Tauschen",
|
||||
"title": "",
|
||||
"from": "Von",
|
||||
"to": "Zu",
|
||||
"youPay": "Sie zahlen",
|
||||
"youReceive": "Sie erhalten",
|
||||
"youPay": "Verkaufen",
|
||||
"youReceive": "Kaufen",
|
||||
"balance": "Guthaben",
|
||||
"selectToken": "Token auswählen",
|
||||
"swapButton": "Tauschen",
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
"stake": "Stake"
|
||||
},
|
||||
"swap": {
|
||||
"title": "Swap",
|
||||
"title": "",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"youPay": "You pay",
|
||||
"youReceive": "You receive",
|
||||
"youPay": "Sell",
|
||||
"youReceive": "Buy",
|
||||
"balance": "Balance",
|
||||
"selectToken": "Select token",
|
||||
"swapButton": "Swap",
|
||||
@@ -20,6 +20,15 @@
|
||||
},
|
||||
"stake": {
|
||||
"title": "Stake",
|
||||
"comingSoon": "Coming soon..."
|
||||
"comingSoon": "Coming soon...",
|
||||
"selectPool": "Select Pool",
|
||||
"selectToken": "Select Token",
|
||||
"amount": "Amount",
|
||||
"stakeButton": "Stake",
|
||||
"insufficientBalance": "Insufficient balance",
|
||||
"amountUsed": "Amount Used",
|
||||
"fee": "Fee",
|
||||
"lpMinted": "LP Minted",
|
||||
"amountOut": "Amount Out"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
"stake": "Apostar"
|
||||
},
|
||||
"swap": {
|
||||
"title": "Intercambiar",
|
||||
"title": "",
|
||||
"from": "Desde",
|
||||
"to": "Hasta",
|
||||
"youPay": "Pagas",
|
||||
"youReceive": "Recibes",
|
||||
"youPay": "Vender",
|
||||
"youReceive": "Comprar",
|
||||
"balance": "Saldo",
|
||||
"selectToken": "Seleccionar token",
|
||||
"swapButton": "Intercambiar",
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
"stake": "Staker"
|
||||
},
|
||||
"swap": {
|
||||
"title": "Échanger",
|
||||
"title": "",
|
||||
"from": "De",
|
||||
"to": "À",
|
||||
"youPay": "Vous payez",
|
||||
"youReceive": "Vous recevez",
|
||||
"youPay": "Vendre",
|
||||
"youReceive": "Acheter",
|
||||
"balance": "Solde",
|
||||
"selectToken": "Sélectionner un token",
|
||||
"swapButton": "Échanger",
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
"stake": "ステーキング"
|
||||
},
|
||||
"swap": {
|
||||
"title": "スワップ",
|
||||
"title": "",
|
||||
"from": "から",
|
||||
"to": "へ",
|
||||
"youPay": "支払い",
|
||||
"youReceive": "受取り",
|
||||
"youPay": "売却",
|
||||
"youReceive": "購入",
|
||||
"balance": "残高",
|
||||
"selectToken": "トークンを選択",
|
||||
"swapButton": "スワップ",
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
"stake": "质押"
|
||||
},
|
||||
"swap": {
|
||||
"title": "兑换",
|
||||
"title": "",
|
||||
"from": "从",
|
||||
"to": "到",
|
||||
"youPay": "您支付",
|
||||
"youReceive": "您收到",
|
||||
"youPay": "卖出",
|
||||
"youReceive": "买入",
|
||||
"balance": "余额",
|
||||
"selectToken": "选择代币",
|
||||
"swapButton": "兑换",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||
import { type ThemeProviderProps } from 'next-themes/dist/types';
|
||||
import { type ThemeProviderProps } from 'next-themes';
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
|
||||