From 3408b5885083b51a3d1d2ed33a33d1d3fe97018d Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Tue, 2 Jan 2024 01:39:09 +0700 Subject: [PATCH 001/115] chore: cleanup --- src/constants/rpc.ts | 2 +- src/pages/api/nodes/[$chainID]/[[...$args]].ts | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/constants/rpc.ts b/src/constants/rpc.ts index 9d4825e8..c4e7517c 100644 --- a/src/constants/rpc.ts +++ b/src/constants/rpc.ts @@ -6,4 +6,4 @@ export const CHAIN_IDS_L5_NODES = [ export const CHAIN_IDS_POLKACHU_BACKUP_NODES = [ "osmosis-1", // -] +]; diff --git a/src/pages/api/nodes/[$chainID]/[[...$args]].ts b/src/pages/api/nodes/[$chainID]/[[...$args]].ts index d0fcafdb..0b7aff8c 100644 --- a/src/pages/api/nodes/[$chainID]/[[...$args]].ts +++ b/src/pages/api/nodes/[$chainID]/[[...$args]].ts @@ -1,7 +1,10 @@ import { PageConfig } from "next"; import { NextRequest } from "next/server"; -import { CHAIN_IDS_L5_NODES, CHAIN_IDS_POLKACHU_BACKUP_NODES } from "@/constants/rpc"; +import { + CHAIN_IDS_L5_NODES, + CHAIN_IDS_POLKACHU_BACKUP_NODES, +} from "@/constants/rpc"; import { getCorsDomains } from "@/lib/edge-config"; import { getPolkachuAuthHeader } from "@/utils/api"; import { raise } from "@/utils/assert"; @@ -51,20 +54,18 @@ export default async function handler(req: NextRequest) { searchParams.delete("$args"); const shouldUseL5 = CHAIN_IDS_L5_NODES.includes(chainID); - const shouldUsePolkachuBackup = CHAIN_IDS_POLKACHU_BACKUP_NODES.includes(chainID); + const shouldUsePolkachuBackup = + CHAIN_IDS_POLKACHU_BACKUP_NODES.includes(chainID); const headers = new Headers(); - var rpcURL = ''; - + let rpcURL = `https://${chainID}-skip-rpc.polkachu.com`; if (shouldUseL5) { rpcURL = `https://skip-secretnetwork-rpc.lavenderfive.com`; - } else if (shouldUsePolkachuBackup) { + } + if (shouldUsePolkachuBackup) { rpcURL = `https://${chainID}-skip-rpc-1.polkachu.com`; - } else { - rpcURL = `https://${chainID}-skip-rpc.polkachu.com`; } - if (!shouldUseL5) { headers.set("authorization", getPolkachuAuthHeader()); } From d5a19adc25af70f8c91ff59a6e3c2115cbbb2db6 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Tue, 2 Jan 2024 01:39:17 +0700 Subject: [PATCH 002/115] chore: update registry --- chain-registry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain-registry b/chain-registry index b539099a..a21853a1 160000 --- a/chain-registry +++ b/chain-registry @@ -1 +1 @@ -Subproject commit b539099ada02a25033be7fcdb868f5321296fd26 +Subproject commit a21853a1b1420d30b68848aae93601ba0b1ee1d3 From 72a35d8deecdabea2b810e0c4177ef12b295c61c Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Wed, 3 Jan 2024 23:43:08 +0700 Subject: [PATCH 003/115] chore: update deps and registry Signed-off-by: Griko Nibras --- chain-registry | 2 +- package-lock.json | 875 +++++++++++++++++++++++----------------------- package.json | 64 ++-- 3 files changed, 478 insertions(+), 463 deletions(-) diff --git a/chain-registry b/chain-registry index a21853a1..618b5d7b 160000 --- a/chain-registry +++ b/chain-registry @@ -1 +1 @@ -Subproject commit a21853a1b1420d30b68848aae93601ba0b1ee1d3 +Subproject commit 618b5d7b25dd33c5f38d292b180916d2d0418f16 diff --git a/package-lock.json b/package-lock.json index ccc63d11..9ce4120d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,16 +17,16 @@ "@cosmjs/proto-signing": "0.31.x", "@cosmjs/stargate": "0.31.x", "@cosmjs/tendermint-rpc": "0.31.x", - "@cosmos-kit/core": "^2.7.11", - "@cosmos-kit/cosmostation": "^2.5.1", - "@cosmos-kit/keplr": "^2.5.1", - "@cosmos-kit/leap": "^2.5.1", - "@cosmos-kit/okxwallet": "^2.3.12", - "@cosmos-kit/react": "^2.9.16", - "@cosmos-kit/react-lite": "^2.5.13", - "@cosmos-kit/station": "^2.4.11", - "@cosmos-kit/vectis": "^2.5.1", - "@cosmos-kit/xdefi": "^2.4.13", + "@cosmos-kit/core": "^2.7.14", + "@cosmos-kit/cosmostation": "^2.5.4", + "@cosmos-kit/keplr": "^2.5.4", + "@cosmos-kit/leap": "^2.5.4", + "@cosmos-kit/okxwallet": "^2.3.15", + "@cosmos-kit/react": "^2.9.20", + "@cosmos-kit/react-lite": "^2.5.17", + "@cosmos-kit/station": "^2.4.14", + "@cosmos-kit/vectis": "^2.5.4", + "@cosmos-kit/xdefi": "^2.4.16", "@evmos/proto": "^0.2.1", "@evmos/provider": "^0.3.1", "@evmos/transactions": "^0.3.2", @@ -35,7 +35,7 @@ "@heroicons/react": "^2.1.1", "@injectivelabs/sdk-ts": "^1.14.4", "@injectivelabs/utils": "^1.14.4", - "@keplr-wallet/types": "^0.12.54", + "@keplr-wallet/types": "^0.12.57", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", @@ -44,57 +44,57 @@ "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@react-stately/table": "^3.11.4", - "@sentry/nextjs": "^7.90.0", - "@skip-router/core": "^1.1.2", - "@tanstack/react-query": "^5.14.2", - "@types/node": "^20.10.5", - "@types/react": "^18.2.45", + "@sentry/nextjs": "^7.91.0", + "@skip-router/core": "^1.2.0", + "@tanstack/react-query": "^5.17.1", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", "@vercel/analytics": "^1.1.1", "@vercel/edge-config": "^0.4.1", - "@web3modal/core": "^3.5.1", - "@web3modal/ui": "^3.5.1", + "@web3modal/core": "^3.5.3", + "@web3modal/ui": "^3.5.3", "autoprefixer": "^10.4.16", - "axios": "^1.6.2", - "clsx": "^2.0.0", + "axios": "^1.6.3", + "clsx": "^2.1.0", "cosmjs-types": "0.8.x", - "date-fns": "^3.0.1", + "date-fns": "^3.0.6", "download": "^8.0.0", - "ethers": "^6.9.1", + "ethers": "^6.9.2", "next": "^14.0.4", "postcss": "^8.4.32", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwindcss": "^3.4.0", "tinykeys": "^2.1.0", - "undici": "^6.2.0", - "viem": "^1.20.3", - "wagmi": "^1.4.12", + "undici": "^6.2.1", + "viem": "^1.21.4", + "wagmi": "^1.4.13", "zod": "^3.22.4", "zustand": "^4.4.7" }, "devDependencies": { "@playwright/test": "^1.40.1", - "@tanstack/eslint-plugin-query": "^5.12.1", - "@testing-library/jest-dom": "^6.1.5", + "@tanstack/eslint-plugin-query": "^5.17.1", + "@testing-library/jest-dom": "^6.1.6", "@testing-library/react": "^14.1.2", - "@testing-library/user-event": "^14.5.1", + "@testing-library/user-event": "^14.5.2", "@types/download": "^8.0.5", "@types/jest": "^29.5.11", "@types/testing-library__jest-dom": "^5.14.9", - "@typescript-eslint/eslint-plugin": "^6.15.0", - "@typescript-eslint/parser": "^6.15.0", + "@typescript-eslint/eslint-plugin": "^6.17.0", + "@typescript-eslint/parser": "^6.17.0", "eslint": "^8.56.0", "eslint-config-next": "^14.0.4", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.0", + "eslint-plugin-prettier": "^5.1.2", "eslint-plugin-simple-import-sort": "^10.0.0", "globby": "^14.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "msw": "1.x", "npm-run-all2": "^6.1.1", - "p-map": "^7.0.0", + "p-map": "^7.0.1", "patch-package": "^8.0.0", "prettier": "^3.1.1", "resize-observer-polyfill": "^1.5.1", @@ -299,9 +299,9 @@ } }, "node_modules/@axelar-network/axelarjs-sdk/node_modules/ws": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", - "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, @@ -1166,9 +1166,9 @@ } }, "node_modules/@chain-registry/utils": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@chain-registry/utils/-/utils-1.16.1.tgz", - "integrity": "sha512-f4MG92Nr04g3X/M+eTFzfFbiw1Lm++X6Rs5OCpJb5D0DYNceU5LvH3qk4moxsUZdl+wOHZ0MncFI1XgtxcFofw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@chain-registry/utils/-/utils-1.17.0.tgz", + "integrity": "sha512-MNIfpFM8rF1Gex+LhmuHgUElLbjz2vgmjI9qpRl9MMuQUSndu/NVhCDWcgvGNVKCD6woR6ohD9UIs9n0/Q91AA==", "dependencies": { "@babel/runtime": "^7.21.0", "@chain-registry/types": "^0.17.1", @@ -1428,9 +1428,9 @@ "integrity": "sha512-VBhAgzrrYdIe0O5IbKRqwszbQa7ZyQLx9nEQuHQ3HUplQW7P44COG/ye2n6AzCudtqxmwdX7nyX8ta1J07GoqA==" }, "node_modules/@cosmos-kit/core": { - "version": "2.7.11", - "resolved": "https://registry.npmjs.org/@cosmos-kit/core/-/core-2.7.11.tgz", - "integrity": "sha512-WlUbrFMtLvtZ2O9h81wQ7nJMLkCCc0iqY6owgxAvUIvFIoCvhl7P49TIfeni7+jIuVLpcU8CYO8sdjMfPMUOoA==", + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/@cosmos-kit/core/-/core-2.7.14.tgz", + "integrity": "sha512-MFM4P8y3sENmwpnYqrPiZQTmpIn9m1HP86qYZXTP6ZrD4Lfttpg5o7o8jPU7YDGCPT+3oPgJxHJbSoj/6mKBeQ==", "dependencies": { "@chain-registry/types": "0.17.0", "@walletconnect/types": "2.10.4", @@ -1447,21 +1447,21 @@ } }, "node_modules/@cosmos-kit/cosmostation": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation/-/cosmostation-2.5.1.tgz", - "integrity": "sha512-BQWgtEftfVkYjU0UZq7jJ+fPSobvHgnOQHouh+fb8W4OQWFzprxOJpGRsZ8Nvgf6SUtqrf5Sz06jRg+xtlm7oA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation/-/cosmostation-2.5.4.tgz", + "integrity": "sha512-YRGUI4DKg4Z5gONXQAz0Th0+nmGLIy5yfZHAkB/Ykb6Yso3Co81VIt7nBJIEDVTduuwTCu38oQEohnRF9FObhQ==", "dependencies": { - "@cosmos-kit/cosmostation-extension": "^2.6.1", - "@cosmos-kit/cosmostation-mobile": "^2.5.1" + "@cosmos-kit/cosmostation-extension": "^2.6.4", + "@cosmos-kit/cosmostation-mobile": "^2.5.4" } }, "node_modules/@cosmos-kit/cosmostation-extension": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-extension/-/cosmostation-extension-2.6.1.tgz", - "integrity": "sha512-7g6MGOoDZl06bsALokaICPBRxrLJ9XMJSIfKwl/XGxLvnUEFw0ACEqRTI2cn05V0UKG7XmoiGIxvaME49VRO4w==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-extension/-/cosmostation-extension-2.6.4.tgz", + "integrity": "sha512-7sqjbBp7khIzyCzteUYkk18CTzQaTfiCoyF/h3LPFt3TMYRf0G8t4tHJxmm73L/mzHFwcs2PZHAeyh+Raj1cRw==", "dependencies": { "@chain-registry/cosmostation": "^1.26.0", - "@cosmos-kit/core": "^2.7.11" + "@cosmos-kit/core": "^2.7.14" }, "peerDependencies": { "@cosmjs/amino": ">= 0.28", @@ -1469,31 +1469,31 @@ } }, "node_modules/@cosmos-kit/cosmostation-mobile": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-mobile/-/cosmostation-mobile-2.5.1.tgz", - "integrity": "sha512-sd/xhpgpzRzokUy5KU/PIdzjzQy6hHQavU/ZGImXwSd5oK3u/4QxNQl6tmPEFQmRWcyrGSU1GuBYSwnSe3/zcQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-mobile/-/cosmostation-mobile-2.5.4.tgz", + "integrity": "sha512-m8tdXwXVgpmkErxwblRq5cEegd2bEE7Yg4SwWrx+mtyZBnaA4l0oA3mqtb/cjUKfvxcKvBX0o67kDkBJ8pobFg==", "dependencies": { "@chain-registry/cosmostation": "1.26.0", - "@cosmos-kit/core": "^2.7.11", - "@cosmos-kit/walletconnect": "^2.4.14" + "@cosmos-kit/core": "^2.7.14", + "@cosmos-kit/walletconnect": "^2.4.17" } }, "node_modules/@cosmos-kit/keplr": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr/-/keplr-2.5.1.tgz", - "integrity": "sha512-DwAk+QWX/VOqADfBkgTD98zHOsgVVV0z7EgQUnyDBKTcLrGhZ4cs/02WK0EO4huG20bheUFnD8YTQmASHESsLQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr/-/keplr-2.5.4.tgz", + "integrity": "sha512-1zJVWgwPL04yDlkmGc1yhu8zbZ/MLOolORFU1BS+oL1sHTgdWep5GpBxWYUVTrXljqu7yPLxh7sEWSYI2yyCsA==", "dependencies": { - "@cosmos-kit/keplr-extension": "^2.6.1", - "@cosmos-kit/keplr-mobile": "^2.5.1" + "@cosmos-kit/keplr-extension": "^2.6.4", + "@cosmos-kit/keplr-mobile": "^2.5.4" } }, "node_modules/@cosmos-kit/keplr-extension": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr-extension/-/keplr-extension-2.6.1.tgz", - "integrity": "sha512-lTxYCWyoO3Vy5DEoUEkICgi37CkQm2MFfftNY6llRLz0uq89hNcDZfF9a9g6KKNtwwxriNN+X1ppYDbOlG80Ow==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr-extension/-/keplr-extension-2.6.4.tgz", + "integrity": "sha512-zWcI5cSYnmbJ26gPwdjH6GDSFi/kBGiYcJCqf7Tzh5f1sWu/66l9o6DPokJusKtYEVHnaRkkarAS0IVB1xhTiQ==", "dependencies": { "@chain-registry/keplr": "1.28.0", - "@cosmos-kit/core": "^2.7.11" + "@cosmos-kit/core": "^2.7.14" }, "peerDependencies": { "@cosmjs/amino": ">= 0.28", @@ -1501,13 +1501,13 @@ } }, "node_modules/@cosmos-kit/keplr-mobile": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr-mobile/-/keplr-mobile-2.5.1.tgz", - "integrity": "sha512-1oELwcnYkMxuxwQPXetoI/E5QCot6BWnwccNA9G20u+TG7gjv2H1TjHeWyuSop2tNiYFnszMa0ws2R4FF0UtFA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr-mobile/-/keplr-mobile-2.5.4.tgz", + "integrity": "sha512-GK/XKcn+KPRoEKpqw4JlqWWDw7h/+Q52141JLqK39SJ9HkWqeN/c00Sn75Oy99PAiIRFyX4anPoULKz/qd7k+Q==", "dependencies": { "@chain-registry/keplr": "1.28.0", - "@cosmos-kit/core": "^2.7.11", - "@cosmos-kit/walletconnect": "^2.4.14" + "@cosmos-kit/core": "^2.7.14", + "@cosmos-kit/walletconnect": "^2.4.17" }, "peerDependencies": { "@cosmjs/amino": ">= 0.28", @@ -1515,22 +1515,22 @@ } }, "node_modules/@cosmos-kit/leap": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/leap/-/leap-2.5.1.tgz", - "integrity": "sha512-qZbM2LdTzs9Ebsto1NgKJxzZBmjPCPYT8vA8aPd1OjYvnJjiHzLgW5RP43VLOiIu57wNTHC1BwKd0XLtT6aVTQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/leap/-/leap-2.5.4.tgz", + "integrity": "sha512-PpBvVC7vLU8KfYuiAGBmnkpjENezBZAwfoVHDYHcIGGdRIxTpSrTz0NcZtx3KvArwdsxW7+wA3rgHxe7h1ulAA==", "dependencies": { - "@cosmos-kit/leap-extension": "^2.6.1", - "@cosmos-kit/leap-metamask-cosmos-snap": "^0.4.1", - "@cosmos-kit/leap-mobile": "^2.5.1" + "@cosmos-kit/leap-extension": "^2.6.4", + "@cosmos-kit/leap-metamask-cosmos-snap": "^0.4.4", + "@cosmos-kit/leap-mobile": "^2.5.4" } }, "node_modules/@cosmos-kit/leap-extension": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-extension/-/leap-extension-2.6.1.tgz", - "integrity": "sha512-dE8wihePcVJACz31trAB0/oBRBxbyMfRZ6v6mmHdHl7zr7dXvIFUvJSyGHRWVohk2dluTjqTaSIKBDJg0xRlXA==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-extension/-/leap-extension-2.6.4.tgz", + "integrity": "sha512-MGkjYtBn0HQUGxy+6+NIxdtTf31s757y5XnPCGNVjLiOU2xVd8v/7/URC7dpdG6zaqZWIUrKtcV12BcREgEz7w==", "dependencies": { "@chain-registry/keplr": "1.28.0", - "@cosmos-kit/core": "^2.7.11" + "@cosmos-kit/core": "^2.7.14" }, "peerDependencies": { "@cosmjs/amino": ">= 0.28", @@ -1538,12 +1538,12 @@ } }, "node_modules/@cosmos-kit/leap-metamask-cosmos-snap": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-metamask-cosmos-snap/-/leap-metamask-cosmos-snap-0.4.1.tgz", - "integrity": "sha512-nrtIREHgZ0Wm0aYT0XNiWziqt7CDfZ+WpYMzHPxKzh50Tv1i25bSI38Rck3pRjYMSDFCN9pogKhUTYmE43Ujmw==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-metamask-cosmos-snap/-/leap-metamask-cosmos-snap-0.4.4.tgz", + "integrity": "sha512-kOEH5yviV5evQjgq3EiSx7PZs24rXzjzMnJDSo2DW53wVNmDGmEDhgpilFSwHYwM/Y/EcoLFIE5vAwb4ao773w==", "dependencies": { "@chain-registry/keplr": "1.28.0", - "@cosmos-kit/core": "^2.7.11", + "@cosmos-kit/core": "^2.7.14", "@leapwallet/cosmos-snap-provider": "0.1.25", "@metamask/providers": "^11.1.1" }, @@ -1553,29 +1553,29 @@ } }, "node_modules/@cosmos-kit/leap-mobile": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-mobile/-/leap-mobile-2.5.1.tgz", - "integrity": "sha512-c2Mr15j3VirsLIkTbKgMEWufz5mEI5QP3IXaXobNfMN7WFKBg2QOj8oPpP5WqA1XbZ00vZmjqKd3KHOAPpXHHw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-mobile/-/leap-mobile-2.5.4.tgz", + "integrity": "sha512-k0jh61XfDEje5FyQqiogpRpUHSJQLZfRNJ7404ZK7kJjs8NjNxtpUAay552ce5shKS8if9QgWPycOx6x3SZ4XA==", "dependencies": { "@chain-registry/keplr": "1.28.0", - "@cosmos-kit/core": "^2.7.11", - "@cosmos-kit/walletconnect": "^2.4.14" + "@cosmos-kit/core": "^2.7.14", + "@cosmos-kit/walletconnect": "^2.4.17" } }, "node_modules/@cosmos-kit/okxwallet": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@cosmos-kit/okxwallet/-/okxwallet-2.3.12.tgz", - "integrity": "sha512-LrwioNHyXM21WYr/l2B/7B56Nfq8np4h2t/SDzhuhUoLDlC4k5DH1TkuVfTdWo58ZR2rijDCBP26WveO1nzbew==", + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@cosmos-kit/okxwallet/-/okxwallet-2.3.15.tgz", + "integrity": "sha512-f9DNVdn0OZW/BUgz+5soxvUnDUajAos5h0kWbAm11KOMTHFhneZ52PJCgrHo4PcIwgoqOBENfSuW+Q8GbfNQXw==", "dependencies": { - "@cosmos-kit/okxwallet-extension": "^2.5.12" + "@cosmos-kit/okxwallet-extension": "^2.5.15" } }, "node_modules/@cosmos-kit/okxwallet-extension": { - "version": "2.5.12", - "resolved": "https://registry.npmjs.org/@cosmos-kit/okxwallet-extension/-/okxwallet-extension-2.5.12.tgz", - "integrity": "sha512-+MCp4XGXkXjBquFscNWwriYHWSF+X3TU1gzOVBkxY7PAGsbjSXzcWlPgxzVG58E4guT1gIHrJHLund6n6wvfQw==", + "version": "2.5.15", + "resolved": "https://registry.npmjs.org/@cosmos-kit/okxwallet-extension/-/okxwallet-extension-2.5.15.tgz", + "integrity": "sha512-jx7Sng8l5TkNo0cBCgEEG9kadoaR+6IFRiEpFK+wfWQFHOJyRy8NXMjPW1MZPr9Z0af8Y7WozVUv5o8y9+2vMw==", "dependencies": { - "@cosmos-kit/core": "^2.7.11" + "@cosmos-kit/core": "^2.7.14" }, "peerDependencies": { "@cosmjs/amino": ">= 0.28", @@ -1583,13 +1583,13 @@ } }, "node_modules/@cosmos-kit/react": { - "version": "2.9.16", - "resolved": "https://registry.npmjs.org/@cosmos-kit/react/-/react-2.9.16.tgz", - "integrity": "sha512-5cl2+ed7tkXXLtPKqA/ckj0VX5bZyWoYMDmCuS3iOsJcjWCgkPVcBYT5KDIuzQN2EEfVHdOmP1ksiwrSsKjesA==", + "version": "2.9.20", + "resolved": "https://registry.npmjs.org/@cosmos-kit/react/-/react-2.9.20.tgz", + "integrity": "sha512-FmkkWeT7W7VUTf+B7Qti41e6su+51jI5mGQZcd/YJouJURaqT4ko1qc1i6sOeyI7S9RvSElSTqMe0PZIzJoyww==", "dependencies": { "@chain-registry/types": "0.17.0", - "@cosmos-kit/core": "^2.7.11", - "@cosmos-kit/react-lite": "^2.5.13", + "@cosmos-kit/core": "^2.7.14", + "@cosmos-kit/react-lite": "^2.5.17", "@react-icons/all-files": "^4.1.0" }, "peerDependencies": { @@ -1599,12 +1599,12 @@ } }, "node_modules/@cosmos-kit/react-lite": { - "version": "2.5.13", - "resolved": "https://registry.npmjs.org/@cosmos-kit/react-lite/-/react-lite-2.5.13.tgz", - "integrity": "sha512-CSnySfwcTBWYpmBaewKPz0hjvmIFeKnXi2GW6eaKR2LqUPuXVQCkx8biUpYKYsvQghlLmnL2+7+tmvZI5sTnmQ==", + "version": "2.5.17", + "resolved": "https://registry.npmjs.org/@cosmos-kit/react-lite/-/react-lite-2.5.17.tgz", + "integrity": "sha512-YpaGiCJ0K/h3H8k/mpx45wnsNndhjf3Vkzi3ANdjDskkEM6q9AZQ8NN/e9hDTN05CF5J5EO1UnmAzEgRmkViGg==", "dependencies": { "@chain-registry/types": "0.17.0", - "@cosmos-kit/core": "^2.7.11" + "@cosmos-kit/core": "^2.7.14" }, "peerDependencies": { "react": "^18", @@ -1612,20 +1612,20 @@ } }, "node_modules/@cosmos-kit/station": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/@cosmos-kit/station/-/station-2.4.11.tgz", - "integrity": "sha512-CyXXNsSXjPHMSTk5dz0ozxZOUBYjtp69z1NGHmJDA0/gXqD36Kt67iZQpkY0xA724Vs/Y2Z6JC8LSk8PLttiAQ==", + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@cosmos-kit/station/-/station-2.4.14.tgz", + "integrity": "sha512-yDwO6Aiy5DY4NoFUsU/CBdRPl2gVLVsO/t+Wr+poYdYaTPZe5orXs5QLymbWFFGrW1tx+/dq7p5QqSCF1bZ3sg==", "dependencies": { - "@cosmos-kit/station-extension": "^2.5.11" + "@cosmos-kit/station-extension": "^2.5.14" } }, "node_modules/@cosmos-kit/station-extension": { - "version": "2.5.11", - "resolved": "https://registry.npmjs.org/@cosmos-kit/station-extension/-/station-extension-2.5.11.tgz", - "integrity": "sha512-Ca0fDDZ8hy9d455fHOdAmisiDEkrdcJqPMSqlszfIqx/aYen663jpKZdOLDuvHcZhQrjep/y+roZ3Z5PA8tx/Q==", + "version": "2.5.14", + "resolved": "https://registry.npmjs.org/@cosmos-kit/station-extension/-/station-extension-2.5.14.tgz", + "integrity": "sha512-KsvPL6ES+IRlJWEYBRY5SollNgfNeynFAht3Jf0YIIdE4z9XmCionG8Zs4VWavCvkk1MsuJ8rlEal9VZlWcWTw==", "dependencies": { "@chain-registry/types": "0.17.0", - "@cosmos-kit/core": "^2.7.11", + "@cosmos-kit/core": "^2.7.14", "@terra-money/feather.js": "^1.0.8", "@terra-money/station-connector": "^1.0.5", "@terra-money/wallet-types": "^3.11.2" @@ -1681,20 +1681,20 @@ } }, "node_modules/@cosmos-kit/vectis": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/vectis/-/vectis-2.5.1.tgz", - "integrity": "sha512-G3rIUnrJe3/ZYCvblOoWvISc0Vh4GQvwj7Eij4QQnbr0YKhyXUs71/T78iUUZc3oKlMSfozI+YDsR0I93+6+JQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/vectis/-/vectis-2.5.4.tgz", + "integrity": "sha512-+4BwktYmFAgFksBDo911VPNfrKylYiT8w7ZhGYCcli77NVVX6ckFehMEFw5VLil7n2xBQvUFAdTiDSQcuNGy5w==", "dependencies": { - "@cosmos-kit/vectis-extension": "^2.5.1" + "@cosmos-kit/vectis-extension": "^2.5.4" } }, "node_modules/@cosmos-kit/vectis-extension": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@cosmos-kit/vectis-extension/-/vectis-extension-2.5.1.tgz", - "integrity": "sha512-gKPeSYs4s+cvJlGToofbGLtWLobfgZ3IJ2hBn4aPQ1y4ndbFwb7x7IfRMZvvA/HwlxzdVgfiylubkL8gOBNLFw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@cosmos-kit/vectis-extension/-/vectis-extension-2.5.4.tgz", + "integrity": "sha512-bJNvWenRvN7aIUMKMlqB7R+TPHR+bOGk5KO89cQu9rOlOtZhxqmMjt9fe1HGdcpUaAuLTPVMtdtD5j7rWAKxIg==", "dependencies": { "@chain-registry/keplr": "1.28.0", - "@cosmos-kit/core": "^2.7.11" + "@cosmos-kit/core": "^2.7.14" }, "peerDependencies": { "@cosmjs/amino": ">= 0.28", @@ -1702,11 +1702,11 @@ } }, "node_modules/@cosmos-kit/walletconnect": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@cosmos-kit/walletconnect/-/walletconnect-2.4.14.tgz", - "integrity": "sha512-wIWfx0KiW+QNJDfMVNv6M5ZWR+/NMZrOMqJ4Nqy3y87h4ce/BPatuASRFvcYsFnJQ19hbgzzKWzAtYMit0ML8w==", + "version": "2.4.17", + "resolved": "https://registry.npmjs.org/@cosmos-kit/walletconnect/-/walletconnect-2.4.17.tgz", + "integrity": "sha512-RHPBfA2FbYxCOelvEPB1ZNCWJrz0CqtTABwb0XgqK2nQkq39edLLNKGpFA63+zw3X6kYWfDsblg3kTHVwr9Lug==", "dependencies": { - "@cosmos-kit/core": "^2.7.11", + "@cosmos-kit/core": "^2.7.14", "@walletconnect/sign-client": "^2.9.0", "@walletconnect/types": "2.10.4", "@walletconnect/utils": "^2.9.0", @@ -1718,19 +1718,19 @@ } }, "node_modules/@cosmos-kit/xdefi": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@cosmos-kit/xdefi/-/xdefi-2.4.13.tgz", - "integrity": "sha512-s/v4hbI7l74twM/lAD5AzW+I+BrYvT+MV/dpk4PDWr6eEwSmRN6gvKB/hSI/jokAv1m049wNZId9PMuAVdqtqw==", + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@cosmos-kit/xdefi/-/xdefi-2.4.16.tgz", + "integrity": "sha512-tP34/7Ej4/fnFLQi0RGQ0ppWAP8YZRqi5VXzKSA+zS0BhAa7WXsN7qOXhgti3oEvbd1+1Utioen1t9A/PeucwQ==", "dependencies": { - "@cosmos-kit/xdefi-extension": "^2.5.13" + "@cosmos-kit/xdefi-extension": "^2.5.16" } }, "node_modules/@cosmos-kit/xdefi-extension": { - "version": "2.5.13", - "resolved": "https://registry.npmjs.org/@cosmos-kit/xdefi-extension/-/xdefi-extension-2.5.13.tgz", - "integrity": "sha512-JVRCcOhuGRy5jIM++wHmP9O6RqBDELyf63HPsR9Yxi2eO51CaqB7Lu6aqvFwq66qC3tOfEb3ck/IOwORI+8mMg==", + "version": "2.5.16", + "resolved": "https://registry.npmjs.org/@cosmos-kit/xdefi-extension/-/xdefi-extension-2.5.16.tgz", + "integrity": "sha512-pGymgwVbDclR61A6th9MUwPfB5wlMtXgzJxlvcruLUan37V0SdazfaMV119XkqGl6BtXI0PYgj9qz/w+E6SHjA==", "dependencies": { - "@cosmos-kit/core": "^2.7.11" + "@cosmos-kit/core": "^2.7.14" }, "peerDependencies": { "@cosmjs/amino": ">= 0.28", @@ -4348,9 +4348,9 @@ "integrity": "sha512-T2CiKS2B5n0ZA7CWw0CA6qIAH0XYI1siE50MP+i+V0ZniCGBeL+BMcDw64vFJUcEH+1L5X4sDAzV37fQxGwllA==" }, "node_modules/@keplr-wallet/types": { - "version": "0.12.54", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.54.tgz", - "integrity": "sha512-rt1NgbR560UrHNdUlMLhfnQwISp1MRT12vONA/hbaGIMTUhIBGctHJZJeSl6C90xWlFTRfdrZBwVShAQhN4c9g==", + "version": "0.12.57", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.57.tgz", + "integrity": "sha512-YXSrPD1ys6CEZ3flxHidVrUK0LyiMEb8QVVE+OiCnAwus+l7bUzuNjzCnht3I2KevUyYoco+SWtvsbVJVuA+RA==", "dependencies": { "long": "^4.0.0" } @@ -7659,42 +7659,42 @@ } }, "node_modules/@sentry-internal/feedback": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.90.0.tgz", - "integrity": "sha512-ZIdpwK9KmiE/UYGUgNE3N9A5MWm92rbC/0u04LxQfZh0tGqN2EchwivQpFCWuu5QsKMlsza7aO6YQXsKvwt1Ww==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.91.0.tgz", + "integrity": "sha512-SJKTSaz68F5YIwF79EttBm915M2LnacgZMYRnRumyTmMKnebGhYQLwWbZdpaDvOa1U18dgRajDX8Qed/8A3tXw==", "dependencies": { - "@sentry/core": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0" + "@sentry/core": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.90.0.tgz", - "integrity": "sha512-74jEtpdio9aRkiVBcrY1ZJXek0oFMqxDJK6BkJNCA+aUK1z96V9viehANRk3Nbxm01rWjmH1U4e1siuo9FhjuQ==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.91.0.tgz", + "integrity": "sha512-JH5y6gs6BS0its7WF2DhySu7nkhPDfZcdpAXldxzIlJpqFkuwQKLU5nkYJpiIyZz1NHYYtW5aum2bV2oCOdDRA==", "dependencies": { - "@sentry/core": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0" + "@sentry/core": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/browser": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.90.0.tgz", - "integrity": "sha512-ik3Jwo+TYjoEesJlt3PlHDcPE9h//WwyUsVkV9ZsVx0MSXb8c1VC4uDMsyUqjA+gPImmw1l9KlWZtvaOooZEhg==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.91.0.tgz", + "integrity": "sha512-lJv3x/xekzC/biiyAsVCioq2XnKNOZhI6jY3ZzLJZClYV8eKRi7D3KCsHRvMiCdGak1d/6sVp8F4NYY+YiWy1Q==", "dependencies": { - "@sentry-internal/feedback": "7.90.0", - "@sentry-internal/tracing": "7.90.0", - "@sentry/core": "7.90.0", - "@sentry/replay": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0" + "@sentry-internal/feedback": "7.91.0", + "@sentry-internal/tracing": "7.91.0", + "@sentry/core": "7.91.0", + "@sentry/replay": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0" }, "engines": { "node": ">=8" @@ -7721,25 +7721,25 @@ } }, "node_modules/@sentry/core": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.90.0.tgz", - "integrity": "sha512-HolpdHjULCwehKPWHR6IPQM0NBjmORhlBU7FtCh/e8TtSkZ9ztPsuofNBomMS1+mdbL+yxOIc9KUYEl0zRfeAQ==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", + "integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", "dependencies": { - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0" + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/integrations": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.90.0.tgz", - "integrity": "sha512-Eba1gbXkh08Ov/DuI/oQdi8a17lE0XMTkw2RtPrrvBWkYjdwEE0eI6Ls23BemTqm3+JNzTCPBzuJwdUq3yWbnw==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.91.0.tgz", + "integrity": "sha512-LGRfb+WfG3FaWHtDnJIhtupweat0imCQr2z/5SSbQKzqxHhtlaEU+9IExBmBdzq90n4lRBaVQHA3zGuU02uOhg==", "dependencies": { - "@sentry/core": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0", + "@sentry/core": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0", "localforage": "^1.8.1" }, "engines": { @@ -7747,18 +7747,18 @@ } }, "node_modules/@sentry/nextjs": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.90.0.tgz", - "integrity": "sha512-dQwzXhTBjk3rcRRCDubl5tKzUXG8EaYQLUCxVP8cv6Q/cnWmerECZrApQ5laVRP6DOoaEoIpI1skHj64SJPlDg==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.91.0.tgz", + "integrity": "sha512-wE83+OTEH4yYnDrhMw9eVEARSfZc6xY5qJb9xyYm5rW3+gVjNQZQaUY+wkM61Xdo0T35BN+7U4T88HbwzGeMqA==", "dependencies": { "@rollup/plugin-commonjs": "24.0.0", - "@sentry/core": "7.90.0", - "@sentry/integrations": "7.90.0", - "@sentry/node": "7.90.0", - "@sentry/react": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0", - "@sentry/vercel-edge": "7.90.0", + "@sentry/core": "7.91.0", + "@sentry/integrations": "7.91.0", + "@sentry/node": "7.91.0", + "@sentry/react": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0", + "@sentry/vercel-edge": "7.91.0", "@sentry/webpack-plugin": "1.21.0", "chalk": "3.0.0", "resolve": "1.22.8", @@ -7780,14 +7780,14 @@ } }, "node_modules/@sentry/node": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.90.0.tgz", - "integrity": "sha512-VjDI2MCkidoFEzrMa1gqmwYt1sUhYnu+zoFF5P5jgapTVVJ5xc2b7k/lS62U6IsfxHNrIdTtQHsrbCS5+s0GvQ==", - "dependencies": { - "@sentry-internal/tracing": "7.90.0", - "@sentry/core": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.91.0.tgz", + "integrity": "sha512-hTIfSQxD7L+AKIqyjoq8CWBRkEQrrMZmA3GSZgPI5JFWBHgO0HBo5TH/8TU81oEJh6kqqHAl2ObMhmcnaFqlzg==", + "dependencies": { + "@sentry-internal/tracing": "7.91.0", + "@sentry/core": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0", "https-proxy-agent": "^5.0.0" }, "engines": { @@ -7795,13 +7795,13 @@ } }, "node_modules/@sentry/react": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.90.0.tgz", - "integrity": "sha512-ixhSa3+kmmXvJkKiewLYju0FnYDE8dv8c3E1s4CNU/3SjvqCqcTBOnLzgGleixs3JVklS1JUTU2zS3cwQ2uEOw==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.91.0.tgz", + "integrity": "sha512-7JH2rWaX3WKHHvBcZQ4f/KnkYIXTf7hMojRFncUwPocdtDlhJw/JUvjAYNpEysixXIgsMes3B32lmtZjGjRhwQ==", "dependencies": { - "@sentry/browser": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0", + "@sentry/browser": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -7812,47 +7812,47 @@ } }, "node_modules/@sentry/replay": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.90.0.tgz", - "integrity": "sha512-fsABtzQ5JQI7G5CC4fg05lVI5DTbd1uwKi41xVKFRmCB5NVTHvK7bHgP8n6uSbnle+gp9rUxVPLLBIPjKfaTmw==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.91.0.tgz", + "integrity": "sha512-XwbesnLLNtaVXKtDoyBB96GxJuhGi9zy3a662Ba/McmumCnkXrMQYpQPh08U7MgkTyDRgjDwm7PXDhiKpcb03g==", "dependencies": { - "@sentry-internal/tracing": "7.90.0", - "@sentry/core": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0" + "@sentry-internal/tracing": "7.91.0", + "@sentry/core": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/types": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.90.0.tgz", - "integrity": "sha512-dA0Mtba5jYlcQ6xBsGILZuFq4NGrWLfr2ys036z2JE4H1+3PxOVERlD3Di7p+WKYM5gjFw10Hn3EgUV979E3dA==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", + "integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.90.0.tgz", - "integrity": "sha512-6BpqAzONm/HQbdlL4TY2W2vBSmaG/eVvwUaHoz0wB49EkWwpF6j/SO9Kb/XkiA/qp9GoJVXpnGBFQLPx7kv/Yw==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", + "integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", "dependencies": { - "@sentry/types": "7.90.0" + "@sentry/types": "7.91.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/vercel-edge": { - "version": "7.90.0", - "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-7.90.0.tgz", - "integrity": "sha512-meVUgfqCvn8wlVP/8KjggshFBW8sLCRqynO+pX2b8YO+u/kh0w8UUTC3RXy/upCzoz1wLk274/+U14z11prjlA==", + "version": "7.91.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-7.91.0.tgz", + "integrity": "sha512-CounqhXPwFh67zf6L/q4ACBHHqknT6YY9LdgIAnUd0GGgHzrJPyKcthvh8Je4lNdpo5LFg2gnR+6g6JS8DDYDQ==", "dependencies": { - "@sentry-internal/tracing": "7.90.0", - "@sentry/core": "7.90.0", - "@sentry/types": "7.90.0", - "@sentry/utils": "7.90.0" + "@sentry-internal/tracing": "7.91.0", + "@sentry/core": "7.91.0", + "@sentry/types": "7.91.0", + "@sentry/utils": "7.91.0" }, "engines": { "node": ">=8" @@ -7915,9 +7915,9 @@ } }, "node_modules/@skip-router/core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@skip-router/core/-/core-1.1.2.tgz", - "integrity": "sha512-dSevVO9GCb1AmDcv1HWOkU8FXB/LiIbHF1C55WejPXhXDYkU8xcZZXAyP54itW1uuM8u6u24IbTrdG4sJSMC8w==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@skip-router/core/-/core-1.2.0.tgz", + "integrity": "sha512-HaFLrQxmlgzUDVKNOg0eMm9fvZNDLQ5onj3aehq+4iaqkSDE+gBumUcP79KQPsNkRsNDESdtXQwrCCOAuIuITg==", "dependencies": { "@axelar-network/axelarjs-sdk": "^0.13.6", "@cosmjs/amino": "^0.31.1", @@ -8130,12 +8130,12 @@ } }, "node_modules/@tanstack/eslint-plugin-query": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.12.1.tgz", - "integrity": "sha512-FuG/mN9cUO8KlT7KJzl8Yiw55uyXz1vzxXfoMnBbgiH8vk5WtUbEMdFeDF4ujgO+VSKR10uDdZ1+TIOpHhDUfQ==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.17.1.tgz", + "integrity": "sha512-KWag75cTqs+/t3DSuoyM1Vh9Ns4FOnXZOkCn8nkxvOWnHRBL073c9Kg3YiYP3uo8ftcb1iiw3+a/5w1bf+8A7A==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "^5.54.0" + "@typescript-eslint/utils": "^5.62.0" }, "funding": { "type": "github", @@ -8179,11 +8179,11 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.14.2.tgz", - "integrity": "sha512-SbOzV7UBW8ED3tOnyn6kqNGscnOAfoxShYlbvaQo/5528mDZKpvrwoL/1du1/ukSC6RMAiKmx95SrYqlwPzWDw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.17.1.tgz", + "integrity": "sha512-4JYgX0kU+pvwVQi5eRiHGvBK7WnahEl6lmaxd32ZVSKmByAxLgaewoxBR03cdDNse8lUD2zGOe0sx3M/EGRlmA==", "dependencies": { - "@tanstack/query-core": "5.14.2" + "@tanstack/query-core": "5.17.1" }, "funding": { "type": "github", @@ -8194,9 +8194,9 @@ } }, "node_modules/@tanstack/react-query/node_modules/@tanstack/query-core": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.14.2.tgz", - "integrity": "sha512-QmoJvC72sSWs3hgGis8JdmlDvqLfYGWUK4UG6OR9Q6t28JMN9m2FDwKPqoSJ9YVocELCSjMt/FGjEiLfk8000Q==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.1.tgz", + "integrity": "sha512-kUXozQmU7NBtzX5dM6qfFNZN+YK/9Ct37hnG/ogdgI4mExIx7VH/qRepsPhKfNrJz2w81/JykmM3Uug6sVpUSw==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -8475,12 +8475,12 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.5.tgz", - "integrity": "sha512-3y04JLW+EceVPy2Em3VwNr95dOKqA8DhR0RJHhHKDZNYXcVXnEK7WIrpj4eYU8SVt/qYZ2aRWt/WgQ+grNES8g==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.6.tgz", + "integrity": "sha512-YwuiOdYEcxhfC2u5iNKlvg2Q5MgbutovP6drq7J1HrCbvR+G58BbtoCoq+L/kNlrNFsu2Kt3jaFAviLVxYHJZg==", "dev": true, "dependencies": { - "@adobe/css-tools": "^4.3.1", + "@adobe/css-tools": "^4.3.2", "@babel/runtime": "^7.9.2", "aria-query": "^5.0.0", "chalk": "^3.0.0", @@ -8534,9 +8534,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.5.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", - "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", "dev": true, "engines": { "node": ">=12", @@ -8796,9 +8796,9 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "20.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", - "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "version": "20.10.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", + "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", "dependencies": { "undici-types": "~5.26.4" } @@ -8823,9 +8823,9 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { - "version": "18.2.45", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", - "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", + "version": "18.2.46", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.46.tgz", + "integrity": "sha512-nNCvVBcZlvX4NU1nRRNV/mFl1nNRuTuslAJglQsq+8ldXe5Xv0Wd2f7WTE3jOxhLH2BFfiZGC6GCp+kHQbgG+w==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -8923,16 +8923,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", - "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", + "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.15.0", - "@typescript-eslint/type-utils": "6.15.0", - "@typescript-eslint/utils": "6.15.0", - "@typescript-eslint/visitor-keys": "6.15.0", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/type-utils": "6.17.0", + "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -8958,17 +8958,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", - "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", + "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.15.0", - "@typescript-eslint/types": "6.15.0", - "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", "semver": "^7.5.4" }, "engines": { @@ -8983,15 +8983,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz", - "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", + "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.15.0", - "@typescript-eslint/types": "6.15.0", - "@typescript-eslint/typescript-estree": "6.15.0", - "@typescript-eslint/visitor-keys": "6.15.0", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4" }, "engines": { @@ -9011,13 +9011,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz", - "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", + "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.15.0", - "@typescript-eslint/visitor-keys": "6.15.0" + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -9028,13 +9028,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz", - "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", + "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.15.0", - "@typescript-eslint/utils": "6.15.0", + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/utils": "6.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -9055,17 +9055,17 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", - "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", + "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.15.0", - "@typescript-eslint/types": "6.15.0", - "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", "semver": "^7.5.4" }, "engines": { @@ -9080,9 +9080,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz", - "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", + "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -9093,16 +9093,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz", - "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", + "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.15.0", - "@typescript-eslint/visitor-keys": "6.15.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -9119,6 +9120,15 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -9139,6 +9149,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -9278,12 +9303,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz", - "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", + "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/types": "6.17.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -9384,9 +9409,9 @@ "integrity": "sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg==" }, "node_modules/@wagmi/connectors": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-3.1.10.tgz", - "integrity": "sha512-ZLJC1QaeiZarkF07Cr9mOlVjPO1Lf5TBx+JKBms2y5fUIXlKrxCfQgO/gDCureboI+Us2X3IRI659+XacSGpbA==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-3.1.11.tgz", + "integrity": "sha512-wzxp9f9PtSUFjDUP/QDjc1t7HON4D8wrVKsw35ejdO8hToDpx1gU9lwH/47Zo/1zExGezQc392sjoHSszYd7OA==", "funding": [ { "type": "gitcoin", @@ -9401,10 +9426,10 @@ "@coinbase/wallet-sdk": "^3.6.6", "@safe-global/safe-apps-provider": "^0.18.1", "@safe-global/safe-apps-sdk": "^8.1.0", - "@walletconnect/ethereum-provider": "2.10.6", + "@walletconnect/ethereum-provider": "2.11.0", "@walletconnect/legacy-provider": "^2.0.0", "@walletconnect/modal": "2.6.2", - "@walletconnect/utils": "2.10.2", + "@walletconnect/utils": "2.11.0", "abitype": "0.8.7", "eventemitter3": "^4.0.7" }, @@ -9418,40 +9443,6 @@ } } }, - "node_modules/@wagmi/connectors/node_modules/@walletconnect/types": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.10.2.tgz", - "integrity": "sha512-luNV+07Wdla4STi9AejseCQY31tzWKQ5a7C3zZZaRK/di+rFaAAb7YW04OP4klE7tw/mJRGPTlekZElmHxO8kQ==", - "dependencies": { - "@walletconnect/events": "^1.0.1", - "@walletconnect/heartbeat": "1.2.1", - "@walletconnect/jsonrpc-types": "1.0.3", - "@walletconnect/keyvaluestorage": "^1.0.2", - "@walletconnect/logger": "^2.0.1", - "events": "^3.3.0" - } - }, - "node_modules/@wagmi/connectors/node_modules/@walletconnect/utils": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.10.2.tgz", - "integrity": "sha512-syxXRpc2yhSknMu3IfiBGobxOY7fLfLTJuw+ppKaeO6WUdZpIit3wfuGOcc0Ms3ZPFCrGfyGOoZsCvgdXtptRg==", - "dependencies": { - "@stablelib/chacha20poly1305": "1.0.1", - "@stablelib/hkdf": "1.0.1", - "@stablelib/random": "^1.0.2", - "@stablelib/sha256": "1.0.1", - "@stablelib/x25519": "^1.0.3", - "@walletconnect/relay-api": "^1.0.9", - "@walletconnect/safe-json": "^1.0.2", - "@walletconnect/time": "^1.0.2", - "@walletconnect/types": "2.10.2", - "@walletconnect/window-getters": "^1.0.1", - "@walletconnect/window-metadata": "^1.0.1", - "detect-browser": "5.3.0", - "query-string": "7.1.3", - "uint8arrays": "^3.1.0" - } - }, "node_modules/@wagmi/connectors/node_modules/abitype": { "version": "0.8.7", "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.8.7.tgz", @@ -9467,9 +9458,9 @@ } }, "node_modules/@wagmi/core": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-1.4.12.tgz", - "integrity": "sha512-bLcYmmGgjtl3jAGo8X3Sm6oUwsdjbVxFMu9SWnwHdE4S9JdYeWM57dEhQgq8SYul2yQ7yY2/gimBf1Or0Ky3dQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-1.4.13.tgz", + "integrity": "sha512-ytMCvXbBOgfDu9Qw67279wq/jNEe7EZLjLyekX7ROnvHRADqFr3lwZI6ih41UmtRZAmXAx8Ghyuqy154EjB5mQ==", "funding": [ { "type": "gitcoin", @@ -9481,7 +9472,7 @@ } ], "dependencies": { - "@wagmi/connectors": "3.1.10", + "@wagmi/connectors": "3.1.11", "abitype": "0.8.7", "eventemitter3": "^4.0.7", "zustand": "^4.3.1" @@ -9511,9 +9502,9 @@ } }, "node_modules/@walletconnect/core": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.10.6.tgz", - "integrity": "sha512-Z4vh4ZdfcoQjgPEOxeuF9HUZCVLtV3MgRbS/awLIj/omDrFnOwlBhxi5Syr4Y8muVGC0ocRetQYHae0/gX5crQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.11.0.tgz", + "integrity": "sha512-2Tjp5BCevI7dbmqo/OrCjX4tqgMqwJNQLlQAlphqPfvwlF9+tIu6pGcVbSN3U9zyXzWIZCeleqEaWUeSeET4Ew==", "dependencies": { "@walletconnect/heartbeat": "1.2.1", "@walletconnect/jsonrpc-provider": "1.0.13", @@ -9526,17 +9517,18 @@ "@walletconnect/relay-auth": "^1.0.4", "@walletconnect/safe-json": "^1.0.2", "@walletconnect/time": "^1.0.2", - "@walletconnect/types": "2.10.6", - "@walletconnect/utils": "2.10.6", + "@walletconnect/types": "2.11.0", + "@walletconnect/utils": "2.11.0", "events": "^3.3.0", + "isomorphic-unfetch": "3.1.0", "lodash.isequal": "4.5.0", "uint8arrays": "^3.1.0" } }, "node_modules/@walletconnect/core/node_modules/@walletconnect/types": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.10.6.tgz", - "integrity": "sha512-WgHfiTG1yakmxheaBRiXhUdEmgxwrvsAdOIWaMf/spvrzVKYh6sHI3oyEEky5qj5jjiMiyQBeB57QamzCotbcQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.11.0.tgz", + "integrity": "sha512-AB5b1lrEbCGHxqS2vqfCkIoODieH+ZAUp9rA1O2ftrhnqDJiJK983Df87JhYhECsQUBHHfALphA8ydER0q+9sw==", "dependencies": { "@walletconnect/events": "^1.0.1", "@walletconnect/heartbeat": "1.2.1", @@ -9598,26 +9590,26 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@walletconnect/ethereum-provider": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.10.6.tgz", - "integrity": "sha512-bBQ+yUfxLv8VxNttgNKY7nED35gSVayO/BnLHbNKvyV1gpvSCla5mWB9MsXuQs70MK0g+/qtgRVSrOtdSubaNQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.11.0.tgz", + "integrity": "sha512-YrTeHVjuSuhlUw7SQ6xBJXDuJ6iAC+RwINm9nVhoKYJSHAy3EVSJZOofMKrnecL0iRMtD29nj57mxAInIBRuZA==", "dependencies": { "@walletconnect/jsonrpc-http-connection": "^1.0.7", "@walletconnect/jsonrpc-provider": "^1.0.13", "@walletconnect/jsonrpc-types": "^1.0.3", "@walletconnect/jsonrpc-utils": "^1.0.8", - "@walletconnect/modal": "^2.4.3", - "@walletconnect/sign-client": "2.10.6", - "@walletconnect/types": "2.10.6", - "@walletconnect/universal-provider": "2.10.6", - "@walletconnect/utils": "2.10.6", + "@walletconnect/modal": "^2.6.2", + "@walletconnect/sign-client": "2.11.0", + "@walletconnect/types": "2.11.0", + "@walletconnect/universal-provider": "2.11.0", + "@walletconnect/utils": "2.11.0", "events": "^3.3.0" } }, "node_modules/@walletconnect/ethereum-provider/node_modules/@walletconnect/types": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.10.6.tgz", - "integrity": "sha512-WgHfiTG1yakmxheaBRiXhUdEmgxwrvsAdOIWaMf/spvrzVKYh6sHI3oyEEky5qj5jjiMiyQBeB57QamzCotbcQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.11.0.tgz", + "integrity": "sha512-AB5b1lrEbCGHxqS2vqfCkIoODieH+ZAUp9rA1O2ftrhnqDJiJK983Df87JhYhECsQUBHHfALphA8ydER0q+9sw==", "dependencies": { "@walletconnect/events": "^1.0.1", "@walletconnect/heartbeat": "1.2.1", @@ -9984,25 +9976,25 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@walletconnect/sign-client": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.10.6.tgz", - "integrity": "sha512-EvUWjaZBQu2yKnH5/5F2qzbuiIuUN9ZgrNKgvXkw5z1Dq5RJCks0S9/MFlKH/ZSGqXnLl7uAzBXtoX4sMgbCMA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.11.0.tgz", + "integrity": "sha512-H2ukscibBS+6WrzQWh+WyVBqO5z4F5et12JcwobdwgHnJSlqIoZxqnUYYWNCI5rUR5UKsKWaUyto4AE9N5dw4Q==", "dependencies": { - "@walletconnect/core": "2.10.6", + "@walletconnect/core": "2.11.0", "@walletconnect/events": "^1.0.1", "@walletconnect/heartbeat": "1.2.1", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "^2.0.1", "@walletconnect/time": "^1.0.2", - "@walletconnect/types": "2.10.6", - "@walletconnect/utils": "2.10.6", + "@walletconnect/types": "2.11.0", + "@walletconnect/utils": "2.11.0", "events": "^3.3.0" } }, "node_modules/@walletconnect/sign-client/node_modules/@walletconnect/types": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.10.6.tgz", - "integrity": "sha512-WgHfiTG1yakmxheaBRiXhUdEmgxwrvsAdOIWaMf/spvrzVKYh6sHI3oyEEky5qj5jjiMiyQBeB57QamzCotbcQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.11.0.tgz", + "integrity": "sha512-AB5b1lrEbCGHxqS2vqfCkIoODieH+ZAUp9rA1O2ftrhnqDJiJK983Df87JhYhECsQUBHHfALphA8ydER0q+9sw==", "dependencies": { "@walletconnect/events": "^1.0.1", "@walletconnect/heartbeat": "1.2.1", @@ -10039,25 +10031,25 @@ } }, "node_modules/@walletconnect/universal-provider": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.10.6.tgz", - "integrity": "sha512-CEivusqqoD31BhCTKp08DnrccfGjwD9MFjZs5BNRorDteRFE8zVm9LmP6DSiNJCw82ZajGlZThggLQ/BAATfwA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.11.0.tgz", + "integrity": "sha512-zgJv8jDvIMP4Qse/D9oIRXGdfoNqonsrjPZanQ/CHNe7oXGOBiQND2IIeX+tS0H7uNA0TPvctljCLiIN9nw4eA==", "dependencies": { "@walletconnect/jsonrpc-http-connection": "^1.0.7", "@walletconnect/jsonrpc-provider": "1.0.13", "@walletconnect/jsonrpc-types": "^1.0.2", "@walletconnect/jsonrpc-utils": "^1.0.7", "@walletconnect/logger": "^2.0.1", - "@walletconnect/sign-client": "2.10.6", - "@walletconnect/types": "2.10.6", - "@walletconnect/utils": "2.10.6", + "@walletconnect/sign-client": "2.11.0", + "@walletconnect/types": "2.11.0", + "@walletconnect/utils": "2.11.0", "events": "^3.3.0" } }, "node_modules/@walletconnect/universal-provider/node_modules/@walletconnect/types": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.10.6.tgz", - "integrity": "sha512-WgHfiTG1yakmxheaBRiXhUdEmgxwrvsAdOIWaMf/spvrzVKYh6sHI3oyEEky5qj5jjiMiyQBeB57QamzCotbcQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.11.0.tgz", + "integrity": "sha512-AB5b1lrEbCGHxqS2vqfCkIoODieH+ZAUp9rA1O2ftrhnqDJiJK983Df87JhYhECsQUBHHfALphA8ydER0q+9sw==", "dependencies": { "@walletconnect/events": "^1.0.1", "@walletconnect/heartbeat": "1.2.1", @@ -10068,9 +10060,9 @@ } }, "node_modules/@walletconnect/utils": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.10.6.tgz", - "integrity": "sha512-oRsWWhN2+hi3aiDXrQEOfysz6FHQJGXLsNQPVt+WIBJplO6Szmdau9dbleD88u1iiT4GKPqE0R9FOYvvPm1H/w==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.11.0.tgz", + "integrity": "sha512-hxkHPlTlDQILHfIKXlmzgNJau/YcSBC3XHUSuZuKZbNEw3duFT6h6pm3HT/1+j1a22IG05WDsNBuTCRkwss+BQ==", "dependencies": { "@stablelib/chacha20poly1305": "1.0.1", "@stablelib/hkdf": "1.0.1", @@ -10080,7 +10072,7 @@ "@walletconnect/relay-api": "^1.0.9", "@walletconnect/safe-json": "^1.0.2", "@walletconnect/time": "^1.0.2", - "@walletconnect/types": "2.10.6", + "@walletconnect/types": "2.11.0", "@walletconnect/window-getters": "^1.0.1", "@walletconnect/window-metadata": "^1.0.1", "detect-browser": "5.3.0", @@ -10089,9 +10081,9 @@ } }, "node_modules/@walletconnect/utils/node_modules/@walletconnect/types": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.10.6.tgz", - "integrity": "sha512-WgHfiTG1yakmxheaBRiXhUdEmgxwrvsAdOIWaMf/spvrzVKYh6sHI3oyEEky5qj5jjiMiyQBeB57QamzCotbcQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.11.0.tgz", + "integrity": "sha512-AB5b1lrEbCGHxqS2vqfCkIoODieH+ZAUp9rA1O2ftrhnqDJiJK983Df87JhYhECsQUBHHfALphA8ydER0q+9sw==", "dependencies": { "@walletconnect/events": "^1.0.1", "@walletconnect/heartbeat": "1.2.1", @@ -10129,31 +10121,40 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@web3modal/common": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@web3modal/common/-/common-3.5.1.tgz", - "integrity": "sha512-OBm8ugfK7VoVSIJdZRhcxGFU3yNoHnCVxrOxnJ/+cBnGT/hariWtGOIaGJXaaj+HHWm9sRDsk8iMHS/Nil+lHw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@web3modal/common/-/common-3.5.3.tgz", + "integrity": "sha512-HQKaDHUU5k4gz7BBiCJRmgDnPCIIyAPwz21Z3UcrL3evDv24rlGOD8k8A3E40VxBKdIj5/TjgEUwlX2r+VKFvQ==", "dependencies": { "dayjs": "1.11.10" } }, "node_modules/@web3modal/core": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@web3modal/core/-/core-3.5.1.tgz", - "integrity": "sha512-lJrUbDSU36ejeoUynq+7WoSeDgb50m+Hgj0QnnnB2eG59dZ+2ls5PuEPLsKy/1O4sUyHoUpSqjbd3jUt5ic68g==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@web3modal/core/-/core-3.5.3.tgz", + "integrity": "sha512-wVJR/vnk6Xx1snnZE6rMCTrxxk8vUW4uH478yWa7BTWSGXsbz05tU7FAUXUSvngzbWTZF/GydSZ+JcuJCqwFkA==", "dependencies": { - "@web3modal/common": "3.5.1", + "@web3modal/common": "3.5.3", + "@web3modal/wallet": "3.5.3", "valtio": "1.11.2" } }, "node_modules/@web3modal/ui": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@web3modal/ui/-/ui-3.5.1.tgz", - "integrity": "sha512-sfj0DL6fBdnYEeENV5FvebUzCnx+blch3cRG1lIorCGz9/nB4gBpxNjBFl6ONF7AgekMnnn109JpTqdlmO7Wig==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@web3modal/ui/-/ui-3.5.3.tgz", + "integrity": "sha512-GHi4jJVDa2eTXcoyEUYMEUjMj00MU8oXKAu0YiAppbEzW/CPvr7r/3HR0tDYh2y8sh1650uSxMO7iC3EEoGKtA==", "dependencies": { "lit": "3.1.0", "qrcode": "1.5.3" } }, + "node_modules/@web3modal/wallet": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@web3modal/wallet/-/wallet-3.5.3.tgz", + "integrity": "sha512-tzQlrBYbKIYrFw0JRH7iarZFUgCueWwMyDMmN7Pjy80dNQ8vcNjlLQM97yymVKun0QNBfZZrHLrDP4yd66dW3g==", + "dependencies": { + "zod": "3.22.4" + } + }, "node_modules/@wry/caches": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz", @@ -10733,9 +10734,9 @@ } }, "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", + "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -11703,9 +11704,9 @@ } }, "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", "engines": { "node": ">=6" } @@ -12087,9 +12088,9 @@ } }, "node_modules/date-fns": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.1.tgz", - "integrity": "sha512-cr9igCUa0QSqgAMj7JOrYTY6Nh1rmyGrFDko7ADqfmaQqP/I2N4rlfrLl7AWuzDaoIpz6MNjoEcTPzgZYIrhnA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.6.tgz", + "integrity": "sha512-W+G99rycpKMMF2/YD064b2lE7jJGUe+EjOES7Q8BIGY8sbNdbgcs9XFTZwvzc9Jx1f3k7LB7gZaZa7f8Agzljg==", "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -12493,9 +12494,9 @@ } }, "node_modules/default-browser/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", "dev": true, "dependencies": { "path-key": "^4.0.0" @@ -13469,19 +13470,19 @@ "dev": true }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.0.tgz", - "integrity": "sha512-hQc+2zbnMeXcIkg+pKZtVa+3Yqx4WY7SMkn1PLZ4VbBEU7jJIpVn9347P8BBhTbz6ne85aXvQf30kvexcqBeWw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.2.tgz", + "integrity": "sha512-dhlpWc9vOwohcWmClFcA+HjlvUpuyynYs0Rf+L/P6/0iQE6vlHW9l5bkfzN62/Stm9fbq8ku46qzde76T1xlSg==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.8.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/prettier" + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { "@types/eslint": ">=8.0.0", @@ -13906,9 +13907,9 @@ } }, "node_modules/ethers": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.9.1.tgz", - "integrity": "sha512-kuV8fGd4/8Gj7wkurbsuUsm1DCG6N5gKGYdw3fnWG/7QGknhy1xtHD7kbkCWQAcbAYmzLCLqCPedS3FYncFkKQ==", + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.9.2.tgz", + "integrity": "sha512-YpkrtILnMQz5jSEsJQRTpduaGT/CXuLnUIuOYzHA0v/7c8IX91m2J48wSKjzGL5L9J/Us3tLoUdb+OwE3U+FFQ==", "funding": [ { "type": "individual", @@ -15856,6 +15857,15 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, "node_modules/isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -17368,9 +17378,9 @@ } }, "node_modules/jsdom/node_modules/ws": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", - "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -19146,9 +19156,9 @@ } }, "node_modules/p-map": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.0.tgz", - "integrity": "sha512-EZl03dLKv3RypkrjlevZoNwQMSy4bAblWcR18zhonktnN4fUs3asFQKSe0awn982omGxamvbejqQKQYDJYHCEg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.1.tgz", + "integrity": "sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==", "dev": true, "engines": { "node": ">=18" @@ -20454,9 +20464,9 @@ } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.8.3.tgz", - "integrity": "sha512-//BaTm14Q/gHBn09xlnKNqfI8t6bmdzx2DXYfPBNofN0WUybCEUDcbCWcTa0oF09lzLjZgPphXAsvRiMK0V6Bw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.9.0.tgz", + "integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==", "dev": true, "engines": { "node": ">=16" @@ -20793,9 +20803,9 @@ } }, "node_modules/rpc-websockets": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.8.0.tgz", - "integrity": "sha512-AStkq6KDvSAmA4WiwlK1pDvj/33BWmExTATUokC0v+NhWekXSTNzXS5OGXeYwq501/pj6lBZMofg/h4dx4/tCg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.9.0.tgz", + "integrity": "sha512-DwKewQz1IUA5wfLvgM8wDpPRcr+nWSxuFxx5CbrI2z/MyyZ4nXLM86TvIA+cI1ZAdqC8JIBR1mZR55dzaLU+Hw==", "dependencies": { "@babel/runtime": "^7.17.2", "eventemitter3": "^4.0.7", @@ -20820,9 +20830,9 @@ } }, "node_modules/rpc-websockets/node_modules/ws": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", - "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, @@ -22534,9 +22544,9 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==" }, "node_modules/undici": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.2.0.tgz", - "integrity": "sha512-bglzaehBOLMe+BfK+Gv5Vpwpgdq2skaEBbSH92tcAv37wHHHTEGu43boyfTaHB+mwSo3VjyD/8d+ytPGeqsFIg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.2.1.tgz", + "integrity": "sha512-7Wa9thEM6/LMnnKtxJHlc8SrTlDmxqJecgz1iy8KlsN0/iskQXOQCuPkrZLXbElPaSw5slFFyKIKXyJ3UtbApw==", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -22561,6 +22571,11 @@ "pathe": "^1.1.1" } }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "node_modules/unicorn-magic": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", @@ -22900,9 +22915,9 @@ } }, "node_modules/viem": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/viem/-/viem-1.20.3.tgz", - "integrity": "sha512-7CrmeCb2KYkeCgUmUyb1hsf+IX/PLwi+Np+Vm4YUTPeG82y3HRSgGHSaCOp3d0YtR2kXD3nv9y5kE7LBFE+wWw==", + "version": "1.21.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-1.21.4.tgz", + "integrity": "sha512-BNVYdSaUjeS2zKQgPs+49e5JKocfo60Ib2yiXOWBT6LuVxY1I/6fFX3waEtpXvL1Xn4qu+BVitVtMh9lyThyhQ==", "funding": [ { "type": "github", @@ -22961,9 +22976,9 @@ } }, "node_modules/wagmi": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-1.4.12.tgz", - "integrity": "sha512-QRxpjhdMlZmbYTfn9VQkQMKq+l3kwA1O7tF10vaykPrjbGX+IIlyn72ib9oqW9BfQO7n/Sf/mnVz1zbxRhGPWA==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-1.4.13.tgz", + "integrity": "sha512-AScVYFjqNt1wMgL99Bob7MLdhoTZ3XKiOZL5HVBdy4W1sh7QodA3gQ8IsmTuUrQ7oQaTxjiXEhwg7sWNrPBvJA==", "funding": [ { "type": "gitcoin", @@ -22978,7 +22993,7 @@ "@tanstack/query-sync-storage-persister": "^4.27.1", "@tanstack/react-query": "^4.28.0", "@tanstack/react-query-persist-client": "^4.28.0", - "@wagmi/core": "1.4.12", + "@wagmi/core": "1.4.13", "abitype": "0.8.7", "use-sync-external-store": "^1.2.0" }, diff --git a/package.json b/package.json index aee98d2f..7a6f5a67 100644 --- a/package.json +++ b/package.json @@ -33,16 +33,16 @@ "@cosmjs/proto-signing": "0.31.x", "@cosmjs/stargate": "0.31.x", "@cosmjs/tendermint-rpc": "0.31.x", - "@cosmos-kit/core": "^2.7.11", - "@cosmos-kit/cosmostation": "^2.5.1", - "@cosmos-kit/keplr": "^2.5.1", - "@cosmos-kit/leap": "^2.5.1", - "@cosmos-kit/okxwallet": "^2.3.12", - "@cosmos-kit/react": "^2.9.16", - "@cosmos-kit/react-lite": "^2.5.13", - "@cosmos-kit/station": "^2.4.11", - "@cosmos-kit/vectis": "^2.5.1", - "@cosmos-kit/xdefi": "^2.4.13", + "@cosmos-kit/core": "^2.7.14", + "@cosmos-kit/cosmostation": "^2.5.4", + "@cosmos-kit/keplr": "^2.5.4", + "@cosmos-kit/leap": "^2.5.4", + "@cosmos-kit/okxwallet": "^2.3.15", + "@cosmos-kit/react": "^2.9.20", + "@cosmos-kit/react-lite": "^2.5.17", + "@cosmos-kit/station": "^2.4.14", + "@cosmos-kit/vectis": "^2.5.4", + "@cosmos-kit/xdefi": "^2.4.16", "@evmos/proto": "^0.2.1", "@evmos/provider": "^0.3.1", "@evmos/transactions": "^0.3.2", @@ -51,7 +51,7 @@ "@heroicons/react": "^2.1.1", "@injectivelabs/sdk-ts": "^1.14.4", "@injectivelabs/utils": "^1.14.4", - "@keplr-wallet/types": "^0.12.54", + "@keplr-wallet/types": "^0.12.57", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", @@ -60,57 +60,57 @@ "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@react-stately/table": "^3.11.4", - "@sentry/nextjs": "^7.90.0", - "@skip-router/core": "^1.1.2", - "@tanstack/react-query": "^5.14.2", - "@types/node": "^20.10.5", - "@types/react": "^18.2.45", + "@sentry/nextjs": "^7.91.0", + "@skip-router/core": "^1.2.0", + "@tanstack/react-query": "^5.17.1", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", "@vercel/analytics": "^1.1.1", "@vercel/edge-config": "^0.4.1", - "@web3modal/core": "^3.5.1", - "@web3modal/ui": "^3.5.1", + "@web3modal/core": "^3.5.3", + "@web3modal/ui": "^3.5.3", "autoprefixer": "^10.4.16", - "axios": "^1.6.2", - "clsx": "^2.0.0", + "axios": "^1.6.3", + "clsx": "^2.1.0", "cosmjs-types": "0.8.x", - "date-fns": "^3.0.1", + "date-fns": "^3.0.6", "download": "^8.0.0", - "ethers": "^6.9.1", + "ethers": "^6.9.2", "next": "^14.0.4", "postcss": "^8.4.32", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwindcss": "^3.4.0", "tinykeys": "^2.1.0", - "undici": "^6.2.0", - "viem": "^1.20.3", - "wagmi": "^1.4.12", + "undici": "^6.2.1", + "viem": "^1.21.4", + "wagmi": "^1.4.13", "zod": "^3.22.4", "zustand": "^4.4.7" }, "devDependencies": { "@playwright/test": "^1.40.1", - "@tanstack/eslint-plugin-query": "^5.12.1", - "@testing-library/jest-dom": "^6.1.5", + "@tanstack/eslint-plugin-query": "^5.17.1", + "@testing-library/jest-dom": "^6.1.6", "@testing-library/react": "^14.1.2", - "@testing-library/user-event": "^14.5.1", + "@testing-library/user-event": "^14.5.2", "@types/download": "^8.0.5", "@types/jest": "^29.5.11", "@types/testing-library__jest-dom": "^5.14.9", - "@typescript-eslint/eslint-plugin": "^6.15.0", - "@typescript-eslint/parser": "^6.15.0", + "@typescript-eslint/eslint-plugin": "^6.17.0", + "@typescript-eslint/parser": "^6.17.0", "eslint": "^8.56.0", "eslint-config-next": "^14.0.4", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.0", + "eslint-plugin-prettier": "^5.1.2", "eslint-plugin-simple-import-sort": "^10.0.0", "globby": "^14.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "msw": "1.x", "npm-run-all2": "^6.1.1", - "p-map": "^7.0.0", + "p-map": "^7.0.1", "patch-package": "^8.0.0", "prettier": "^3.1.1", "resize-observer-polyfill": "^1.5.1", From 5bedc2fc3e0ef822c4e3a5643a300113116792ba Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Thu, 4 Jan 2024 04:09:03 +0700 Subject: [PATCH 004/115] refactor: optimize queries and refetches Signed-off-by: Griko Nibras --- src/api/__test__/queries.test.ts | 10 +-- src/api/queries.ts | 18 +--- src/components/ChainSymbol.tsx | 2 +- src/components/RouteDisplay.tsx | 8 +- src/components/SwapWidget/SwapWidget.tsx | 2 +- src/components/SwapWidget/useSwapWidget.ts | 2 +- .../TransactionDialogContent.tsx | 6 +- src/components/TransactionSuccessView.tsx | 2 +- src/components/WalletModal/WalletModal.tsx | 2 +- src/context/assets.tsx | 2 +- src/hooks/useAccount.ts | 2 +- src/hooks/useFinalityTimeEstimate.ts | 2 +- src/lib/react-query.ts | 5 +- src/pages/index.tsx | 18 ++-- src/solve/queries.ts | 86 +++++++++++-------- src/utils/utils.ts | 9 +- 16 files changed, 88 insertions(+), 88 deletions(-) diff --git a/src/api/__test__/queries.test.ts b/src/api/__test__/queries.test.ts index 9fa7eb8a..c433e524 100644 --- a/src/api/__test__/queries.test.ts +++ b/src/api/__test__/queries.test.ts @@ -11,13 +11,13 @@ test("useChains attaches a prettyName", async () => { timeout: 10000, }); - expect(result.current.chains).toBeDefined(); + expect(result.current.data).toBeDefined(); - if (!result.current.chains) { + if (!result.current.data) { throw new Error("chains is undefined"); } - for (const chain of result.current.chains) { + for (const chain of result.current.data) { expect(chain.prettyName).toBeDefined(); } }); @@ -31,7 +31,7 @@ test("useChainByID returns the chain matching the specified chainID", async () = timeout: 10000, }); - expect(result.current.chain?.chainID).toEqual("osmosis-1"); + expect(result.current.data?.chainID).toEqual("osmosis-1"); }); test("useChainByID returns undefined if a matching chain cannot be found", async () => { @@ -43,5 +43,5 @@ test("useChainByID returns undefined if a matching chain cannot be found", async timeout: 10000, }); - expect(result.current.chain).toBeUndefined(); + expect(result.current.data).toBeUndefined(); }); diff --git a/src/api/queries.ts b/src/api/queries.ts index f39a6c2f..89b73654 100644 --- a/src/api/queries.ts +++ b/src/api/queries.ts @@ -16,12 +16,12 @@ export type UseChainsQueryArgs = { export function useChains(args: UseChainsQueryArgs = {}) { const { select = (t) => t as T } = args; - const skipRouter = useSkipClient(); + const skipClient = useSkipClient(); - const query = useQuery({ + return useQuery({ queryKey: ["skip-api-chains"], queryFn: async () => { - const chains = await skipRouter.chains({ + const chains = await skipClient.chains({ includeEVM: true, }); @@ -40,20 +40,10 @@ export function useChains(args: UseChainsQueryArgs = {}) { }, select, }); - - return { - ...query, - chains: query.data, - }; } export function useChainByID(chainID: string) { - const { chains, ...queryResult } = useChains({ + return useChains({ select: (chains) => (chains ?? []).find((c) => c.chainID === chainID), }); - - return { - ...queryResult, - chain: chains, - }; } diff --git a/src/components/ChainSymbol.tsx b/src/components/ChainSymbol.tsx index b93cac5e..d6f01c09 100644 --- a/src/components/ChainSymbol.tsx +++ b/src/components/ChainSymbol.tsx @@ -10,7 +10,7 @@ interface Props { } export const ChainSymbol = ({ chainId }: Props) => { - const { chain } = useChainByID(chainId); + const { data: chain } = useChainByID(chainId); const src = useMemo(() => { if (!chain) return; diff --git a/src/components/RouteDisplay.tsx b/src/components/RouteDisplay.tsx index b7f46ee1..36a336f5 100644 --- a/src/components/RouteDisplay.tsx +++ b/src/components/RouteDisplay.tsx @@ -78,8 +78,8 @@ const RouteEnd: FC<{ const TransferStep: FC<{ action: TransferAction }> = ({ action }) => { console.log("destination chain id", action.destinationChain); - const { chain: sourceChain } = useChainByID(action.sourceChain); - const { chain: destinationChain } = useChainByID(action.destinationChain); + const { data: sourceChain } = useChainByID(action.sourceChain); + const { data: destinationChain } = useChainByID(action.destinationChain); const { getAsset } = useAssets(); @@ -284,8 +284,8 @@ const RouteDisplay: FC = ({ route }) => { route.destAssetChainID, ); - const { chain: sourceChain } = useChainByID(route.sourceAssetChainID); - const { chain: destinationChain } = useChainByID(route.destAssetChainID); + const { data: sourceChain } = useChainByID(route.sourceAssetChainID); + const { data: destinationChain } = useChainByID(route.destAssetChainID); const amountIn = useMemo(() => { try { diff --git a/src/components/SwapWidget/SwapWidget.tsx b/src/components/SwapWidget/SwapWidget.tsx index ba4d4302..60048598 100644 --- a/src/components/SwapWidget/SwapWidget.tsx +++ b/src/components/SwapWidget/SwapWidget.tsx @@ -28,7 +28,7 @@ import { useSwapWidget } from "./useSwapWidget"; export const SwapWidget: FC = () => { const { openWalletModal } = useWalletModal(); - const { chains } = useSkipChains(); + const { data: chains } = useSkipChains(); const { amountIn, diff --git a/src/components/SwapWidget/useSwapWidget.ts b/src/components/SwapWidget/useSwapWidget.ts index d22502bb..b4ef1f1b 100644 --- a/src/components/SwapWidget/useSwapWidget.ts +++ b/src/components/SwapWidget/useSwapWidget.ts @@ -301,7 +301,7 @@ const useFormValuesStore = create( // useFormValues returns a set of form values that are used to populate the swap widget // and handles logic regarding setting initial values based on local storage and other form values. function useFormValues() { - const { chains } = useChains(); + const { data: chains } = useChains(); const { assetsByChainID, getFeeDenom } = useAssets(); diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index 32ebb33e..5fcd4cc7 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -51,9 +51,9 @@ const TransactionDialogContent: FC = ({ }) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { toast } = useToast(); - const { chains = [] } = useChains(); + const { data: chains = [] } = useChains(); - const skipRouter = useSkipClient(); + const skipClient = useSkipClient(); const { address: evmAddress } = useAccount(); const [transacting, setTransacting] = useState(false); @@ -139,7 +139,7 @@ const TransactionDialogContent: FC = ({ ...txStatuses.slice(1), ]); - await skipRouter.executeRoute({ + await skipClient.executeRoute({ route, userAddresses, validateGasBalance: true, diff --git a/src/components/TransactionSuccessView.tsx b/src/components/TransactionSuccessView.tsx index 2b4e7e04..c14b4577 100644 --- a/src/components/TransactionSuccessView.tsx +++ b/src/components/TransactionSuccessView.tsx @@ -13,7 +13,7 @@ const TransactionSuccessView: FC<{ transactions: RouteTransaction[]; }> = ({ route, onClose, transactions }) => { const { getAsset } = useAssets(); - const { chains } = useChains(); + const { data: chains } = useChains(); const sourceAsset = getAsset( route.sourceAssetDenom, diff --git a/src/components/WalletModal/WalletModal.tsx b/src/components/WalletModal/WalletModal.tsx index 909109a3..4581c893 100644 --- a/src/components/WalletModal/WalletModal.tsx +++ b/src/components/WalletModal/WalletModal.tsx @@ -105,7 +105,7 @@ const WalletModalWithContext: FC = () => { const { setIsOpen } = useWalletModal(); - const { chain } = useChainByID(chainID); + const { data: chain } = useChainByID(chainID); if (!chain) { return null; diff --git a/src/context/assets.tsx b/src/context/assets.tsx index cc72ab0d..162a273c 100644 --- a/src/context/assets.tsx +++ b/src/context/assets.tsx @@ -128,7 +128,7 @@ function getAssetSymbol( } export const AssetsProvider: FC = ({ children }) => { - const { chains } = useChains(); + const { data: chains } = useChains(); const { data: solveAssets } = useSolveAssets(); diff --git a/src/hooks/useAccount.ts b/src/hooks/useAccount.ts index fdbefc5b..cff35723 100644 --- a/src/hooks/useAccount.ts +++ b/src/hooks/useAccount.ts @@ -5,7 +5,7 @@ import { useChainByID } from "@/api/queries"; import { EVM_WALLET_LOGOS } from "@/constants/constants"; export function useAccount(chainID: string) { - const { chain } = useChainByID(chainID); + const { data: chain } = useChainByID(chainID); const cosmosChain = useChain(chain?.record?.chain_name ?? "cosmoshub"); diff --git a/src/hooks/useFinalityTimeEstimate.ts b/src/hooks/useFinalityTimeEstimate.ts index 1e2e89e5..bf7f234c 100644 --- a/src/hooks/useFinalityTimeEstimate.ts +++ b/src/hooks/useFinalityTimeEstimate.ts @@ -5,7 +5,7 @@ import { useChains } from "@/api/queries"; import { getFinalityTime } from "@/constants/finality"; export function useFinalityTimeEstimate(route: RouteResponse) { - const { chains = [] } = useChains(); + const { data: chains = [] } = useChains(); return useMemo(() => { for (const operation of route.operations) { diff --git a/src/lib/react-query.ts b/src/lib/react-query.ts index 173ad5f0..7e5c73fd 100644 --- a/src/lib/react-query.ts +++ b/src/lib/react-query.ts @@ -3,10 +3,7 @@ import { QueryClient } from "@tanstack/react-query"; export const queryClient = new QueryClient({ defaultOptions: { queries: { - refetchOnMount: false, - refetchOnReconnect: false, - refetchOnWindowFocus: false, - staleTime: 5000, + staleTime: 1000 * 5, }, }, }); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 01dc5cd5..c20e9627 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -65,7 +65,7 @@ function maybeGetAxelarscanLinkFromTransactionStatus( async function updatePendingRoute( id: string, historyItem: TxHistoryItem, - skipRouter: SkipRouter, + skipClient: SkipRouter, ) { const firstTx = historyItem.txStatus.length > 0 ? historyItem.txStatus[0] : undefined; @@ -75,7 +75,7 @@ async function updatePendingRoute( } try { - const status = await skipRouter.transactionStatus({ + const status = await skipClient.transactionStatus({ chainID: firstTx.chainId, txHash: firstTx.txHash, }); @@ -95,11 +95,11 @@ async function updatePendingRoute( async function updateAxelarscanLink( id: string, historyItem: TxHistoryItem, - skipRouter: SkipRouter, + skipClient: SkipRouter, ) { for (const tx of historyItem.txStatus) { try { - const status = await skipRouter.transactionStatus({ + const status = await skipClient.transactionStatus({ chainID: tx.chainId, txHash: tx.txHash, }); @@ -124,7 +124,7 @@ async function updateAxelarscanLink( export default function Home() { const { walletRepos } = useManager(); const history = useTxHistory(); - const skipRouter = useSkipClient(); + const skipClient = useSkipClient(); const { assetsByChainID } = useAssets(); async function prefetchBalances(address: string, chainID: string) { @@ -155,11 +155,11 @@ export default function Home() { } if (historyItem.status === "pending") { - await updatePendingRoute(id, historyItem, skipRouter); + await updatePendingRoute(id, historyItem, skipClient); } if (historyItemIsMissingAxelarlarscanLink(historyItem)) { - await updateAxelarscanLink(id, historyItem, skipRouter); + await updateAxelarscanLink(id, historyItem, skipClient); } } } @@ -170,7 +170,7 @@ export default function Home() { prefetchBalances(repo.current.address, repo.chainRecord.chain.chain_id); } } - }, 5000); + }, 1000 * 5); // on the first run (aka page load), check all transactions in the history const [firstRun, setFirstRun] = useState(true); @@ -181,7 +181,7 @@ export default function Home() { if (firstRun) { setFirstRun(false); } - }, 2000); + }, 1000 * 2); return (
diff --git a/src/solve/queries.ts b/src/solve/queries.ts index c550c6c9..e05288ec 100644 --- a/src/solve/queries.ts +++ b/src/solve/queries.ts @@ -1,35 +1,26 @@ +import { AssetsRequest } from "@skip-router/core"; import { useQuery } from "@tanstack/react-query"; -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useSkipClient } from "./hooks"; -export function useAssets() { +export function useAssets(options: AssetsRequest = {}) { const skipClient = useSkipClient(); + const queryKey = useMemo(() => ["solve-assets", options] as const, [options]); + return useQuery({ - queryKey: ["solve-assets"], - queryFn: () => { + queryKey, + queryFn: ({ queryKey: [, options] }) => { return skipClient.assets({ includeEvmAssets: true, includeCW20Assets: true, + ...options, }); }, }); } -export function useSolveChains() { - const skipClient = useSkipClient(); - return useQuery({ - queryKey: ["solve-chains"], - queryFn: () => { - return skipClient.chains({ - includeEVM: true, - }); - }, - placeholderData: [], - }); -} - interface UseRouteArgs { direction: "swap-in" | "swap-out"; amount: string; @@ -53,17 +44,40 @@ export function useRoute({ const [refetchCount, setRefetchCount] = useState(0); - return useQuery({ - queryKey: [ - "solve-route", - direction, + const queryKey = useMemo( + () => + [ + "solve-route", + direction, + amount, + sourceAsset, + destinationAsset, + sourceAssetChainID, + destinationAssetChainID, + ] as const, + [ amount, - sourceAsset, destinationAsset, - sourceAssetChainID, destinationAssetChainID, + direction, + sourceAsset, + sourceAssetChainID, ], - queryFn: async () => { + ); + + const query = useQuery({ + queryKey, + queryFn: async ({ + queryKey: [ + , + direction, + amount, + sourceAsset, + destinationAsset, + sourceAssetChainID, + destinationAssetChainID, + ], + }) => { if ( !sourceAsset || !sourceAssetChainID || @@ -97,17 +111,7 @@ export function useRoute({ return route; }, - refetchInterval: (query) => { - if (refetchCount < 10 && query.isActive()) { - setRefetchCount((c) => c + 1); - return 1000 * 2; - } - return false; - }, - refetchOnMount: true, - refetchOnWindowFocus: true, - refetchOnReconnect: true, - retry: false, + refetchInterval: refetchCount < 10 ? 1000 * 5 : false, enabled: enabled && !!sourceAsset && @@ -116,4 +120,16 @@ export function useRoute({ !!destinationAssetChainID && amount !== "0", }); + + useEffect(() => { + if (query.isRefetching) { + setRefetchCount((count) => count + 1); + } + }, [query.isRefetching]); + + useEffect(() => { + setRefetchCount(0); + }, [queryKey]); + + return query; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index cad12ddf..f7eb29c9 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -353,7 +353,7 @@ export function useBalancesByChain( chainId: chain?.chainType === "evm" ? parseInt(chain.chainID) : undefined, }); - const skipRouter = useSkipClient(); + const skipClient = useSkipClient(); return useQuery({ queryKey: ["balances-by-chain", address, chain, assets], @@ -364,7 +364,7 @@ export function useBalancesByChain( if (chain.chainType === "evm") { return getEvmChainBalances( - skipRouter, + skipClient, publicClient, address, chain.chainID, @@ -373,10 +373,7 @@ export function useBalancesByChain( return getBalancesByChain(address, chain.chainID, assets ?? []); }, - refetchInterval: 1000, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, + refetchInterval: 1000 * 5, enabled: !!chain && !!address && enabled, }); } From ab64be7368a4c6181b49b9b1dac589b3f12c373b Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Thu, 4 Jan 2024 04:09:19 +0700 Subject: [PATCH 005/115] feat: update injected evm wallet logos Signed-off-by: Griko Nibras --- src/constants/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/constants.ts b/src/constants/constants.ts index d78feefd..7e74f905 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -26,6 +26,7 @@ export const EVM_WALLET_LOGOS: Record = { export const INJECTED_EVM_WALLET_LOGOS: Record = { "OKX Wallet": `https://raw.githubusercontent.com/rainbow-me/rainbowkit/6b460fcba954e155828e03f46228ee88a171a83b/packages/rainbowkit/src/wallets/walletConnectors/okxWallet/okxWallet.svg`, + Rainbow: `https://raw.githubusercontent.com/rainbow-me/rainbow/develop/src/assets/rainbow-icon-circle.png`, }; export const EVM_CHAINS: Chain[] = [ From 4ec4c38504ff0f086010122427c64431f71385e7 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Thu, 4 Jan 2024 04:09:30 +0700 Subject: [PATCH 006/115] fix: update MergedWalletClient types Signed-off-by: Griko Nibras --- src/lib/cosmos-kit.ts | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/lib/cosmos-kit.ts b/src/lib/cosmos-kit.ts index f418e927..cc6d31ab 100644 --- a/src/lib/cosmos-kit.ts +++ b/src/lib/cosmos-kit.ts @@ -1,28 +1,31 @@ -import { WalletClient } from "@cosmos-kit/core"; -import { wallets as cosmostationWallets } from "@cosmos-kit/cosmostation"; -import { wallets as keplrWallets } from "@cosmos-kit/keplr"; -import { wallets as leapWallets } from "@cosmos-kit/leap"; -import { wallets as okxWallets } from "@cosmos-kit/okxwallet"; -import { wallets as stationWallets } from "@cosmos-kit/station"; -import { wallets as vectisWallets } from "@cosmos-kit/vectis"; -import { wallets as xdefiWallets } from "@cosmos-kit/xdefi"; +import { wallets as cosmostation } from "@cosmos-kit/cosmostation"; +import { wallets as keplr } from "@cosmos-kit/keplr"; +import { wallets as leap } from "@cosmos-kit/leap"; +import { wallets as okx } from "@cosmos-kit/okxwallet"; +import { wallets as station } from "@cosmos-kit/station"; +import { wallets as vectis } from "@cosmos-kit/vectis"; +import { wallets as xdefi } from "@cosmos-kit/xdefi"; export const wallets = [ - ...keplrWallets, - ...leapWallets, - ...cosmostationWallets, - ...okxWallets, - ...stationWallets, - ...vectisWallets, - ...xdefiWallets, + ...keplr, + ...leap, + ...cosmostation, + ...okx, + ...station, + ...vectis, + ...xdefi, ]; export type MergedWalletClient = - | WalletClient | import("@cosmos-kit/cosmostation-extension/cjs/extension/client").CosmostationClient + | import("@cosmos-kit/cosmostation-mobile/cjs/wallet-connect/client").CosmostationClient | import("@cosmos-kit/keplr-extension/cjs/extension/client").KeplrClient + | import("@cosmos-kit/keplr-mobile/cjs/wallet-connect/client").KeplrClient | import("@cosmos-kit/leap-extension/cjs/extension/client").LeapClient + | import("@cosmos-kit/leap-metamask-cosmos-snap/cjs/extension/client").CosmosSnapClient + | import("@cosmos-kit/leap-mobile/cjs/wallet-connect/client").LeapClient | import("@cosmos-kit/okxwallet-extension/cjs/extension/client").OkxwalletClient | import("@cosmos-kit/station-extension/cjs/extension/client").StationClient + | import("@cosmos-kit/station-extension/cjs/extension/client").StationClient | import("@cosmos-kit/vectis-extension/cjs/extension/client").VectisClient | import("@cosmos-kit/xdefi-extension/cjs/extension/client").XDEFIClient; From aacb18b9ee2e8b7510dce26b540bfa783f26f4b0 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Thu, 4 Jan 2024 04:10:14 +0700 Subject: [PATCH 007/115] chore: remove extended okx wallet wagmi connector Signed-off-by: Griko Nibras --- src/lib/wagmi.ts | 5 ++--- src/lib/wagmi/connectors.ts | 9 --------- 2 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 src/lib/wagmi/connectors.ts diff --git a/src/lib/wagmi.ts b/src/lib/wagmi.ts index 1833606c..779aaf8f 100644 --- a/src/lib/wagmi.ts +++ b/src/lib/wagmi.ts @@ -1,11 +1,10 @@ import { configureChains, createConfig } from "wagmi"; +import { InjectedConnector } from "wagmi/connectors/injected"; import { MetaMaskConnector } from "wagmi/connectors/metaMask"; import { publicProvider } from "wagmi/providers/public"; import { EVM_CHAINS } from "@/constants/constants"; -import { OkxWalletConnector } from "./wagmi/connectors"; - const { publicClient, chains } = configureChains(EVM_CHAINS, [ publicProvider(), ]); @@ -14,7 +13,7 @@ export const wagmiConfig = createConfig({ autoConnect: true, connectors: [ new MetaMaskConnector({ chains }), - new OkxWalletConnector({ chains }), + new InjectedConnector({ chains }), ], publicClient, }); diff --git a/src/lib/wagmi/connectors.ts b/src/lib/wagmi/connectors.ts deleted file mode 100644 index d2ac8aa2..00000000 --- a/src/lib/wagmi/connectors.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { InjectedConnector } from "@wagmi/core"; - -export class OkxWalletConnector extends InjectedConnector { - readonly name = "OKX Wallet"; - async getProvider() { - if (typeof window === "undefined") return; - return window.okexchain?.ethereum ?? window.ethereum; - } -} From 103098446c6762fe0394bb361046270aa16ee55d Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Thu, 4 Jan 2024 16:33:17 +0700 Subject: [PATCH 008/115] fix: restore mergedwalletclient type --- src/lib/cosmos-kit.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/cosmos-kit.ts b/src/lib/cosmos-kit.ts index cc6d31ab..f058086b 100644 --- a/src/lib/cosmos-kit.ts +++ b/src/lib/cosmos-kit.ts @@ -1,3 +1,4 @@ +import { WalletClient } from "@cosmos-kit/core"; import { wallets as cosmostation } from "@cosmos-kit/cosmostation"; import { wallets as keplr } from "@cosmos-kit/keplr"; import { wallets as leap } from "@cosmos-kit/leap"; @@ -17,6 +18,7 @@ export const wallets = [ ]; export type MergedWalletClient = + | WalletClient | import("@cosmos-kit/cosmostation-extension/cjs/extension/client").CosmostationClient | import("@cosmos-kit/cosmostation-mobile/cjs/wallet-connect/client").CosmostationClient | import("@cosmos-kit/keplr-extension/cjs/extension/client").KeplrClient From 8a61f9d9833a1345584a67fd635d215d54230072 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Fri, 5 Jan 2024 05:17:26 +0700 Subject: [PATCH 009/115] =?UTF-8?q?refactor:=20constants/constants=20?= =?UTF-8?q?=E2=86=92=20constants/wagmi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/WalletModal/WalletModal.tsx | 5 +---- src/constants/{constants.ts => wagmi.ts} | 0 src/hooks/useAccount.ts | 2 +- src/lib/wagmi.ts | 2 +- src/utils/utils.ts | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) rename src/constants/{constants.ts => wagmi.ts} (100%) diff --git a/src/components/WalletModal/WalletModal.tsx b/src/components/WalletModal/WalletModal.tsx index 4581c893..2a0ccc25 100644 --- a/src/components/WalletModal/WalletModal.tsx +++ b/src/components/WalletModal/WalletModal.tsx @@ -4,10 +4,7 @@ import { FC } from "react"; import { useAccount, useConnect, useDisconnect } from "wagmi"; import { useChainByID } from "@/api/queries"; -import { - EVM_WALLET_LOGOS, - INJECTED_EVM_WALLET_LOGOS, -} from "@/constants/constants"; +import { EVM_WALLET_LOGOS, INJECTED_EVM_WALLET_LOGOS } from "@/constants/wagmi"; import { DialogContent } from "@/elements/Dialog"; import { getChainByID } from "@/utils/utils"; diff --git a/src/constants/constants.ts b/src/constants/wagmi.ts similarity index 100% rename from src/constants/constants.ts rename to src/constants/wagmi.ts diff --git a/src/hooks/useAccount.ts b/src/hooks/useAccount.ts index cff35723..f0aeb661 100644 --- a/src/hooks/useAccount.ts +++ b/src/hooks/useAccount.ts @@ -2,7 +2,7 @@ import { useChain } from "@cosmos-kit/react"; import { useAccount as useWagmiAccount } from "wagmi"; import { useChainByID } from "@/api/queries"; -import { EVM_WALLET_LOGOS } from "@/constants/constants"; +import { EVM_WALLET_LOGOS } from "@/constants/wagmi"; export function useAccount(chainID: string) { const { data: chain } = useChainByID(chainID); diff --git a/src/lib/wagmi.ts b/src/lib/wagmi.ts index 779aaf8f..e5ef0b19 100644 --- a/src/lib/wagmi.ts +++ b/src/lib/wagmi.ts @@ -3,7 +3,7 @@ import { InjectedConnector } from "wagmi/connectors/injected"; import { MetaMaskConnector } from "wagmi/connectors/metaMask"; import { publicProvider } from "wagmi/providers/public"; -import { EVM_CHAINS } from "@/constants/constants"; +import { EVM_CHAINS } from "@/constants/wagmi"; const { publicClient, chains } = configureChains(EVM_CHAINS, [ publicProvider(), diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f7eb29c9..c9a0f603 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -17,7 +17,7 @@ import { erc20ABI, PublicClient, usePublicClient } from "wagmi"; import { Chain } from "@/api/queries"; import { ChainId, getChain } from "@/chains"; import { multicall3ABI } from "@/constants/abis"; -import { EVM_CHAINS } from "@/constants/constants"; +import { EVM_CHAINS } from "@/constants/wagmi"; import { AssetWithMetadata } from "@/context/assets"; import { MergedWalletClient } from "@/lib/cosmos-kit"; import { useSkipClient } from "@/solve"; From e3316790205d4a4c4b2061b75abe26b67fd2bd25 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Fri, 5 Jan 2024 20:46:46 +0700 Subject: [PATCH 010/115] feat: update deps and registry Signed-off-by: Griko Nibras --- chain-registry | 2 +- package-lock.json | 248 ++++++++++++++++++++++++---------------------- package.json | 20 ++-- 3 files changed, 138 insertions(+), 132 deletions(-) diff --git a/chain-registry b/chain-registry index 618b5d7b..03e30a3c 160000 --- a/chain-registry +++ b/chain-registry @@ -1 +1 @@ -Subproject commit 618b5d7b25dd33c5f38d292b180916d2d0418f16 +Subproject commit 03e30a3c84dc8ef39b19817b2759d54b1f14fff8 diff --git a/package-lock.json b/package-lock.json index 9ce4120d..328d1119 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@heroicons/react": "^2.1.1", "@injectivelabs/sdk-ts": "^1.14.4", "@injectivelabs/utils": "^1.14.4", - "@keplr-wallet/types": "^0.12.57", + "@keplr-wallet/types": "^0.12.58", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", @@ -44,7 +44,7 @@ "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@react-stately/table": "^3.11.4", - "@sentry/nextjs": "^7.91.0", + "@sentry/nextjs": "^7.92.0", "@skip-router/core": "^1.2.0", "@tanstack/react-query": "^5.17.1", "@types/node": "^20.10.6", @@ -52,31 +52,31 @@ "@types/react-dom": "^18.2.18", "@vercel/analytics": "^1.1.1", "@vercel/edge-config": "^0.4.1", - "@web3modal/core": "^3.5.3", - "@web3modal/ui": "^3.5.3", + "@web3modal/core": "^3.5.4", + "@web3modal/ui": "^3.5.4", "autoprefixer": "^10.4.16", - "axios": "^1.6.3", + "axios": "^1.6.4", "clsx": "^2.1.0", "cosmjs-types": "0.8.x", - "date-fns": "^3.0.6", + "date-fns": "^3.1.0", "download": "^8.0.0", "ethers": "^6.9.2", "next": "^14.0.4", - "postcss": "^8.4.32", + "postcss": "^8.4.33", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwindcss": "^3.4.0", "tinykeys": "^2.1.0", "undici": "^6.2.1", - "viem": "^1.21.4", - "wagmi": "^1.4.13", + "viem": "1.x", + "wagmi": "1.x", "zod": "^3.22.4", "zustand": "^4.4.7" }, "devDependencies": { "@playwright/test": "^1.40.1", "@tanstack/eslint-plugin-query": "^5.17.1", - "@testing-library/jest-dom": "^6.1.6", + "@testing-library/jest-dom": "^6.2.0", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "@types/download": "^8.0.5", @@ -4348,9 +4348,9 @@ "integrity": "sha512-T2CiKS2B5n0ZA7CWw0CA6qIAH0XYI1siE50MP+i+V0ZniCGBeL+BMcDw64vFJUcEH+1L5X4sDAzV37fQxGwllA==" }, "node_modules/@keplr-wallet/types": { - "version": "0.12.57", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.57.tgz", - "integrity": "sha512-YXSrPD1ys6CEZ3flxHidVrUK0LyiMEb8QVVE+OiCnAwus+l7bUzuNjzCnht3I2KevUyYoco+SWtvsbVJVuA+RA==", + "version": "0.12.58", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.58.tgz", + "integrity": "sha512-cUBpdtfn5w2m9M3Lsn69TvvA4rG9NHiTkBuFlZUoUrXTBLYimRC3M/gDwEPhFHW+Jr8HM6G7Zwphjwh1j5Fspg==", "dependencies": { "long": "^4.0.0" } @@ -7659,42 +7659,42 @@ } }, "node_modules/@sentry-internal/feedback": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.91.0.tgz", - "integrity": "sha512-SJKTSaz68F5YIwF79EttBm915M2LnacgZMYRnRumyTmMKnebGhYQLwWbZdpaDvOa1U18dgRajDX8Qed/8A3tXw==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.92.0.tgz", + "integrity": "sha512-/jEALRtVqboxB9kcK2tag8QCO6XANTlGBb9RV3oeGXJe0DDNJXRq6wVZbfgztXJRrfgx4XVDcNt1pRVoGGG++g==", "dependencies": { - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.91.0.tgz", - "integrity": "sha512-JH5y6gs6BS0its7WF2DhySu7nkhPDfZcdpAXldxzIlJpqFkuwQKLU5nkYJpiIyZz1NHYYtW5aum2bV2oCOdDRA==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.92.0.tgz", + "integrity": "sha512-ur55vPcUUUWFUX4eVLNP71ohswK7ZZpleNZw9Y1GfLqyI+0ILQUwjtzqItJrdClvVsdRZJMRmDV40Hp9Lbb9mA==", "dependencies": { - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/browser": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.91.0.tgz", - "integrity": "sha512-lJv3x/xekzC/biiyAsVCioq2XnKNOZhI6jY3ZzLJZClYV8eKRi7D3KCsHRvMiCdGak1d/6sVp8F4NYY+YiWy1Q==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.92.0.tgz", + "integrity": "sha512-loMr02/zQ38u8aQhYLtIBg0i5n3ps2e3GUXrt3CdsJQdkRYfa62gcrE7SzvoEpMVHTk7VOI4fWGht8cWw/1k3A==", "dependencies": { - "@sentry-internal/feedback": "7.91.0", - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/replay": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry-internal/feedback": "7.92.0", + "@sentry-internal/tracing": "7.92.0", + "@sentry/core": "7.92.0", + "@sentry/replay": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=8" @@ -7721,25 +7721,25 @@ } }, "node_modules/@sentry/core": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.91.0.tgz", - "integrity": "sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.92.0.tgz", + "integrity": "sha512-1Tly7YB2I1byI5xb0Cwrxs56Rhww+6mQ7m9P7rTmdC3/ijOzbEoohtYIUPwcooCEarpbEJe/tAayRx6BrH2UbQ==", "dependencies": { - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/integrations": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.91.0.tgz", - "integrity": "sha512-LGRfb+WfG3FaWHtDnJIhtupweat0imCQr2z/5SSbQKzqxHhtlaEU+9IExBmBdzq90n4lRBaVQHA3zGuU02uOhg==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.92.0.tgz", + "integrity": "sha512-9OT4i9b0Ge5sP3rCV8yYqoPp3BKcB9wjddW7sG0w88va32F0UWMKk4gmprtwgPYM0+u5AS/TTAVichRVRj+I1Q==", "dependencies": { - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0", + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0", "localforage": "^1.8.1" }, "engines": { @@ -7747,18 +7747,18 @@ } }, "node_modules/@sentry/nextjs": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.91.0.tgz", - "integrity": "sha512-wE83+OTEH4yYnDrhMw9eVEARSfZc6xY5qJb9xyYm5rW3+gVjNQZQaUY+wkM61Xdo0T35BN+7U4T88HbwzGeMqA==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.92.0.tgz", + "integrity": "sha512-gHLB06EwLWIxI4VdNADw3RaSokc+YKKLrG/RH8PrfWczCa2v5uQgzxWwKNYuViSGH/MBxZhpKGIZimjIkpOlBw==", "dependencies": { "@rollup/plugin-commonjs": "24.0.0", - "@sentry/core": "7.91.0", - "@sentry/integrations": "7.91.0", - "@sentry/node": "7.91.0", - "@sentry/react": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0", - "@sentry/vercel-edge": "7.91.0", + "@sentry/core": "7.92.0", + "@sentry/integrations": "7.92.0", + "@sentry/node": "7.92.0", + "@sentry/react": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0", + "@sentry/vercel-edge": "7.92.0", "@sentry/webpack-plugin": "1.21.0", "chalk": "3.0.0", "resolve": "1.22.8", @@ -7780,14 +7780,14 @@ } }, "node_modules/@sentry/node": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.91.0.tgz", - "integrity": "sha512-hTIfSQxD7L+AKIqyjoq8CWBRkEQrrMZmA3GSZgPI5JFWBHgO0HBo5TH/8TU81oEJh6kqqHAl2ObMhmcnaFqlzg==", - "dependencies": { - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.92.0.tgz", + "integrity": "sha512-LZeQL1r6kikEoOzA9K61OmMl32/lK/6PzmFNDH6z7UYwQopCZgVA6IP+CZuln8K2ys5c9hCyF7ICQMysXfpNJA==", + "dependencies": { + "@sentry-internal/tracing": "7.92.0", + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0", "https-proxy-agent": "^5.0.0" }, "engines": { @@ -7795,13 +7795,13 @@ } }, "node_modules/@sentry/react": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.91.0.tgz", - "integrity": "sha512-7JH2rWaX3WKHHvBcZQ4f/KnkYIXTf7hMojRFncUwPocdtDlhJw/JUvjAYNpEysixXIgsMes3B32lmtZjGjRhwQ==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.92.0.tgz", + "integrity": "sha512-lTvrLuvxtGEZbkW6NHru03K6eyixKyBliwiLwO+k37FK7Ha8Bwat2m77weyizWCdQ6DKlVazJNppkNeAlACIvQ==", "dependencies": { - "@sentry/browser": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0", + "@sentry/browser": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -7812,47 +7812,47 @@ } }, "node_modules/@sentry/replay": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.91.0.tgz", - "integrity": "sha512-XwbesnLLNtaVXKtDoyBB96GxJuhGi9zy3a662Ba/McmumCnkXrMQYpQPh08U7MgkTyDRgjDwm7PXDhiKpcb03g==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.92.0.tgz", + "integrity": "sha512-G1t9Uvc9cR8VpNkElwvHIMGzykjIKikb10n0tfVd3e+rBPMCCjCPWOduwG6jZYxcvCjTpqmJh6NSLXxL/Mt4JA==", "dependencies": { - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry-internal/tracing": "7.92.0", + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/types": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.91.0.tgz", - "integrity": "sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.92.0.tgz", + "integrity": "sha512-APmSOuZuoRGpbPpPeYIbMSplPjiWNLZRQa73QiXuTflW4Tu/ItDlU8hOa2+A6JKVkJCuD2EN6yUrxDGSMyNXeg==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.91.0.tgz", - "integrity": "sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.92.0.tgz", + "integrity": "sha512-3nEfrQ1z28b/2zgFGANPh5yMVtgwXmrasZxTvKbrAj+KWJpjrJHrIR84r9W277J44NMeZ5RhRW2uoDmuBslPnA==", "dependencies": { - "@sentry/types": "7.91.0" + "@sentry/types": "7.92.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/vercel-edge": { - "version": "7.91.0", - "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-7.91.0.tgz", - "integrity": "sha512-CounqhXPwFh67zf6L/q4ACBHHqknT6YY9LdgIAnUd0GGgHzrJPyKcthvh8Je4lNdpo5LFg2gnR+6g6JS8DDYDQ==", + "version": "7.92.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-7.92.0.tgz", + "integrity": "sha512-iUmYjFj5ze/k9B06HmLZ/2JGhaf/tmjd0foz3pSbMyKbql6TTnybIwG+gcg+ukcek5mB82288Fh+mWXoUUkMIg==", "dependencies": { - "@sentry-internal/tracing": "7.91.0", - "@sentry/core": "7.91.0", - "@sentry/types": "7.91.0", - "@sentry/utils": "7.91.0" + "@sentry-internal/tracing": "7.92.0", + "@sentry/core": "7.92.0", + "@sentry/types": "7.92.0", + "@sentry/utils": "7.92.0" }, "engines": { "node": ">=8" @@ -8475,9 +8475,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.6.tgz", - "integrity": "sha512-YwuiOdYEcxhfC2u5iNKlvg2Q5MgbutovP6drq7J1HrCbvR+G58BbtoCoq+L/kNlrNFsu2Kt3jaFAviLVxYHJZg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", + "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -8485,7 +8485,7 @@ "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", + "dom-accessibility-api": "^0.6.3", "lodash": "^4.17.15", "redent": "^3.0.0" }, @@ -8515,6 +8515,12 @@ } } }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + }, "node_modules/@testing-library/react": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", @@ -10121,36 +10127,36 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@web3modal/common": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@web3modal/common/-/common-3.5.3.tgz", - "integrity": "sha512-HQKaDHUU5k4gz7BBiCJRmgDnPCIIyAPwz21Z3UcrL3evDv24rlGOD8k8A3E40VxBKdIj5/TjgEUwlX2r+VKFvQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@web3modal/common/-/common-3.5.4.tgz", + "integrity": "sha512-9qBg8anJjAnGxFicgZUl/Gdj9lunqtGm8fKx8yGYEpwW1R/Hqq9WqjCgwEH/+exvA5Xv2avO803P40mYI/ADRw==", "dependencies": { "dayjs": "1.11.10" } }, "node_modules/@web3modal/core": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@web3modal/core/-/core-3.5.3.tgz", - "integrity": "sha512-wVJR/vnk6Xx1snnZE6rMCTrxxk8vUW4uH478yWa7BTWSGXsbz05tU7FAUXUSvngzbWTZF/GydSZ+JcuJCqwFkA==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@web3modal/core/-/core-3.5.4.tgz", + "integrity": "sha512-d2V3Ooj5OaqHtT0ie75ANNuvoZeT+qw0D9dMmaGL+KTbAC8m2Z/AkoyvHdaM656rsjIdxWLo7g170TLmnNS3FQ==", "dependencies": { - "@web3modal/common": "3.5.3", - "@web3modal/wallet": "3.5.3", + "@web3modal/common": "3.5.4", + "@web3modal/wallet": "3.5.4", "valtio": "1.11.2" } }, "node_modules/@web3modal/ui": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@web3modal/ui/-/ui-3.5.3.tgz", - "integrity": "sha512-GHi4jJVDa2eTXcoyEUYMEUjMj00MU8oXKAu0YiAppbEzW/CPvr7r/3HR0tDYh2y8sh1650uSxMO7iC3EEoGKtA==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@web3modal/ui/-/ui-3.5.4.tgz", + "integrity": "sha512-Z9HWqsb6eakryd8XbcwYshIP2Pd2/0pICSCO+aCrS2/2tSfSfKe885RHjdEfjXqsLpg3j/53lZGJT0O7IRj2Bg==", "dependencies": { "lit": "3.1.0", "qrcode": "1.5.3" } }, "node_modules/@web3modal/wallet": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@web3modal/wallet/-/wallet-3.5.3.tgz", - "integrity": "sha512-tzQlrBYbKIYrFw0JRH7iarZFUgCueWwMyDMmN7Pjy80dNQ8vcNjlLQM97yymVKun0QNBfZZrHLrDP4yd66dW3g==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@web3modal/wallet/-/wallet-3.5.4.tgz", + "integrity": "sha512-G4RsIW581n/YUzJr9dJ6NZ8MD2kydm+IWRK9f1TcdAYGD3RMUt4LskwwJJrKpSR15XzfTTc42O1nxO7AdmW8lg==", "dependencies": { "zod": "3.22.4" } @@ -10734,11 +10740,11 @@ } }, "node_modules/axios": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", - "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz", + "integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -12088,9 +12094,9 @@ } }, "node_modules/date-fns": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.6.tgz", - "integrity": "sha512-W+G99rycpKMMF2/YD064b2lE7jJGUe+EjOES7Q8BIGY8sbNdbgcs9XFTZwvzc9Jx1f3k7LB7gZaZa7f8Agzljg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.1.0.tgz", + "integrity": "sha512-ZO7yefXV/wCWzd3I9haCHmfzlfA3i1a2HHO7ZXjtJrRjXt8FULKJ2Vl8wji3XYF4dQ0ZJ/tokXDZeYlFvgms9Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -14356,9 +14362,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -19641,9 +19647,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "funding": [ { "type": "opencollective", diff --git a/package.json b/package.json index 7a6f5a67..6f7aa944 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@heroicons/react": "^2.1.1", "@injectivelabs/sdk-ts": "^1.14.4", "@injectivelabs/utils": "^1.14.4", - "@keplr-wallet/types": "^0.12.57", + "@keplr-wallet/types": "^0.12.58", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", @@ -60,7 +60,7 @@ "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@react-stately/table": "^3.11.4", - "@sentry/nextjs": "^7.91.0", + "@sentry/nextjs": "^7.92.0", "@skip-router/core": "^1.2.0", "@tanstack/react-query": "^5.17.1", "@types/node": "^20.10.6", @@ -68,31 +68,31 @@ "@types/react-dom": "^18.2.18", "@vercel/analytics": "^1.1.1", "@vercel/edge-config": "^0.4.1", - "@web3modal/core": "^3.5.3", - "@web3modal/ui": "^3.5.3", + "@web3modal/core": "^3.5.4", + "@web3modal/ui": "^3.5.4", "autoprefixer": "^10.4.16", - "axios": "^1.6.3", + "axios": "^1.6.4", "clsx": "^2.1.0", "cosmjs-types": "0.8.x", - "date-fns": "^3.0.6", + "date-fns": "^3.1.0", "download": "^8.0.0", "ethers": "^6.9.2", "next": "^14.0.4", - "postcss": "^8.4.32", + "postcss": "^8.4.33", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwindcss": "^3.4.0", "tinykeys": "^2.1.0", "undici": "^6.2.1", - "viem": "^1.21.4", - "wagmi": "^1.4.13", + "viem": "1.x", + "wagmi": "1.x", "zod": "^3.22.4", "zustand": "^4.4.7" }, "devDependencies": { "@playwright/test": "^1.40.1", "@tanstack/eslint-plugin-query": "^5.17.1", - "@testing-library/jest-dom": "^6.1.6", + "@testing-library/jest-dom": "^6.2.0", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "@types/download": "^8.0.5", From afe4599634822d22505dd322a1564a510f46dbe1 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Fri, 5 Jan 2024 22:12:47 +0700 Subject: [PATCH 011/115] feat: add gas field in settings store Signed-off-by: Griko Nibras --- src/context/settings.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/context/settings.ts b/src/context/settings.ts index 8550f259..67f3d301 100644 --- a/src/context/settings.ts +++ b/src/context/settings.ts @@ -2,11 +2,13 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; interface SettingsStore { + gas: string; slippage: string; } export const defaultValues: SettingsStore = { - slippage: "3", + gas: (150_000).toString(), + slippage: (3).toString(), }; export const useSettingsStore = create()( From a53f32a171b610cb90ac1372f730fc24b67c81aa Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Fri, 5 Jan 2024 22:13:20 +0700 Subject: [PATCH 012/115] refactor: rework max button calculation Signed-off-by: Griko Nibras --- src/components/AssetInput.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index 96cc628d..8bd52986 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -10,7 +10,7 @@ import { disclosure } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; import Toast from "@/elements/Toast"; import { useAccount } from "@/hooks/useAccount"; -import { formatUSD, getFee, useBalancesByChain } from "@/utils/utils"; +import { formatUSD, useBalancesByChain } from "@/utils/utils"; import AssetSelect from "./AssetSelect"; import ChainSelect from "./ChainSelect"; @@ -226,23 +226,25 @@ const AssetInput: FC = ({ onClick={() => { if (!selectedAssetBalance || !chain || !asset) return; - const feeDenom = getFeeDenom(chain.chainID); - let amount = selectedAssetBalance; + let amount = new BigNumber(selectedAssetBalance); + + const feeDenom = getFeeDenom(chain.chainID)!; + const { gasPrice } = chain.feeAssets.find( + (a) => a.denom === feeDenom.denom, + )!; // if selected asset is the fee denom, subtract the fee - if (feeDenom && feeDenom.denom === asset.denom) { - const fee = getFee(chain.chainID); + if (feeDenom.denom === asset.denom) { + const { gas } = useSettingsStore.getState(); - const feeInt = parseFloat( - ethers.formatUnits(fee.toString(), asset.decimals), - ).toFixed(asset.decimals); + const fee = new BigNumber(gasPrice.average) + .multipliedBy(gas) + .shiftedBy(-(feeDenom.decimals ?? 6)); // denom decimals - amount = ( - parseFloat(selectedAssetBalance) - parseFloat(feeInt) - ).toFixed(asset.decimals); + amount = amount.minus(fee); } - onAmountChange?.(amount); + onAmountChange?.(amount.toString()); }} > Max From 898f20da9f0d43e64e0c7dcedf41fad14ad60b35 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Fri, 5 Jan 2024 22:13:28 +0700 Subject: [PATCH 013/115] feat: add adaptive link component Signed-off-by: Griko Nibras --- src/components/AdaptiveLink.tsx | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/components/AdaptiveLink.tsx diff --git a/src/components/AdaptiveLink.tsx b/src/components/AdaptiveLink.tsx new file mode 100644 index 00000000..5e6dfd4a --- /dev/null +++ b/src/components/AdaptiveLink.tsx @@ -0,0 +1,39 @@ +import Link, { LinkProps } from "next/link"; +import { ComponentPropsWithoutRef, useMemo } from "react"; + +type Props = LinkProps & + Omit, "href"> & { isExternal?: boolean }; + +export function AdaptiveLink({ + href, + isExternal, + rel = "", + target, + ...props +}: Props) { + const isActuallyExternal = useMemo(() => { + if (typeof isExternal === "boolean") { + return isExternal; + } + if (typeof href === "string") { + return href.startsWith("http"); + } + if (typeof href === "object") { + return href.href?.startsWith("http"); + } + }, [href, isExternal]); + + const externalProps = useMemo(() => { + if (!isActuallyExternal) return {}; + return { + rel: mergeRelAttributes("noopener", "noreferrer", rel), + target: target || "_blank", + }; + }, [isActuallyExternal, rel, target]); + + return ; +} + +function mergeRelAttributes(...args: string[]) { + return [...new Set(args.join(" ").split(" "))].filter(Boolean).join(" "); +} From 1771bcace01f09a6e70fd0646f5b90844eef0d40 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Fri, 5 Jan 2024 22:13:44 +0700 Subject: [PATCH 014/115] feat: update settings dialog Signed-off-by: Griko Nibras --- src/components/SettingsDialog/GasSetting.tsx | 31 +++++++++++++++++++ .../SettingsDialog/SlippageSetting.tsx | 4 +-- src/components/SettingsDialog/index.tsx | 18 +++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/components/SettingsDialog/GasSetting.tsx diff --git a/src/components/SettingsDialog/GasSetting.tsx b/src/components/SettingsDialog/GasSetting.tsx new file mode 100644 index 00000000..2a8b6e65 --- /dev/null +++ b/src/components/SettingsDialog/GasSetting.tsx @@ -0,0 +1,31 @@ +import { clsx } from "clsx"; + +import { useSettingsStore } from "@/context/settings"; + +export const GasSetting = () => { + const currentValue = useSettingsStore((state) => state.gas); + + return ( +
+

Gas

+
+
+
+ { + const value = Math.max(0, +event.target.value); + useSettingsStore.setState({ gas: value.toString() }); + }} + /> +
+
+
+ ); +}; diff --git a/src/components/SettingsDialog/SlippageSetting.tsx b/src/components/SettingsDialog/SlippageSetting.tsx index 56cb9234..3672b0ed 100644 --- a/src/components/SettingsDialog/SlippageSetting.tsx +++ b/src/components/SettingsDialog/SlippageSetting.tsx @@ -11,7 +11,7 @@ export const SlippageSetting = () => {

Slippage

-
+
{ %
-
+
{OPTION_VALUES.map((value, i) => (
From a2f10c752cb1598da63bf8209e7d51299e71ab17 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 00:48:03 +0700 Subject: [PATCH 015/115] refactor: simplify chain record --- src/api/queries.ts | 11 ++--- src/ast/_types.ts | 4 +- src/ast/parse-chain-paths.ts | 10 +++-- src/ast/write-entrypoints.ts | 52 ++++++++++++++++++---- src/chains/index.ts | 29 +++++------- src/components/AssetValue.tsx | 4 +- src/components/ChainSymbol.tsx | 4 +- src/components/WalletModal/WalletModal.tsx | 6 +-- src/hooks/useAccount.ts | 2 +- src/utils/utils.ts | 6 +-- 10 files changed, 76 insertions(+), 52 deletions(-) diff --git a/src/api/queries.ts b/src/api/queries.ts index 89b73654..5a61108e 100644 --- a/src/api/queries.ts +++ b/src/api/queries.ts @@ -1,13 +1,10 @@ import { Chain as SkipChain } from "@skip-router/core"; import { useQuery } from "@tanstack/react-query"; -import { chainRecord } from "@/chains"; +import { chainIdToPrettyName } from "@/chains/pretty"; import { useSkipClient } from "@/solve"; -export type Chain = { - prettyName: string; - record?: (typeof chainRecord)[string]; -} & SkipChain; +export type Chain = SkipChain & { prettyName: string }; export type UseChainsQueryArgs = { select?: (arr?: Chain[]) => T; @@ -27,11 +24,9 @@ export function useChains(args: UseChainsQueryArgs = {}) { return chains .map((chain): Chain => { - const record = chainRecord[chain.chainID]; return { ...chain, - prettyName: record?.pretty_name ?? chain.chainName, - record, + prettyName: chainIdToPrettyName[chain.chainID] || chain.chainName, }; }) .sort((chainA, chainB) => { diff --git a/src/ast/_types.ts b/src/ast/_types.ts index 913041c4..206492c4 100644 --- a/src/ast/_types.ts +++ b/src/ast/_types.ts @@ -1,4 +1,4 @@ -import { Asset, AssetList, Chain } from "@graz-sh/types"; +import { Asset, AssetList, Chain, Explorer } from "@graz-sh/types"; export interface Variables { assetlists: AssetList[]; @@ -8,8 +8,10 @@ export interface Variables { chainNames: string[]; chainIdToName: Record; + chainIdToPrettyName: Record; chainNameToId: Record; chainRecord: Record; assetsRecord: Record; + explorersRecord: Record; } diff --git a/src/ast/parse-chain-paths.ts b/src/ast/parse-chain-paths.ts index 7e565f3c..ab1dd0a0 100644 --- a/src/ast/parse-chain-paths.ts +++ b/src/ast/parse-chain-paths.ts @@ -1,4 +1,4 @@ -import { Asset, AssetList, Chain } from "@graz-sh/types"; +import { Asset, AssetList, Chain, Explorer } from "@graz-sh/types"; import pMap from "p-map"; import { concurrency } from "./_constants"; @@ -22,9 +22,11 @@ export async function parseChainPaths({ const chainNames: string[] = []; const chainIdToName: Record = {}; + const chainIdToPrettyName: Record = {}; const chainNameToId: Record = {}; const chainRecord: Record = {}; const assetsRecord: Record = {}; + const explorersRecord: Record = {}; async function loadChainPath(chainPath: string) { const [assetlist, chain] = await Promise.all([ @@ -39,13 +41,13 @@ export async function parseChainPaths({ chainNames.push(chain.chain_name); chainIdToName[chain.chain_id] = chain.chain_name; - chainIdToName[chain.chain_name] = chain.chain_name; + chainIdToPrettyName[chain.chain_id] = chain.pretty_name; chainNameToId[chain.chain_name] = chain.chain_id; - chainNameToId[chain.chain_id] = chain.chain_id; chainRecord[chain.chain_id] = chain; assetsRecord[chain.chain_id] = assetlist.assets; + explorersRecord[chain.chain_id] = chain.explorers || []; } await pMap(chainPaths, loadChainPath, { concurrency }); @@ -56,8 +58,10 @@ export async function parseChainPaths({ chainIds, chainNames, chainIdToName, + chainIdToPrettyName, chainNameToId, chainRecord, assetsRecord, + explorersRecord, }; } diff --git a/src/ast/write-entrypoints.ts b/src/ast/write-entrypoints.ts index c203a30e..50f72030 100644 --- a/src/ast/write-entrypoints.ts +++ b/src/ast/write-entrypoints.ts @@ -13,33 +13,69 @@ export async function writeEntrypoints({ variables: v, destPath }: Args) { .mkdir(path.resolve(destPath), { recursive: true }) .catch(() => void 0); - const generatedTs = `/* eslint-disable */ + const typesTs = `/* eslint-disable */ // @ts-nocheck -import { Asset, AssetList, Chain } from "@graz-sh/types"; - export const chainIds = ${JSON.stringify(v.chainIds)} as const; export type ChainId = (typeof chainIds)[number] | (string & {}); export const chainNames = ${JSON.stringify(v.chainNames)} as const; export type ChainName = (typeof chainNames)[number] | (string & {}); -export type ChainIdOrName = ChainId | ChainName; -export const chainIdToName: Record = ${JSON.stringify( +export const chainIdToName: Record = ${JSON.stringify( v.chainIdToName, )}; -export const chainNameToId: Record = ${JSON.stringify( +export const chainNameToId: Record = ${JSON.stringify( v.chainNameToId, )}; +`; + const typesTarget = path.resolve(destPath, "types.ts"); + await fs.writeFile(typesTarget, typesTs, "utf-8"); + + const prettyTs = `/* eslint-disable */ +// @ts-nocheck +import { ChainId } from "./types" + +export const chainIdToPrettyName: Record = ${JSON.stringify( + v.chainIdToPrettyName, + )}; +`; + const prettyTarget = path.resolve(destPath, "pretty.ts"); + await fs.writeFile(prettyTarget, prettyTs, "utf-8"); + + const explorersTs = `/* eslint-disable */ +// @ts-nocheck +import { Explorer } from "@graz-sh/types"; +import { ChainId } from "./types" + +export const explorersRecord: Record = ${JSON.stringify( + v.explorersRecord, + )}; +`; + const explorersTarget = path.resolve(destPath, "explorers.ts"); + await fs.writeFile(explorersTarget, explorersTs, "utf-8"); + + const chainsTs = `/* eslint-disable */ +// @ts-nocheck +import { Chain } from "@graz-sh/types"; +import { ChainId } from "./types"; export const chainRecord: Record = ${JSON.stringify( v.chainRecord, )}; +`; + const chainsTarget = path.resolve(destPath, "chains.ts"); + await fs.writeFile(chainsTarget, chainsTs, "utf-8"); + + const assetsTs = `/* eslint-disable */ +// @ts-nocheck +import { Asset, AssetList } from "@graz-sh/types"; +import { ChainId } from "./types"; export const assetsRecord: Record = ${JSON.stringify( v.assetsRecord, )}; `; - const generatedTarget = path.resolve(destPath, "generated.ts"); - await fs.writeFile(generatedTarget, generatedTs, "utf-8"); + const assetsTarget = path.resolve(destPath, "assets.ts"); + await fs.writeFile(assetsTarget, assetsTs, "utf-8"); } diff --git a/src/chains/index.ts b/src/chains/index.ts index 6d5db6cf..34ab0d2d 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -1,31 +1,24 @@ -/* eslint-disable */ -// @ts-nocheck -import { Asset, AssetList, Chain } from "@graz-sh/types"; -import { - assetsRecord, - ChainIdOrName, - chainIds, - chainIdToName, - chainNameToId, - chainRecord, -} from "./generated"; +import { Asset, AssetList, Chain } from "@graz-sh/types"; +import { ChainId, chainIdToName, chainIds } from "./types"; +import { chainRecord } from "./chains"; +import { assetsRecord } from "./assets"; function raise(message?: string, opts?: ErrorOptions): never { throw new Error(message, opts); } -export function getChain(idOrName: ChainIdOrName): Chain { +export function getChain(chainId: ChainId): Chain { return ( - chainRecord[chainNameToId[idOrName]] || - raise(`chain '${idOrName}' does not exist in chainRecord`) + chainRecord[chainId] || + raise(`chain '${chainId}' does not exist in chainRecord`) ); } -export function getAssets(idOrName: ChainIdOrName): Asset[] { +export function getAssets(chainId: ChainId): Asset[] { return ( - assetsRecord[chainNameToId[idOrName]] || - raise(`chain '${idOrName}' does not exist in assetsRecord`) + assetsRecord[chainId] || + raise(`chain '${chainId}' does not exist in assetsRecord`) ); } @@ -40,4 +33,4 @@ export function getAssetLists(): AssetList[] { })); } -export * from "./generated"; +export * from "./types"; diff --git a/src/components/AssetValue.tsx b/src/components/AssetValue.tsx index 1c36b1c1..a2ad6dd9 100644 --- a/src/components/AssetValue.tsx +++ b/src/components/AssetValue.tsx @@ -1,12 +1,12 @@ import { BigNumberish, formatUnits } from "ethers"; import { useMemo } from "react"; -import { ChainIdOrName } from "@/chains"; +import { ChainId } from "@/chains/types"; import { useAssets } from "@/context/assets"; import { raise } from "@/utils/assert"; interface Props { - chainId: ChainIdOrName; + chainId: ChainId; denom: string; value: BigNumberish; } diff --git a/src/components/ChainSymbol.tsx b/src/components/ChainSymbol.tsx index d6f01c09..1e443c11 100644 --- a/src/components/ChainSymbol.tsx +++ b/src/components/ChainSymbol.tsx @@ -2,11 +2,11 @@ import { CubeIcon } from "@heroicons/react/20/solid"; import { useMemo } from "react"; import { useChainByID } from "@/api/queries"; -import { ChainIdOrName } from "@/chains"; +import { ChainId } from "@/chains/types"; import { getChainLogo } from "@/cosmos"; interface Props { - chainId: ChainIdOrName; + chainId: ChainId; } export const ChainSymbol = ({ chainId }: Props) => { diff --git a/src/components/WalletModal/WalletModal.tsx b/src/components/WalletModal/WalletModal.tsx index 2a0ccc25..af876804 100644 --- a/src/components/WalletModal/WalletModal.tsx +++ b/src/components/WalletModal/WalletModal.tsx @@ -4,9 +4,9 @@ import { FC } from "react"; import { useAccount, useConnect, useDisconnect } from "wagmi"; import { useChainByID } from "@/api/queries"; +import { chainIdToName } from "@/chains/types"; import { EVM_WALLET_LOGOS, INJECTED_EVM_WALLET_LOGOS } from "@/constants/wagmi"; import { DialogContent } from "@/elements/Dialog"; -import { getChainByID } from "@/utils/utils"; import { useWalletModal } from "./context"; @@ -113,10 +113,8 @@ const WalletModalWithContext: FC = () => { let wallets: MinimalWallet[] = []; if (chainType === "cosmos") { - const chainName = getChainByID(chainID).chain_name; - + const chainName = chainIdToName[chainID]; const walletRepo = getWalletRepo(chainName); - wallets = walletRepo.wallets; } diff --git a/src/hooks/useAccount.ts b/src/hooks/useAccount.ts index f0aeb661..e5570397 100644 --- a/src/hooks/useAccount.ts +++ b/src/hooks/useAccount.ts @@ -7,7 +7,7 @@ import { EVM_WALLET_LOGOS } from "@/constants/wagmi"; export function useAccount(chainID: string) { const { data: chain } = useChainByID(chainID); - const cosmosChain = useChain(chain?.record?.chain_name ?? "cosmoshub"); + const cosmosChain = useChain(chain?.chainName ?? "cosmoshub"); const wagmiAccount = useWagmiAccount(); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index c9a0f603..459b6b35 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -15,7 +15,7 @@ import { useQuery } from "@tanstack/react-query"; import { erc20ABI, PublicClient, usePublicClient } from "wagmi"; import { Chain } from "@/api/queries"; -import { ChainId, getChain } from "@/chains"; +import { ChainId } from "@/chains/types"; import { multicall3ABI } from "@/constants/abis"; import { EVM_CHAINS } from "@/constants/wagmi"; import { AssetWithMetadata } from "@/context/assets"; @@ -24,10 +24,6 @@ import { useSkipClient } from "@/solve"; import { getNodeProxyEndpoint } from "./api"; -export function getChainByID(chainID: ChainId) { - return getChain(chainID); -} - // cache clients to reuse later const STARGATE_CLIENTS: Record = {}; From 7c72333dfea7a897a2d825c640d12cecf1fdc891 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:39:50 +0700 Subject: [PATCH 016/115] chore: merge asset select filter method Signed-off-by: Griko Nibras --- src/assets/filters.ts | 8 -------- src/components/AssetSelect/AssetSelectContent.tsx | 11 +++++++++-- 2 files changed, 9 insertions(+), 10 deletions(-) delete mode 100644 src/assets/filters.ts diff --git a/src/assets/filters.ts b/src/assets/filters.ts deleted file mode 100644 index 5c9d6f41..00000000 --- a/src/assets/filters.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AssetWithMetadata } from "@/context/assets"; - -export function filterSifAssets(asset: AssetWithMetadata) { - if (asset.originChainID === "sifchain-1" && asset.originDenom !== "rowan") { - return false; - } - return true; -} diff --git a/src/components/AssetSelect/AssetSelectContent.tsx b/src/components/AssetSelect/AssetSelectContent.tsx index 90d9b5af..7e0801db 100644 --- a/src/components/AssetSelect/AssetSelectContent.tsx +++ b/src/components/AssetSelect/AssetSelectContent.tsx @@ -3,7 +3,6 @@ import { ArrowLeftIcon } from "@heroicons/react/20/solid"; import { ethers, toBigInt } from "ethers"; import { FC, useEffect, useRef, useState } from "react"; -import { filterSifAssets } from "@/assets/filters"; import { AssetWithMetadata } from "@/context/assets"; import { useWindowSize } from "@/hooks/useWindowSize"; @@ -54,7 +53,15 @@ const AssetSelectContent: FC = ({ return 0; }) - .filter(filterSifAssets) + .filter((asset) => { + if ( + asset.originChainID === "sifchain-1" && + asset.originDenom !== "rowan" + ) { + return false; + } + return true; + }) .sort((a, b) => { const balanceA = balances[a.denom] ? toBigInt(balances[a.denom]) : 0n; const balanceB = balances[b.denom] ? toBigInt(balances[b.denom]) : 0n; From 350c7c121a46399ee98a17992d879d4d6af4b855 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:44:52 +0700 Subject: [PATCH 017/115] refactor: merge elements dir in components Signed-off-by: Griko Nibras --- src/components/AssetInput.tsx | 2 +- src/components/AssetSelect/index.tsx | 2 +- src/components/ChainSelect/index.tsx | 2 +- src/{elements => components}/Dialog/Dialog.tsx | 0 src/{elements => components}/Dialog/DialogContent.tsx | 0 src/{elements => components}/Dialog/DialogTrigger.tsx | 0 src/{elements => components}/Dialog/context.ts | 0 src/{elements => components}/Dialog/index.tsx | 0 src/{elements => components}/Toast.tsx | 0 src/components/TransactionDialog/TransactionDialogContent.tsx | 2 +- src/components/WalletModal/WalletModal.tsx | 2 +- src/components/WalletModal/context.tsx | 2 +- src/context/toast.tsx | 2 +- 13 files changed, 7 insertions(+), 7 deletions(-) rename src/{elements => components}/Dialog/Dialog.tsx (100%) rename src/{elements => components}/Dialog/DialogContent.tsx (100%) rename src/{elements => components}/Dialog/DialogTrigger.tsx (100%) rename src/{elements => components}/Dialog/context.ts (100%) rename src/{elements => components}/Dialog/index.tsx (100%) rename src/{elements => components}/Toast.tsx (100%) diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index 8bd52986..efaa379c 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -8,7 +8,7 @@ import { Chain } from "@/api/queries"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { disclosure } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; -import Toast from "@/elements/Toast"; +import Toast from "@/components/Toast"; import { useAccount } from "@/hooks/useAccount"; import { formatUSD, useBalancesByChain } from "@/utils/utils"; diff --git a/src/components/AssetSelect/index.tsx b/src/components/AssetSelect/index.tsx index 8d2606b4..e8256928 100644 --- a/src/components/AssetSelect/index.tsx +++ b/src/components/AssetSelect/index.tsx @@ -2,8 +2,8 @@ import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { FC, useState } from "react"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/Dialog"; import { AssetWithMetadata } from "@/context/assets"; -import { Dialog, DialogContent, DialogTrigger } from "@/elements/Dialog"; import AssetSelectContent from "./AssetSelectContent"; diff --git a/src/components/ChainSelect/index.tsx b/src/components/ChainSelect/index.tsx index b54223a3..3037b0fb 100644 --- a/src/components/ChainSelect/index.tsx +++ b/src/components/ChainSelect/index.tsx @@ -1,7 +1,7 @@ import { FC, Fragment, useState } from "react"; import { Chain } from "@/api/queries"; -import { Dialog, DialogContent, DialogTrigger } from "@/elements/Dialog"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/Dialog"; import ChainSelectContent from "./ChainSelectContent"; import ChainSelectTrigger from "./ChainSelectTrigger"; diff --git a/src/elements/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx similarity index 100% rename from src/elements/Dialog/Dialog.tsx rename to src/components/Dialog/Dialog.tsx diff --git a/src/elements/Dialog/DialogContent.tsx b/src/components/Dialog/DialogContent.tsx similarity index 100% rename from src/elements/Dialog/DialogContent.tsx rename to src/components/Dialog/DialogContent.tsx diff --git a/src/elements/Dialog/DialogTrigger.tsx b/src/components/Dialog/DialogTrigger.tsx similarity index 100% rename from src/elements/Dialog/DialogTrigger.tsx rename to src/components/Dialog/DialogTrigger.tsx diff --git a/src/elements/Dialog/context.ts b/src/components/Dialog/context.ts similarity index 100% rename from src/elements/Dialog/context.ts rename to src/components/Dialog/context.ts diff --git a/src/elements/Dialog/index.tsx b/src/components/Dialog/index.tsx similarity index 100% rename from src/elements/Dialog/index.tsx rename to src/components/Dialog/index.tsx diff --git a/src/elements/Toast.tsx b/src/components/Toast.tsx similarity index 100% rename from src/elements/Toast.tsx rename to src/components/Toast.tsx diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index 5fcd4cc7..6f9b423c 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -14,7 +14,7 @@ import { failTxHistory, successTxHistory, } from "@/context/tx-history"; -import Toast from "@/elements/Toast"; +import Toast from "@/components/Toast"; import { useFinalityTimeEstimate } from "@/hooks/useFinalityTimeEstimate"; import { useSkipClient } from "@/solve"; import { diff --git a/src/components/WalletModal/WalletModal.tsx b/src/components/WalletModal/WalletModal.tsx index af876804..18f70f6f 100644 --- a/src/components/WalletModal/WalletModal.tsx +++ b/src/components/WalletModal/WalletModal.tsx @@ -6,7 +6,7 @@ import { useAccount, useConnect, useDisconnect } from "wagmi"; import { useChainByID } from "@/api/queries"; import { chainIdToName } from "@/chains/types"; import { EVM_WALLET_LOGOS, INJECTED_EVM_WALLET_LOGOS } from "@/constants/wagmi"; -import { DialogContent } from "@/elements/Dialog"; +import { DialogContent } from "@/components/Dialog"; import { useWalletModal } from "./context"; diff --git a/src/components/WalletModal/context.tsx b/src/components/WalletModal/context.tsx index 8e6dbe06..8c7fa378 100644 --- a/src/components/WalletModal/context.tsx +++ b/src/components/WalletModal/context.tsx @@ -6,7 +6,7 @@ import { useState, } from "react"; -import { Dialog } from "@/elements/Dialog"; +import { Dialog } from "@/components/Dialog"; interface WalletModalContext { chainID: string; diff --git a/src/context/toast.tsx b/src/context/toast.tsx index 32776d79..b683d533 100644 --- a/src/context/toast.tsx +++ b/src/context/toast.tsx @@ -6,7 +6,7 @@ import { useState, } from "react"; -import Toast from "@/elements/Toast"; +import Toast from "@/components/Toast"; interface ToastContext { toast: (title: string, message: string, type: "success" | "error") => void; From 55839e0b0b350b4bdd6fb5470a849e0f6f635408 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:48:11 +0700 Subject: [PATCH 018/115] =?UTF-8?q?refactor:=20api/queries=20=E2=86=92=20h?= =?UTF-8?q?ooks/useChains?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Griko Nibras --- src/components/AssetInput.tsx | 4 ++-- src/components/ChainSelect/ChainSelectContent.tsx | 2 +- src/components/ChainSelect/ChainSelectTrigger.tsx | 2 +- src/components/ChainSelect/index.tsx | 2 +- src/components/ChainSymbol.tsx | 2 +- src/components/RouteDisplay.tsx | 2 +- src/components/SwapWidget/SwapWidget.tsx | 2 +- src/components/SwapWidget/useSwapWidget.ts | 2 +- src/components/TransactionDialog/TransactionDialogContent.tsx | 4 ++-- src/components/TransactionSuccessView.tsx | 2 +- src/components/WalletModal/WalletModal.tsx | 2 +- src/context/assets.tsx | 2 +- .../queries.test.ts => hooks/__test__/useChains.test.ts} | 2 +- src/hooks/useAccount.ts | 2 +- src/{api/queries.ts => hooks/useChains.ts} | 0 src/hooks/useFinalityTimeEstimate.ts | 2 +- src/utils/utils.ts | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) rename src/{api/__test__/queries.test.ts => hooks/__test__/useChains.test.ts} (95%) rename src/{api/queries.ts => hooks/useChains.ts} (100%) diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index efaa379c..8169b04b 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -4,12 +4,12 @@ import { clsx } from "clsx"; import { ethers } from "ethers"; import { FC, Fragment, useMemo, useState } from "react"; -import { Chain } from "@/api/queries"; +import Toast from "@/components/Toast"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { disclosure } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; -import Toast from "@/components/Toast"; import { useAccount } from "@/hooks/useAccount"; +import { Chain } from "@/hooks/useChains"; import { formatUSD, useBalancesByChain } from "@/utils/utils"; import AssetSelect from "./AssetSelect"; diff --git a/src/components/ChainSelect/ChainSelectContent.tsx b/src/components/ChainSelect/ChainSelectContent.tsx index 3f484346..4709fbbf 100644 --- a/src/components/ChainSelect/ChainSelectContent.tsx +++ b/src/components/ChainSelect/ChainSelectContent.tsx @@ -2,8 +2,8 @@ import { ArrowLeftIcon } from "@heroicons/react/20/solid"; import { FC, useEffect, useMemo, useRef, useState } from "react"; -import { Chain } from "@/api/queries"; import { getChainLogo } from "@/cosmos"; +import { Chain } from "@/hooks/useChains"; import { useWindowSize } from "@/hooks/useWindowSize"; interface Props { diff --git a/src/components/ChainSelect/ChainSelectTrigger.tsx b/src/components/ChainSelect/ChainSelectTrigger.tsx index f38c4bc8..32479015 100644 --- a/src/components/ChainSelect/ChainSelectTrigger.tsx +++ b/src/components/ChainSelect/ChainSelectTrigger.tsx @@ -1,7 +1,7 @@ import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { ForwardedRef, forwardRef } from "react"; -import { Chain } from "@/api/queries"; +import { Chain } from "@/hooks/useChains"; interface Props { chain?: Chain; diff --git a/src/components/ChainSelect/index.tsx b/src/components/ChainSelect/index.tsx index 3037b0fb..a5573690 100644 --- a/src/components/ChainSelect/index.tsx +++ b/src/components/ChainSelect/index.tsx @@ -1,7 +1,7 @@ import { FC, Fragment, useState } from "react"; -import { Chain } from "@/api/queries"; import { Dialog, DialogContent, DialogTrigger } from "@/components/Dialog"; +import { Chain } from "@/hooks/useChains"; import ChainSelectContent from "./ChainSelectContent"; import ChainSelectTrigger from "./ChainSelectTrigger"; diff --git a/src/components/ChainSymbol.tsx b/src/components/ChainSymbol.tsx index 1e443c11..4b49edc7 100644 --- a/src/components/ChainSymbol.tsx +++ b/src/components/ChainSymbol.tsx @@ -1,9 +1,9 @@ import { CubeIcon } from "@heroicons/react/20/solid"; import { useMemo } from "react"; -import { useChainByID } from "@/api/queries"; import { ChainId } from "@/chains/types"; import { getChainLogo } from "@/cosmos"; +import { useChainByID } from "@/hooks/useChains"; interface Props { chainId: ChainId; diff --git a/src/components/RouteDisplay.tsx b/src/components/RouteDisplay.tsx index 36a336f5..0b8e9f93 100644 --- a/src/components/RouteDisplay.tsx +++ b/src/components/RouteDisplay.tsx @@ -3,9 +3,9 @@ import { RouteResponse } from "@skip-router/core"; import { ethers } from "ethers"; import { FC, Fragment, useMemo, useState } from "react"; -import { useChainByID } from "@/api/queries"; import { useAssets } from "@/context/assets"; import { getChainLogo } from "@/cosmos"; +import { useChainByID } from "@/hooks/useChains"; export interface SwapVenueConfig { name: string; diff --git a/src/components/SwapWidget/SwapWidget.tsx b/src/components/SwapWidget/SwapWidget.tsx index 60048598..1bf805c5 100644 --- a/src/components/SwapWidget/SwapWidget.tsx +++ b/src/components/SwapWidget/SwapWidget.tsx @@ -4,10 +4,10 @@ import { clsx } from "clsx"; import { FC, useEffect } from "react"; import type {} from "typed-query-selector"; -import { useChains as useSkipChains } from "@/api/queries"; import { useDisclosureKey } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; import { useAccount } from "@/hooks/useAccount"; +import { useChains as useSkipChains } from "@/hooks/useChains"; import AssetInput from "../AssetInput"; import { ConnectedWalletButton } from "../ConnectedWalletButton"; diff --git a/src/components/SwapWidget/useSwapWidget.ts b/src/components/SwapWidget/useSwapWidget.ts index b4ef1f1b..55666926 100644 --- a/src/components/SwapWidget/useSwapWidget.ts +++ b/src/components/SwapWidget/useSwapWidget.ts @@ -5,9 +5,9 @@ import { subscribeWithSelector } from "zustand/middleware"; import { shallow } from "zustand/shallow"; import { createWithEqualityFn as create } from "zustand/traditional"; -import { Chain, useChains } from "@/api/queries"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { useAccount } from "@/hooks/useAccount"; +import { Chain, useChains } from "@/hooks/useChains"; import { useRoute } from "@/solve"; import { useBalancesByChain } from "@/utils/utils"; diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index 6f9b423c..b01456f3 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -5,7 +5,7 @@ import { clsx } from "clsx"; import { FC, Fragment, useState } from "react"; import { useAccount } from "wagmi"; -import { Chain, useChains } from "@/api/queries"; +import Toast from "@/components/Toast"; import { useSettingsStore } from "@/context/settings"; import { useToast } from "@/context/toast"; import { @@ -14,7 +14,7 @@ import { failTxHistory, successTxHistory, } from "@/context/tx-history"; -import Toast from "@/components/Toast"; +import { useChains } from "@/hooks/useChains"; import { useFinalityTimeEstimate } from "@/hooks/useFinalityTimeEstimate"; import { useSkipClient } from "@/solve"; import { diff --git a/src/components/TransactionSuccessView.tsx b/src/components/TransactionSuccessView.tsx index c14b4577..f0448b04 100644 --- a/src/components/TransactionSuccessView.tsx +++ b/src/components/TransactionSuccessView.tsx @@ -2,8 +2,8 @@ import { CheckCircleIcon } from "@heroicons/react/20/solid"; import { RouteResponse } from "@skip-router/core"; import { FC } from "react"; -import { Chain, useChains } from "@/api/queries"; import { useAssets } from "@/context/assets"; +import { Chain, useChains } from "@/hooks/useChains"; import { RouteTransaction } from "./TransactionDialog/TransactionDialogContent"; diff --git a/src/components/WalletModal/WalletModal.tsx b/src/components/WalletModal/WalletModal.tsx index 18f70f6f..c3ec445c 100644 --- a/src/components/WalletModal/WalletModal.tsx +++ b/src/components/WalletModal/WalletModal.tsx @@ -3,7 +3,7 @@ import { ArrowLeftIcon } from "@heroicons/react/20/solid"; import { FC } from "react"; import { useAccount, useConnect, useDisconnect } from "wagmi"; -import { useChainByID } from "@/api/queries"; +import { useChainByID } from "@/hooks/useChains"; import { chainIdToName } from "@/chains/types"; import { EVM_WALLET_LOGOS, INJECTED_EVM_WALLET_LOGOS } from "@/constants/wagmi"; import { DialogContent } from "@/components/Dialog"; diff --git a/src/context/assets.tsx b/src/context/assets.tsx index 162a273c..5f2774c9 100644 --- a/src/context/assets.tsx +++ b/src/context/assets.tsx @@ -7,7 +7,7 @@ import { useMemo, } from "react"; -import { Chain, useChains } from "@/api/queries"; +import { Chain, useChains } from "@/hooks/useChains"; import { filterAssetsWithMetadata, diff --git a/src/api/__test__/queries.test.ts b/src/hooks/__test__/useChains.test.ts similarity index 95% rename from src/api/__test__/queries.test.ts rename to src/hooks/__test__/useChains.test.ts index c433e524..84117173 100644 --- a/src/api/__test__/queries.test.ts +++ b/src/hooks/__test__/useChains.test.ts @@ -1,6 +1,6 @@ import { AllTheProviders, renderHook, waitFor } from "@/test"; -import { useChainByID, useChains } from "../queries"; +import { useChainByID, useChains } from "../useChains"; test("useChains attaches a prettyName", async () => { const { result } = renderHook(() => useChains(), { diff --git a/src/hooks/useAccount.ts b/src/hooks/useAccount.ts index e5570397..e4c7c071 100644 --- a/src/hooks/useAccount.ts +++ b/src/hooks/useAccount.ts @@ -1,8 +1,8 @@ import { useChain } from "@cosmos-kit/react"; import { useAccount as useWagmiAccount } from "wagmi"; -import { useChainByID } from "@/api/queries"; import { EVM_WALLET_LOGOS } from "@/constants/wagmi"; +import { useChainByID } from "@/hooks/useChains"; export function useAccount(chainID: string) { const { data: chain } = useChainByID(chainID); diff --git a/src/api/queries.ts b/src/hooks/useChains.ts similarity index 100% rename from src/api/queries.ts rename to src/hooks/useChains.ts diff --git a/src/hooks/useFinalityTimeEstimate.ts b/src/hooks/useFinalityTimeEstimate.ts index bf7f234c..ac3399c9 100644 --- a/src/hooks/useFinalityTimeEstimate.ts +++ b/src/hooks/useFinalityTimeEstimate.ts @@ -1,8 +1,8 @@ import { RouteResponse } from "@skip-router/core"; import { useMemo } from "react"; -import { useChains } from "@/api/queries"; import { getFinalityTime } from "@/constants/finality"; +import { useChains } from "@/hooks/useChains"; export function useFinalityTimeEstimate(route: RouteResponse) { const { data: chains = [] } = useChains(); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 459b6b35..a822ff0d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -14,11 +14,11 @@ import { SkipRouter } from "@skip-router/core"; import { useQuery } from "@tanstack/react-query"; import { erc20ABI, PublicClient, usePublicClient } from "wagmi"; -import { Chain } from "@/api/queries"; import { ChainId } from "@/chains/types"; import { multicall3ABI } from "@/constants/abis"; import { EVM_CHAINS } from "@/constants/wagmi"; import { AssetWithMetadata } from "@/context/assets"; +import { Chain } from "@/hooks/useChains"; import { MergedWalletClient } from "@/lib/cosmos-kit"; import { useSkipClient } from "@/solve"; From 6668af443904aa60b2521539e308532bc86ce82b Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:48:23 +0700 Subject: [PATCH 019/115] chore: remove duplicate Signed-off-by: Griko Nibras --- src/lib/cosmos-kit.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/cosmos-kit.ts b/src/lib/cosmos-kit.ts index f058086b..a15466ef 100644 --- a/src/lib/cosmos-kit.ts +++ b/src/lib/cosmos-kit.ts @@ -28,6 +28,5 @@ export type MergedWalletClient = | import("@cosmos-kit/leap-mobile/cjs/wallet-connect/client").LeapClient | import("@cosmos-kit/okxwallet-extension/cjs/extension/client").OkxwalletClient | import("@cosmos-kit/station-extension/cjs/extension/client").StationClient - | import("@cosmos-kit/station-extension/cjs/extension/client").StationClient | import("@cosmos-kit/vectis-extension/cjs/extension/client").VectisClient | import("@cosmos-kit/xdefi-extension/cjs/extension/client").XDEFIClient; From d408a3c89a9c18e9e1db87fefaf543658404a1b6 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:49:45 +0700 Subject: [PATCH 020/115] =?UTF-8?q?refactor:=20src/cosmos=20=E2=86=92=20sr?= =?UTF-8?q?c/lib/cosmos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Griko Nibras --- src/components/ChainSelect/ChainSelectContent.tsx | 2 +- src/components/ChainSymbol.tsx | 2 +- src/components/RouteDisplay.tsx | 2 +- src/{cosmos/index.ts => lib/cosmos.ts} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/{cosmos/index.ts => lib/cosmos.ts} (97%) diff --git a/src/components/ChainSelect/ChainSelectContent.tsx b/src/components/ChainSelect/ChainSelectContent.tsx index 4709fbbf..1efab85c 100644 --- a/src/components/ChainSelect/ChainSelectContent.tsx +++ b/src/components/ChainSelect/ChainSelectContent.tsx @@ -2,9 +2,9 @@ import { ArrowLeftIcon } from "@heroicons/react/20/solid"; import { FC, useEffect, useMemo, useRef, useState } from "react"; -import { getChainLogo } from "@/cosmos"; import { Chain } from "@/hooks/useChains"; import { useWindowSize } from "@/hooks/useWindowSize"; +import { getChainLogo } from "@/lib/cosmos"; interface Props { chains: Chain[]; diff --git a/src/components/ChainSymbol.tsx b/src/components/ChainSymbol.tsx index 4b49edc7..91177676 100644 --- a/src/components/ChainSymbol.tsx +++ b/src/components/ChainSymbol.tsx @@ -2,8 +2,8 @@ import { CubeIcon } from "@heroicons/react/20/solid"; import { useMemo } from "react"; import { ChainId } from "@/chains/types"; -import { getChainLogo } from "@/cosmos"; import { useChainByID } from "@/hooks/useChains"; +import { getChainLogo } from "@/lib/cosmos"; interface Props { chainId: ChainId; diff --git a/src/components/RouteDisplay.tsx b/src/components/RouteDisplay.tsx index 0b8e9f93..ff5384c5 100644 --- a/src/components/RouteDisplay.tsx +++ b/src/components/RouteDisplay.tsx @@ -4,8 +4,8 @@ import { ethers } from "ethers"; import { FC, Fragment, useMemo, useState } from "react"; import { useAssets } from "@/context/assets"; -import { getChainLogo } from "@/cosmos"; import { useChainByID } from "@/hooks/useChains"; +import { getChainLogo } from "@/lib/cosmos"; export interface SwapVenueConfig { name: string; diff --git a/src/cosmos/index.ts b/src/lib/cosmos.ts similarity index 97% rename from src/cosmos/index.ts rename to src/lib/cosmos.ts index 87e001b6..6035c373 100644 --- a/src/cosmos/index.ts +++ b/src/lib/cosmos.ts @@ -1,4 +1,4 @@ -import { Chain } from "@/api/queries"; +import { Chain } from "@/hooks/useChains"; // chains whose logo provided by the Skip API is not suitable for our UI. const chainsToUseChainListLogo = [ From ac261ee243467491ac7941ed9441eddba7a99069 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:50:12 +0700 Subject: [PATCH 021/115] feat: add tanstack persist deps Signed-off-by: Griko Nibras --- package-lock.json | 81 ++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 ++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 328d1119..2662a065 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,9 @@ "@react-stately/table": "^3.11.4", "@sentry/nextjs": "^7.92.0", "@skip-router/core": "^1.2.0", + "@tanstack/query-sync-storage-persister": "^5.17.1", "@tanstack/react-query": "^5.17.1", + "@tanstack/react-query-persist-client": "^5.17.1", "@types/node": "^20.10.6", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", @@ -8167,11 +8169,33 @@ } }, "node_modules/@tanstack/query-sync-storage-persister": { - "version": "4.36.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.36.1.tgz", - "integrity": "sha512-yMEt5hWe2+1eclf1agMtXHnPIkxEida0lYWkfdhR8U6KXk/lO4Vca6piJmhKI85t0NHlx3l/z6zX+t/Fn5O9NA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.17.1.tgz", + "integrity": "sha512-XOt1oj4/3b6siQ+vyZW3k9nUNsjRmc5AXUXLIM+um5MmvGxApD6UdMgC/+B4y3ehcqVpppcB+oxIG8Ba6QQLWg==", "dependencies": { - "@tanstack/query-persist-client-core": "4.36.1" + "@tanstack/query-core": "5.17.1", + "@tanstack/query-persist-client-core": "5.17.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-sync-storage-persister/node_modules/@tanstack/query-core": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.1.tgz", + "integrity": "sha512-kUXozQmU7NBtzX5dM6qfFNZN+YK/9Ct37hnG/ogdgI4mExIx7VH/qRepsPhKfNrJz2w81/JykmM3Uug6sVpUSw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-sync-storage-persister/node_modules/@tanstack/query-persist-client-core": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.17.1.tgz", + "integrity": "sha512-Eo3LkMmwoZf3zrtL3ZljXekuWSZPUZnLwGjr4naSKSHYTyeWNJzWlNxjxLO8JNEZGd4ZdDgvdJzqxCEnJOzQ2Q==", + "dependencies": { + "@tanstack/query-core": "5.17.1" }, "funding": { "type": "github", @@ -8193,6 +8217,43 @@ "react": "^18.0.0" } }, + "node_modules/@tanstack/react-query-persist-client": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.17.1.tgz", + "integrity": "sha512-b1+xOlMEanreYJXt9HKoBDSsjRTmmeudCeNRNMR5Epl5pIDWzFICpJxXIrfOBcNiqS3IVHSg3VsAu9mmR9ztYg==", + "dependencies": { + "@tanstack/query-persist-client-core": "5.17.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.17.1", + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-query-persist-client/node_modules/@tanstack/query-core": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.1.tgz", + "integrity": "sha512-kUXozQmU7NBtzX5dM6qfFNZN+YK/9Ct37hnG/ogdgI4mExIx7VH/qRepsPhKfNrJz2w81/JykmM3Uug6sVpUSw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query-persist-client/node_modules/@tanstack/query-persist-client-core": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.17.1.tgz", + "integrity": "sha512-Eo3LkMmwoZf3zrtL3ZljXekuWSZPUZnLwGjr4naSKSHYTyeWNJzWlNxjxLO8JNEZGd4ZdDgvdJzqxCEnJOzQ2Q==", + "dependencies": { + "@tanstack/query-core": "5.17.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-query/node_modules/@tanstack/query-core": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.1.tgz", @@ -23014,6 +23075,18 @@ } } }, + "node_modules/wagmi/node_modules/@tanstack/query-sync-storage-persister": { + "version": "4.36.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.36.1.tgz", + "integrity": "sha512-yMEt5hWe2+1eclf1agMtXHnPIkxEida0lYWkfdhR8U6KXk/lO4Vca6piJmhKI85t0NHlx3l/z6zX+t/Fn5O9NA==", + "dependencies": { + "@tanstack/query-persist-client-core": "4.36.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/wagmi/node_modules/@tanstack/react-query": { "version": "4.36.1", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.36.1.tgz", diff --git a/package.json b/package.json index 6f7aa944..1fe98a00 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,9 @@ "@react-stately/table": "^3.11.4", "@sentry/nextjs": "^7.92.0", "@skip-router/core": "^1.2.0", + "@tanstack/query-sync-storage-persister": "^5.17.1", "@tanstack/react-query": "^5.17.1", + "@tanstack/react-query-persist-client": "^5.17.1", "@types/node": "^20.10.6", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", From c5dbbeccb7adeda9d7f0e937583f6b555ea961c2 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:50:33 +0700 Subject: [PATCH 022/115] feat: setup query client persister Signed-off-by: Griko Nibras --- src/lib/react-query.ts | 2 +- src/pages/_app.tsx | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/react-query.ts b/src/lib/react-query.ts index 7e5c73fd..cbb5c591 100644 --- a/src/lib/react-query.ts +++ b/src/lib/react-query.ts @@ -3,7 +3,7 @@ import { QueryClient } from "@tanstack/react-query"; export const queryClient = new QueryClient({ defaultOptions: { queries: { - staleTime: 1000 * 5, + gcTime: 1000 * 60 * 60 * 24, // 24 hours }, }, }); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 1e7fc081..0e8be220 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,7 +3,8 @@ import "@interchain-ui/react/styles"; import { ChainProvider } from "@cosmos-kit/react"; import * as RadixToast from "@radix-ui/react-toast"; -import { QueryClientProvider } from "@tanstack/react-query"; +import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; +import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; import { Analytics } from "@vercel/analytics/react"; import { AppProps } from "next/app"; import Head from "next/head"; @@ -20,6 +21,10 @@ import { queryClient } from "@/lib/react-query"; import { wagmiConfig } from "@/lib/wagmi"; import { SkipProvider } from "@/solve"; +const persister = createSyncStoragePersister({ + storage: typeof window !== "undefined" ? window.sessionStorage : undefined, +}); + type ChainProviderProps = ComponentProps; const assets = getAssetLists() as ChainProviderProps["assetLists"]; @@ -38,7 +43,10 @@ export default function App({ Component, pageProps }: AppProps) { />
- + - +
From e3f7fbbbd88737a6e10460149b0a8816157a2ea4 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 01:52:18 +0700 Subject: [PATCH 023/115] feat: stop refetches on single use queries Signed-off-by: Griko Nibras --- src/hooks/useChains.ts | 3 +++ src/solve/queries.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/hooks/useChains.ts b/src/hooks/useChains.ts index 5a61108e..f1b68cd7 100644 --- a/src/hooks/useChains.ts +++ b/src/hooks/useChains.ts @@ -33,6 +33,9 @@ export function useChains(args: UseChainsQueryArgs = {}) { return chainA.prettyName.localeCompare(chainB.prettyName); }); }, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, select, }); } diff --git a/src/solve/queries.ts b/src/solve/queries.ts index e05288ec..a597fb75 100644 --- a/src/solve/queries.ts +++ b/src/solve/queries.ts @@ -18,6 +18,9 @@ export function useAssets(options: AssetsRequest = {}) { ...options, }); }, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, }); } From c77228efc1638bd0bbc1f70aa3c081fe8198fdb1 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 02:10:25 +0700 Subject: [PATCH 024/115] refactor: colocate intl formatters Signed-off-by: Griko Nibras --- src/components/AssetInput.tsx | 14 +++---- .../AssetSelect/AssetSelectContent.tsx | 12 ++---- src/components/AssetValue.tsx | 5 +-- src/components/HistoryDialog/RenderDate.tsx | 9 ++--- src/components/SwapWidget/SwapDetails.tsx | 14 ++----- src/components/SwapWidget/useSwapWidget.ts | 29 +++++--------- src/components/UsdValue.tsx | 11 ++--- src/utils/intl.ts | 40 +++++++++++++++++++ src/utils/utils.ts | 14 ------- 9 files changed, 72 insertions(+), 76 deletions(-) create mode 100644 src/utils/intl.ts diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index 8169b04b..ab0e42d8 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -10,7 +10,8 @@ import { disclosure } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; import { useAccount } from "@/hooks/useAccount"; import { Chain } from "@/hooks/useChains"; -import { formatUSD, useBalancesByChain } from "@/utils/utils"; +import { formatMaxFraction, formatPercent, formatUSD } from "@/utils/intl"; +import { useBalancesByChain } from "@/utils/utils"; import AssetSelect from "./AssetSelect"; import ChainSelect from "./ChainSelect"; @@ -80,10 +81,8 @@ const AssetInput: FC = ({ }, [asset, balances]); const formattedSelectedAssetBalance = useMemo(() => { - const { format } = new Intl.NumberFormat("en-US", { - maximumFractionDigits: 6, - }); - return format(parseFloat(selectedAssetBalance ?? "0.0")); + const amount = parseFloat(selectedAssetBalance ?? "0.0"); + return formatMaxFraction(amount, 6); }, [selectedAssetBalance]); const maxButtonDisabled = useMemo(() => { @@ -194,10 +193,7 @@ const AssetInput: FC = ({ diffPercentage >= 0 ? "text-green-500" : "text-red-500", )} > - {new Intl.NumberFormat("en-US", { - style: "percent", - maximumFractionDigits: 2, - }).format(diffPercentage)} + {formatPercent(diffPercentage)}

) : null}
diff --git a/src/components/AssetSelect/AssetSelectContent.tsx b/src/components/AssetSelect/AssetSelectContent.tsx index 7e0801db..bf4a75b4 100644 --- a/src/components/AssetSelect/AssetSelectContent.tsx +++ b/src/components/AssetSelect/AssetSelectContent.tsx @@ -1,10 +1,11 @@ /* eslint-disable @next/next/no-img-element */ import { ArrowLeftIcon } from "@heroicons/react/20/solid"; -import { ethers, toBigInt } from "ethers"; +import { formatUnits, toBigInt } from "ethers"; import { FC, useEffect, useRef, useState } from "react"; import { AssetWithMetadata } from "@/context/assets"; import { useWindowSize } from "@/hooks/useWindowSize"; +import { formatMaxFraction } from "@/utils/intl"; interface Props { assets?: AssetWithMetadata[]; @@ -139,14 +140,9 @@ const AssetSelectContent: FC = ({
{balances[asset.denom] && (

- {new Intl.NumberFormat("en-US", { - maximumFractionDigits: 6, - }).format( + {formatMaxFraction( parseFloat( - ethers.formatUnits( - balances[asset.denom], - asset.decimals, - ), + formatUnits(balances[asset.denom], asset.decimals), ), )}

diff --git a/src/components/AssetValue.tsx b/src/components/AssetValue.tsx index a2ad6dd9..36fced74 100644 --- a/src/components/AssetValue.tsx +++ b/src/components/AssetValue.tsx @@ -4,6 +4,7 @@ import { useMemo } from "react"; import { ChainId } from "@/chains/types"; import { useAssets } from "@/context/assets"; import { raise } from "@/utils/assert"; +import { formatMaxFraction } from "@/utils/intl"; interface Props { chainId: ChainId; @@ -20,7 +21,7 @@ export const AssetValue = ({ chainId, denom, value }: Props) => { const formattedValue = useMemo(() => { let v = formatUnits(value, decimals); - v = format(parseFloat(v)); + v = formatMaxFraction(parseFloat(v), 2); return v; }, [decimals, value]); @@ -30,5 +31,3 @@ export const AssetValue = ({ chainId, denom, value }: Props) => { ); }; - -const { format } = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }); diff --git a/src/components/HistoryDialog/RenderDate.tsx b/src/components/HistoryDialog/RenderDate.tsx index 5a86aaad..15301976 100644 --- a/src/components/HistoryDialog/RenderDate.tsx +++ b/src/components/HistoryDialog/RenderDate.tsx @@ -1,5 +1,7 @@ import { useMemo } from "react"; +import { formatShortDate } from "@/utils/intl"; + interface Props { date: Date | string; } @@ -8,7 +10,7 @@ export const RenderDate = ({ date }: Props) => { const [left, right] = useMemo(() => { const $date = new Date(date); - const left = formatter.format($date); + const left = formatShortDate($date); const right = $date.getFullYear(); return [left, right]; @@ -20,8 +22,3 @@ export const RenderDate = ({ date }: Props) => { ); }; - -const formatter = new Intl.DateTimeFormat("en-US", { - month: "short", - day: "numeric", -}); diff --git a/src/components/SwapWidget/SwapDetails.tsx b/src/components/SwapWidget/SwapDetails.tsx index 44b04ef1..90e05fbe 100644 --- a/src/components/SwapWidget/SwapDetails.tsx +++ b/src/components/SwapWidget/SwapDetails.tsx @@ -6,6 +6,7 @@ import { Fragment, useEffect, useMemo } from "react"; import { disclosure, useDisclosureKey } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; +import { formatMaxFraction, formatPercent } from "@/utils/intl"; import { ConversionRate } from "../ConversionRate"; import { SimpleTooltip } from "../SimpleTooltip"; @@ -76,7 +77,7 @@ export const SwapDetails = ({ {({ left, right, conversion, toggle }) => (
- {new Intl.NumberFormat("en-US", { - style: "percent", - maximumFractionDigits: 2, - }).format(priceImpactPercent)} + {formatPercent(priceImpactPercent)} ) : null} @@ -157,14 +155,10 @@ export const SwapDetails = ({
{slippage}%
Bridging Fee
- {format(bridgingFee)} {isEvm ? "ETH" : ""} + {formatMaxFraction(bridgingFee)} {isEvm ? "ETH" : ""}
); }; - -const { format } = new Intl.NumberFormat("en-US", { - maximumFractionDigits: 8, -}); diff --git a/src/components/SwapWidget/useSwapWidget.ts b/src/components/SwapWidget/useSwapWidget.ts index 55666926..bad4a607 100644 --- a/src/components/SwapWidget/useSwapWidget.ts +++ b/src/components/SwapWidget/useSwapWidget.ts @@ -9,6 +9,7 @@ import { AssetWithMetadata, useAssets } from "@/context/assets"; import { useAccount } from "@/hooks/useAccount"; import { Chain, useChains } from "@/hooks/useChains"; import { useRoute } from "@/solve"; +import { formatPercent, formatUSD } from "@/utils/intl"; import { useBalancesByChain } from "@/utils/utils"; export const LAST_SOURCE_CHAIN_KEY = "IBC_DOT_FUN_LAST_SOURCE_CHAIN"; @@ -217,20 +218,15 @@ export function useSwapWidget() { } if (usdDiffPercent && Math.abs(usdDiffPercent) > PRICE_IMPACT_THRESHOLD) { - const amountInUSD = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(parseFloat(routeResponse.usdAmountIn ?? "0")); - - const amountOutUSD = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(parseFloat(routeResponse.usdAmountOut ?? "0")); - - const formattedUsdDiffPercent = new Intl.NumberFormat("en-US", { - style: "percent", - maximumFractionDigits: 2, - }).format(Math.abs(usdDiffPercent)); + const amountInUSD = formatUSD( + parseFloat(routeResponse.usdAmountIn ?? "0"), + ); + + const amountOutUSD = formatUSD( + parseFloat(routeResponse.usdAmountOut ?? "0"), + ); + + const formattedUsdDiffPercent = formatPercent(Math.abs(usdDiffPercent)); return [ "Bad Trade Warning", `Your estimated output value (${amountOutUSD}) is ${formattedUsdDiffPercent} lower than your estimated input value (${amountInUSD}).`, @@ -241,10 +237,7 @@ export function useSwapWidget() { swapPriceImpactPercent && swapPriceImpactPercent > PRICE_IMPACT_THRESHOLD ) { - const formattedPriceImpact = new Intl.NumberFormat("en-US", { - style: "percent", - maximumFractionDigits: 2, - }).format(swapPriceImpactPercent); + const formattedPriceImpact = formatPercent(swapPriceImpactPercent); return [ "Bad Trade Warning", `Your swap is expected to execute at a ${formattedPriceImpact} worse price than the current estimated on-chain price. It's likely there's not much liquidity available for this swap.`, diff --git a/src/components/UsdValue.tsx b/src/components/UsdValue.tsx index b184c19c..9ea2e0a4 100644 --- a/src/components/UsdValue.tsx +++ b/src/components/UsdValue.tsx @@ -2,12 +2,7 @@ import { createContext, ReactNode, useContext, useEffect, useRef } from "react"; import { create } from "zustand"; import { Args, useUsdDiffValue, useUsdValue } from "@/hooks/useUsdValue"; - -const { format } = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - maximumFractionDigits: 6, -}); +import { formatUSD } from "@/utils/intl"; type UsdValueProps = Args & { error?: ReactNode; @@ -45,10 +40,10 @@ export const UsdValue = ({ } if (isLoading && prevValue.current) { - return <>{format(prevValue.current)}; + return <>{formatUSD(prevValue.current)}; } - return <>{isLoading ? loading : format(usdValue)}; + return <>{isLoading ? loading : formatUSD(usdValue)}; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/src/utils/intl.ts b/src/utils/intl.ts new file mode 100644 index 00000000..7d1fab74 --- /dev/null +++ b/src/utils/intl.ts @@ -0,0 +1,40 @@ +export function formatMaxFraction(amount: string | number, fraction = 6) { + const amountNumber = typeof amount === "string" ? parseFloat(amount) : amount; + const { format } = new Intl.NumberFormat("en-US", { + maximumFractionDigits: fraction, + }); + return format(amountNumber); +} + +export function formatPercent(amount: string | number) { + const amountNumber = typeof amount === "string" ? parseFloat(amount) : amount; + return percentFormatter.format(amountNumber); +} + +export function formatShortDate(date: string | Date) { + const dateObject = typeof date === "string" ? new Date(date) : date; + return shortDateFormatter.format(dateObject); +} + +export function formatUSD(amount: string | number) { + const amountNumber = typeof amount === "string" ? parseFloat(amount) : amount; + return usdFormatter.format(amountNumber); +} + +/////////////////////////////////////////////////////////////////////////////// + +const percentFormatter = new Intl.NumberFormat("en-US", { + style: "percent", + maximumFractionDigits: 2, +}); + +const shortDateFormatter = new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", +}); + +const usdFormatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: 2, +}); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index a822ff0d..a25b9cc7 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -418,17 +418,3 @@ async function getEvmChainBalances( {} as Record, ); } - -export const wait = (ms: number) => - new Promise((resolve) => setTimeout(resolve, ms)); - -const { format } = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - maximumFractionDigits: 6, -}); - -export function formatUSD(amount: string | number) { - const amountNumber = typeof amount === "string" ? parseFloat(amount) : amount; - return format(amountNumber); -} From 21eb75aac6261c93bf06776309ac3cff62f838ac Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 02:21:41 +0700 Subject: [PATCH 025/115] refactor: split up utils utils Signed-off-by: Griko Nibras --- src/components/AssetInput.tsx | 2 +- src/components/SwapWidget/useSwapWidget.ts | 2 +- .../TransactionDialogContent.tsx | 60 +-- src/hooks/useBalancesByChain.ts | 126 ++++++ src/hooks/useGetChainWalletClient.ts | 28 ++ src/hooks/useUsdValue.ts | 2 +- src/pages/index.tsx | 2 +- src/solve/context.tsx | 18 +- src/utils/chain.ts | 62 +++ src/utils/clients.ts | 50 +++ src/utils/explorer.ts | 44 ++ src/utils/signer.ts | 15 + src/utils/{llama.ts => usd.ts} | 0 src/utils/utils.ts | 420 ------------------ 14 files changed, 344 insertions(+), 487 deletions(-) create mode 100644 src/hooks/useBalancesByChain.ts create mode 100644 src/hooks/useGetChainWalletClient.ts create mode 100644 src/utils/chain.ts create mode 100644 src/utils/clients.ts create mode 100644 src/utils/explorer.ts create mode 100644 src/utils/signer.ts rename src/utils/{llama.ts => usd.ts} (100%) delete mode 100644 src/utils/utils.ts diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index ab0e42d8..3c59a153 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -9,9 +9,9 @@ import { AssetWithMetadata, useAssets } from "@/context/assets"; import { disclosure } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; import { useAccount } from "@/hooks/useAccount"; +import { useBalancesByChain } from "@/hooks/useBalancesByChain"; import { Chain } from "@/hooks/useChains"; import { formatMaxFraction, formatPercent, formatUSD } from "@/utils/intl"; -import { useBalancesByChain } from "@/utils/utils"; import AssetSelect from "./AssetSelect"; import ChainSelect from "./ChainSelect"; diff --git a/src/components/SwapWidget/useSwapWidget.ts b/src/components/SwapWidget/useSwapWidget.ts index bad4a607..bfd8df66 100644 --- a/src/components/SwapWidget/useSwapWidget.ts +++ b/src/components/SwapWidget/useSwapWidget.ts @@ -7,10 +7,10 @@ import { createWithEqualityFn as create } from "zustand/traditional"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { useAccount } from "@/hooks/useAccount"; +import { useBalancesByChain } from "@/hooks/useBalancesByChain"; import { Chain, useChains } from "@/hooks/useChains"; import { useRoute } from "@/solve"; import { formatPercent, formatUSD } from "@/utils/intl"; -import { useBalancesByChain } from "@/utils/utils"; export const LAST_SOURCE_CHAIN_KEY = "IBC_DOT_FUN_LAST_SOURCE_CHAIN"; diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index b01456f3..f5df942f 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -1,4 +1,3 @@ -import { useManager } from "@cosmos-kit/react"; import { ArrowLeftIcon, CheckCircleIcon } from "@heroicons/react/20/solid"; import { RouteResponse } from "@skip-router/core"; import { clsx } from "clsx"; @@ -16,15 +15,11 @@ import { } from "@/context/tx-history"; import { useChains } from "@/hooks/useChains"; import { useFinalityTimeEstimate } from "@/hooks/useFinalityTimeEstimate"; +import { useGetChainWalletClient } from "@/hooks/useGetChainWalletClient"; import { useSkipClient } from "@/solve"; -import { - enableChains, - getAddressForCosmosChain, - getExplorerLinkForTx, - getOfflineSigner, - getOfflineSignerOnlyAmino, - isLedger, -} from "@/utils/utils"; +import { enableChains, getAddressForCosmosChain } from "@/utils/chain"; +import { getChainExplorerUrl } from "@/utils/explorer"; +import { getOfflineSigner } from "@/utils/signer"; import RouteDisplay from "../RouteDisplay"; import TransactionSuccessView from "../TransactionSuccessView"; @@ -32,8 +27,8 @@ import * as AlertCollapse from "./AlertCollapse"; export interface RouteTransaction { status: "INIT" | "PENDING" | "SUCCESS"; - explorerLink: string | null; - txHash: string | null; + explorerLink: string | null | undefined; + txHash: string | null | undefined; } interface Props { @@ -65,8 +60,6 @@ const TransactionDialogContent: FC = ({ const [numberOfBroadcastedTransactions, setNumberOfBroadcastedTransactions] = useState(0); - const { getWalletRepo } = useManager(); - const [txStatuses, setTxStatuses] = useState(() => Array.from({ length: transactionCount }, () => { return { @@ -77,24 +70,7 @@ const TransactionDialogContent: FC = ({ }), ); - async function getCosmosKitWalletClient(chain: Chain) { - const walletRepo = await getWalletRepo(chain.record?.chain_name ?? ""); - - const currentCosmosKitWallet = localStorage.getItem( - "cosmos-kit@2:core//current-wallet", - ); - - if (!currentCosmosKitWallet) { - throw new Error("No CosmosKit wallet found"); - } - - const wallet = walletRepo.getWallet(currentCosmosKitWallet); - if (!wallet) { - throw new Error("No wallet found"); - } - - return wallet.client; - } + const getChainWalletClient = useGetChainWalletClient(); const onSubmit = async () => { setTransacting(true); @@ -103,7 +79,6 @@ const TransactionDialogContent: FC = ({ try { const userAddresses: Record = {}; - const addressList = []; // get addresses for (const chainID of route.chainIDs) { @@ -113,11 +88,10 @@ const TransactionDialogContent: FC = ({ } if (chain.chainType === "cosmos") { - const walletClient = await getCosmosKitWalletClient(chain); + const walletClient = getChainWalletClient(chain.chainName ?? ""); await enableChains(walletClient, [chainID]); const address = await getAddressForCosmosChain(walletClient, chainID); userAddresses[chainID] = address; - addressList.push(address); } if (chain.chainType === "evm") { @@ -126,7 +100,6 @@ const TransactionDialogContent: FC = ({ } userAddresses[chainID] = evmAddress; - addressList.push(evmAddress); } } @@ -150,21 +123,13 @@ const TransactionDialogContent: FC = ({ throw new Error(`No chain found for chainID ${chainID}`); } - const walletClient = await getCosmosKitWalletClient(chain); - - const signerIsLedger = await isLedger(walletClient, chainID); - - if (signerIsLedger) { - return getOfflineSignerOnlyAmino(walletClient, chainID); - } + const walletClient = getChainWalletClient(chain.chainName ?? ""); return getOfflineSigner(walletClient, chainID); }, onTransactionBroadcast: async (txStatus) => { - const explorerLink = getExplorerLinkForTx( - txStatus.chainID, - txStatus.txHash, - ); + const makeExplorerUrl = await getChainExplorerUrl(txStatus.chainID); + const explorerLink = makeExplorerUrl?.(txStatus.txHash); addTxStatus(historyId, { chainId: txStatus.chainID, @@ -178,7 +143,8 @@ const TransactionDialogContent: FC = ({ ); }, onTransactionCompleted: async (chainID, txHash) => { - const explorerLink = getExplorerLinkForTx(chainID, txHash); + const makeExplorerUrl = await getChainExplorerUrl(chainID); + const explorerLink = makeExplorerUrl?.(txHash); setTxStatuses((statuses) => { const newStatuses = [...statuses]; diff --git a/src/hooks/useBalancesByChain.ts b/src/hooks/useBalancesByChain.ts new file mode 100644 index 00000000..8a1bbd4d --- /dev/null +++ b/src/hooks/useBalancesByChain.ts @@ -0,0 +1,126 @@ +import { SkipRouter } from "@skip-router/core"; +import { useQuery } from "@tanstack/react-query"; +import { erc20ABI, PublicClient, usePublicClient } from "wagmi"; + +import { ChainId } from "@/chains/types"; +import { multicall3ABI } from "@/constants/abis"; +import { AssetWithMetadata } from "@/context/assets"; +import { Chain } from "@/hooks/useChains"; +import { useSkipClient } from "@/solve"; +import { + getCosmWasmClientForChainID, + getStargateClientForChainID, +} from "@/utils/clients"; + +export function useBalancesByChain( + address?: string, + chain?: Chain, + assets?: AssetWithMetadata[], + enabled: boolean = true, +) { + const publicClient = usePublicClient({ + chainId: chain?.chainType === "evm" ? parseInt(chain.chainID) : undefined, + }); + + const skipClient = useSkipClient(); + + return useQuery({ + queryKey: ["balances-by-chain", address, chain, assets], + queryFn: async () => { + if (!chain || !address) { + return {}; + } + + if (chain.chainType === "evm") { + return getEvmChainBalances( + skipClient, + publicClient, + address, + chain.chainID, + ); + } + + return getBalancesByChain(address, chain.chainID, assets ?? []); + }, + refetchInterval: 1000 * 5, + enabled: !!chain && !!address && enabled, + }); +} + +export async function getBalancesByChain( + address: string, + chainID: ChainId, + assets: AssetWithMetadata[], +) { + const client = await getStargateClientForChainID(chainID); + const cosmwasmClient = await getCosmWasmClientForChainID(chainID); + + const balances = await client.getAllBalances(address); + + const cw20Assets = assets.filter((asset) => asset.isCW20); + + const cw20Balances = await Promise.all( + cw20Assets.map((asset) => + cosmwasmClient.queryContractSmart(asset.tokenContract!, { + balance: { address }, + }), + ), + ); + + const allBalances = balances.reduce>( + (acc, balance) => ({ ...acc, [balance.denom]: balance.amount }), + {}, + ); + + cw20Balances.forEach((balance, index) => { + const asset = cw20Assets[index]; + if (balance.balance !== "0") { + allBalances[asset.denom] = balance.balance; + } + }); + + return allBalances; +} + +export async function getEvmChainBalances( + skipClient: SkipRouter, + publicClient: PublicClient, + address: string, + chainID: ChainId, +) { + const assets = await skipClient.assets({ + chainID, + includeEvmAssets: true, + }); + + const chainAssets = assets[chainID]; + + const balances = await publicClient.multicall({ + multicallAddress: "0xcA11bde05977b3631167028862bE2a173976CA11", + contracts: chainAssets.map((asset) => { + if (!asset.tokenContract) { + return { + address: "0xcA11bde05977b3631167028862bE2a173976CA11", + abi: multicall3ABI, + functionName: "getEthBalance", + args: [address as `0x${string}`], + }; + } + + return { + address: asset.tokenContract as `0x${string}`, + abi: erc20ABI, + functionName: "balanceOf", + args: [address as `0x${string}`], + }; + }), + }); + + return chainAssets.reduce>( + (acc, asset, i) => ({ + ...acc, + [asset.denom]: balances[i].result?.toString() || "0", + }), + {}, + ); +} diff --git a/src/hooks/useGetChainWalletClient.ts b/src/hooks/useGetChainWalletClient.ts new file mode 100644 index 00000000..573dc4e4 --- /dev/null +++ b/src/hooks/useGetChainWalletClient.ts @@ -0,0 +1,28 @@ +import { useManager } from "@cosmos-kit/react"; +import { useCallback } from "react"; + +export function useGetChainWalletClient() { + const { getWalletRepo } = useManager(); + + const getter = useCallback( + (chainName: string) => { + const repo = getWalletRepo(chainName); + + const currentWallet = localStorage.getItem( + "cosmos-kit@2:core//current-wallet", + ); + if (!currentWallet) { + throw new Error("No CosmosKit wallet found"); + } + + const wallet = repo.getWallet(currentWallet); + if (!wallet) { + throw new Error("No wallet found"); + } + return wallet.client; + }, + [getWalletRepo], + ); + + return getter; +} diff --git a/src/hooks/useUsdValue.ts b/src/hooks/useUsdValue.ts index 58cd036b..a2aa32f0 100644 --- a/src/hooks/useUsdValue.ts +++ b/src/hooks/useUsdValue.ts @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { getAssets } from "@/chains"; import { raise } from "@/utils/assert"; -import { getUsdPrice } from "@/utils/llama"; +import { getUsdPrice } from "@/utils/usd"; export type Args = { chainId: string; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c20e9627..d30d46e3 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -13,10 +13,10 @@ import { updateTxStatus, useTxHistory, } from "@/context/tx-history"; +import { getBalancesByChain } from "@/hooks/useBalancesByChain"; import { useInterval } from "@/hooks/useInterval"; import { queryClient } from "@/lib/react-query"; import { useSkipClient } from "@/solve"; -import { getBalancesByChain } from "@/utils/utils"; function routeHasAxelarTransfer(route: RouteResponse): boolean { for (const operation of route.operations) { diff --git a/src/solve/context.tsx b/src/solve/context.tsx index db5ff8f8..d1401bb0 100644 --- a/src/solve/context.tsx +++ b/src/solve/context.tsx @@ -6,17 +6,10 @@ import { useNetwork } from "wagmi"; import { API_URL } from "@/constants/api"; import { getNodeProxyEndpoint } from "@/utils/api"; -import { - getOfflineSigner, - getOfflineSignerOnlyAmino, - isLedger, -} from "@/utils/utils"; +import { getOfflineSigner } from "@/utils/signer"; export const SkipContext = createContext< - | { - skipClient: SkipRouter; - } - | undefined + { skipClient: SkipRouter } | undefined >(undefined); export const SkipProvider: FC = ({ children }) => { @@ -29,13 +22,6 @@ export const SkipProvider: FC = ({ children }) => { if (!walletClient) { throw new Error("No offline signer available"); } - - const signerIsLedger = await isLedger(walletClient, chainID); - - if (signerIsLedger) { - return getOfflineSignerOnlyAmino(walletClient, chainID); - } - return getOfflineSigner(walletClient, chainID); }, getEVMSigner: async (chainID) => { diff --git a/src/utils/chain.ts b/src/utils/chain.ts new file mode 100644 index 00000000..4375b11d --- /dev/null +++ b/src/utils/chain.ts @@ -0,0 +1,62 @@ +import { ChainId } from "@/chains/types"; +import { MergedWalletClient } from "@/lib/cosmos-kit"; + +export async function getAddressForCosmosChain( + walletClient: T, + chainId: ChainId, +) { + if ("getOfflineSigner" in walletClient && walletClient.getOfflineSigner) { + const signer = await walletClient.getOfflineSigner(chainId); + const accounts = await signer.getAccounts(); + return accounts[0].address; + } + + if ("getAccount" in walletClient && walletClient.getAccount) { + const account = await walletClient.getAccount(chainId); + return account.address; + } + + if ("getSimpleAccount" in walletClient) { + const account = await walletClient.getSimpleAccount(chainId); + return account.address; + } + + throw new Error( + `unsupported wallet: current wallet client does not have methods to resolve address`, + ); +} + +// generic wrapper to support enabling chains on many different wallets +export async function enableChains( + walletClient: T, + chains: string[], +) { + // KeplrClient | LeapClient | OkxwalletClient | VectisClient | XDEFIClient + if ("enable" in walletClient && walletClient.enable) { + return walletClient.enable(chains); + } + + // CosmostationClient + if ("ikeplr" in walletClient) { + return walletClient.ikeplr.enable(chains); + } + + // CosmostationClient | KeplrClient | LeapClient + if ("connect" in walletClient && walletClient.connect) { + return walletClient.connect(chains); + } + + // CosmosSnapClient + if ("handleConnect" in walletClient) { + return walletClient.handleConnect(); + } + + // KeplrClient | LeapClient | OkxwalletClient | StationClient | VectisClient | XDEFIClient + if ("client" in walletClient && "keplr" in walletClient.client) { + return walletClient.client.keplr.enable(chains); + } + + throw new Error( + `unsupported wallet: current wallet client does not have methods to enable chains`, + ); +} diff --git a/src/utils/clients.ts b/src/utils/clients.ts new file mode 100644 index 00000000..0398fb06 --- /dev/null +++ b/src/utils/clients.ts @@ -0,0 +1,50 @@ +import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { StargateClient } from "@cosmjs/stargate"; + +import { ChainId, chainIdToName } from "@/chains/types"; + +import { getNodeProxyEndpoint } from "./api"; + +const STARGATE_CLIENTS: Record = {}; + +export async function getStargateClientForChainID(chainID: ChainId) { + if (STARGATE_CLIENTS[chainID]) { + return STARGATE_CLIENTS[chainID]; + } + + const chainName = chainIdToName[chainID]; + + if (!chainName) { + throw new Error(`stargateClient error: chain with ID ${chainID} not found`); + } + + const preferredEndpoint = getNodeProxyEndpoint(chainID); + + const client = await StargateClient.connect(preferredEndpoint, {}); + + STARGATE_CLIENTS[chainID] = client; + + return client; +} + +const COSMWASM_CLIENTS: Record = {}; + +export async function getCosmWasmClientForChainID(chainID: ChainId) { + if (COSMWASM_CLIENTS[chainID]) { + return COSMWASM_CLIENTS[chainID]; + } + + const chainName = chainIdToName[chainID]; + + if (!chainName) { + throw new Error(`cosmWasmClient error: chain with ID ${chainID} not found`); + } + + const preferredEndpoint = getNodeProxyEndpoint(chainID); + + const client = await CosmWasmClient.connect(preferredEndpoint); + + COSMWASM_CLIENTS[chainID] = client; + + return client; +} diff --git a/src/utils/explorer.ts b/src/utils/explorer.ts new file mode 100644 index 00000000..4129d258 --- /dev/null +++ b/src/utils/explorer.ts @@ -0,0 +1,44 @@ +import { explorersRecord } from "@/chains/explorers"; + +const explorerCache = new Map string>(); +export async function getChainExplorerUrl(chainId: string) { + const cached = explorerCache.get(chainId); + if (cached) return cached; + + const parsedIntId = parseInt(chainId); + const isEvmChain = typeof parsedIntId === "number" && !isNaN(parsedIntId); + + if (isEvmChain) { + const { EVM_CHAINS } = await import("@/constants/wagmi"); + const chain = EVM_CHAINS.find((chain) => chain.id === parseInt(chainId)); + if (chain?.blockExplorers?.default.url) { + const explorer = (txHash: string) => { + return `${chain.blockExplorers!.default.url}/tx/${txHash}`; + }; + explorerCache.set(chainId, explorer); + return explorer; + } + } + + const explorers = explorersRecord[chainId] || []; + + const mintscan = explorers.find((explorer) => explorer.kind === "mintscan"); + if (mintscan && mintscan.tx_page) { + const explorer = (txHash: string) => { + return mintscan.tx_page!.replace("${txHash}", txHash); + }; + explorerCache.set(chainId, explorer); + return explorer; + } + + if (explorers[0]?.tx_page) { + // return explorers[0].tx_page.replace("${txHash}", txHash); + const explorer = (txHash: string) => { + return explorers[0].tx_page!.replace("${txHash}", txHash); + }; + explorerCache.set(chainId, explorer); + return explorer; + } + + return null; +} diff --git a/src/utils/signer.ts b/src/utils/signer.ts new file mode 100644 index 00000000..05d5e154 --- /dev/null +++ b/src/utils/signer.ts @@ -0,0 +1,15 @@ +import { ChainId } from "@/chains/types"; +import { MergedWalletClient } from "@/lib/cosmos-kit"; + +export async function getOfflineSigner( + walletClient: T, + chainId: ChainId, +) { + if ("getOfflineSigner" in walletClient && walletClient.getOfflineSigner) { + return walletClient.getOfflineSigner(chainId); + } + + throw new Error( + `unsupported wallet: current wallet client does not have 'getOfflineSigner'`, + ); +} diff --git a/src/utils/llama.ts b/src/utils/usd.ts similarity index 100% rename from src/utils/llama.ts rename to src/utils/usd.ts diff --git a/src/utils/utils.ts b/src/utils/utils.ts deleted file mode 100644 index a25b9cc7..00000000 --- a/src/utils/utils.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { OfflineAminoSigner } from "@cosmjs/amino"; -import { - CosmWasmClient, - SigningCosmWasmClient, - SigningCosmWasmClientOptions, -} from "@cosmjs/cosmwasm-stargate"; -import { OfflineDirectSigner, OfflineSigner } from "@cosmjs/proto-signing"; -import { - SigningStargateClient, - SigningStargateClientOptions, - StargateClient, -} from "@cosmjs/stargate"; -import { SkipRouter } from "@skip-router/core"; -import { useQuery } from "@tanstack/react-query"; -import { erc20ABI, PublicClient, usePublicClient } from "wagmi"; - -import { ChainId } from "@/chains/types"; -import { multicall3ABI } from "@/constants/abis"; -import { EVM_CHAINS } from "@/constants/wagmi"; -import { AssetWithMetadata } from "@/context/assets"; -import { Chain } from "@/hooks/useChains"; -import { MergedWalletClient } from "@/lib/cosmos-kit"; -import { useSkipClient } from "@/solve"; - -import { getNodeProxyEndpoint } from "./api"; - -// cache clients to reuse later -const STARGATE_CLIENTS: Record = {}; - -export async function getStargateClientForChainID(chainID: ChainId) { - if (STARGATE_CLIENTS[chainID]) { - return STARGATE_CLIENTS[chainID]; - } - - const chain = getChainByID(chainID); - - if (!chain) { - throw new Error(`stargateClient error: chain with ID ${chainID} not found`); - } - - const preferredEndpoint = getNodeProxyEndpoint(chainID); - - const client = await StargateClient.connect(preferredEndpoint, {}); - - STARGATE_CLIENTS[chainID] = client; - - return client; -} - -const COSMWASM_CLIENTS: Record = {}; - -export async function getCosmWasmClientForChainID(chainID: ChainId) { - if (COSMWASM_CLIENTS[chainID]) { - return COSMWASM_CLIENTS[chainID]; - } - - const chain = getChainByID(chainID); - - if (!chain) { - throw new Error(`cosmWasmClient error: chain with ID ${chainID} not found`); - } - - const preferredEndpoint = getNodeProxyEndpoint(chainID); - - const client = await CosmWasmClient.connect(preferredEndpoint); - - COSMWASM_CLIENTS[chainID] = client; - - return client; -} - -export async function getSigningStargateClientForChainID( - chainID: ChainId, - signer: OfflineSigner, - options?: SigningStargateClientOptions, -) { - const chain = getChainByID(chainID); - - if (!chain) { - throw new Error( - `signingStargateClient error: chain with ID ${chainID} not found`, - ); - } - - const preferredEndpoint = getNodeProxyEndpoint(chainID); - - const client = await SigningStargateClient.connectWithSigner( - preferredEndpoint, - signer, - options, - ); - - console.info(`signingStargateClient: Connected to ${preferredEndpoint}`); - - return client; -} - -export async function getAddressForCosmosChain( - walletClient: T, - chainId: ChainId, -) { - if ("getOfflineSigner" in walletClient && walletClient.getOfflineSigner) { - const signer = await walletClient.getOfflineSigner(chainId); - const accounts = await signer.getAccounts(); - return accounts[0].address; - } - - if ("getAccount" in walletClient && walletClient.getAccount) { - const account = await walletClient.getAccount(chainId); - return account.address; - } - - if ("getSimpleAccount" in walletClient) { - const account = await walletClient.getSimpleAccount(chainId); - return account.address; - } - - throw new Error( - `unsupported wallet: current wallet client does not have 'getOfflineSigner' or 'getSimpleAccount'`, - ); -} - -export async function getSigningCosmWasmClientForChainID( - chainID: ChainId, - signer: OfflineSigner, - options?: SigningCosmWasmClientOptions, -) { - if (!getChainByID(chainID)) { - throw new Error( - `signingCosmWasmClient error: chain with ID ${chainID} not found`, - ); - } - - const preferredEndpoint = getNodeProxyEndpoint(chainID); - - const client = await SigningCosmWasmClient.connectWithSigner( - preferredEndpoint, - signer, - options, - ); - - return client; -} - -// generic wrapper to support enabling chains on many different wallets -export async function enableChains( - walletClient: T, - chains: string[], -) { - // mostly everything else - if ("enable" in walletClient && walletClient.enable) { - return walletClient.enable(chains); - } - - // cosmostation - if ("ikeplr" in walletClient) { - return walletClient.ikeplr.enable(chains); - } - - // metamask snaps - if ("snapInstalled" in walletClient) { - return; - } - - // station - if ("client" in walletClient && "keplr" in walletClient.client) { - return walletClient.client.keplr.enable(chains); - } - - throw new Error( - `unsupported wallet: current wallet client does not have methods to enable chains`, - ); -} - -export async function getAccount( - walletClient: T, - chainId: ChainId, -) { - if (walletClient.getAccount) { - return walletClient.getAccount(chainId); - } - - throw new Error( - `unsupported wallet: current wallet client does not have 'getAccount'`, - ); -} - -export async function getOfflineSigner( - walletClient: T, - chainId: ChainId, -): Promise { - if ( - "getOfflineSignerDirect" in walletClient && - walletClient.getOfflineSignerDirect - ) { - return walletClient.getOfflineSignerDirect(chainId); - } - - if ("getOfflineSigner" in walletClient && walletClient.getOfflineSigner) { - const signer = await walletClient.getOfflineSigner(chainId, "direct"); - if ("signDirect" in signer) return signer; - } - - throw new Error( - `unsupported wallet: current wallet client does not have 'getOfflineSigner' or 'getOfflineSignerDirect'`, - ); -} - -export async function getOfflineSignerOnlyAmino( - walletClient: T, - chainId: ChainId, -): Promise { - if ( - "getOfflineSignerAmino" in walletClient && - walletClient.getOfflineSignerAmino - ) { - return walletClient.getOfflineSignerAmino(chainId); - } - - if ("getOfflineSigner" in walletClient && walletClient.getOfflineSigner) { - const signer = await walletClient.getOfflineSigner(chainId, "amino"); - if ("signAmino" in signer) return signer; - } - - throw new Error( - `unsupported wallet: current wallet client does not have 'getOfflineSigner' or 'getOfflineSignerAmino'`, - ); -} - -export function getFee(chainID: ChainId) { - const chain = getChainByID(chainID); - - const feeInfo = chain.fees?.fee_tokens[0]; - - if (!feeInfo) { - throw new Error("No fee info found"); - } - - let averageGasPrice = 0; - if (feeInfo.average_gas_price) { - averageGasPrice = feeInfo.average_gas_price; - } - - const amountNeeded = averageGasPrice * 1000000; - - return amountNeeded; -} - -export async function isLedger( - walletClient: T, - chainID: ChainId, -) { - // mostly everything else - if ("client" in walletClient && "getKey" in walletClient.client) { - const key = await walletClient.client.getKey(chainID); - return key.isNanoLedger; - } - - // cosmostation - if ("client" in walletClient && "cosmos" in walletClient.client) { - const account = await walletClient.client.cosmos.request({ - method: "cos_account", - params: { chainName: chainID }, - }); - return Boolean(account.isLedger); - } - - return false; -} - -export function getExplorerLinkForTx(chainID: ChainId, txHash: string) { - const evmChain = EVM_CHAINS.find((c) => c.id === parseInt(chainID)); - - if (evmChain?.blockExplorers) { - return `${evmChain.blockExplorers.default.url}/tx/${txHash}`; - } - - const chain = getChainByID(chainID); - - if (!chain) { - return null; - } - - if (!chain.explorers) { - return null; - } - - const mintscan = chain.explorers.find( - (explorer) => explorer.kind === "mintscan", - ); - - if (mintscan && mintscan.tx_page) { - return mintscan.tx_page.replace("${txHash}", txHash); - } - - return chain.explorers[0].tx_page?.replace("${txHash}", txHash) ?? null; -} - -export async function getBalancesByChain( - address: string, - chainID: ChainId, - assets: AssetWithMetadata[], -) { - const client = await getStargateClientForChainID(chainID); - const cosmwasmClient = await getCosmWasmClientForChainID(chainID); - - const balances = await client.getAllBalances(address); - - const cw20Assets = assets.filter((asset) => asset.isCW20); - - const cw20Balances = await Promise.all( - cw20Assets.map((asset) => { - return cosmwasmClient.queryContractSmart(asset.tokenContract as string, { - balance: { - address, - }, - }); - }), - ); - - const allBalances = balances.reduce( - (acc, balance) => { - return { - ...acc, - [balance.denom]: balance.amount, - }; - }, - {} as Record, - ); - - cw20Balances.forEach((balance, index) => { - const asset = cw20Assets[index]; - - if (balance.balance !== "0") { - allBalances[asset.denom] = balance.balance; - } - }); - - return allBalances; -} - -export function useBalancesByChain( - address?: string, - chain?: Chain, - assets?: AssetWithMetadata[], - enabled: boolean = true, -) { - const publicClient = usePublicClient({ - chainId: chain?.chainType === "evm" ? parseInt(chain.chainID) : undefined, - }); - - const skipClient = useSkipClient(); - - return useQuery({ - queryKey: ["balances-by-chain", address, chain, assets], - queryFn: async () => { - if (!chain || !address) { - return {}; - } - - if (chain.chainType === "evm") { - return getEvmChainBalances( - skipClient, - publicClient, - address, - chain.chainID, - ); - } - - return getBalancesByChain(address, chain.chainID, assets ?? []); - }, - refetchInterval: 1000 * 5, - enabled: !!chain && !!address && enabled, - }); -} - -async function getEvmChainBalances( - skipClient: SkipRouter, - publicClient: PublicClient, - address: string, - chainID: ChainId, -) { - const assets = await skipClient.assets({ - chainID, - includeEvmAssets: true, - }); - - const chainAssets = assets[chainID]; - - const balances = await publicClient.multicall({ - multicallAddress: "0xcA11bde05977b3631167028862bE2a173976CA11", - contracts: chainAssets.map((asset) => { - if (!asset.tokenContract) { - return { - address: "0xcA11bde05977b3631167028862bE2a173976CA11", - abi: multicall3ABI, - functionName: "getEthBalance", - args: [address as `0x${string}`], - }; - } - - return { - address: asset.tokenContract as `0x${string}`, - abi: erc20ABI, - functionName: "balanceOf", - args: [address as `0x${string}`], - }; - }), - }); - - return chainAssets.reduce( - (acc, asset, index) => { - return { - ...acc, - [asset.denom]: balances[index].result?.toString() || "0", - }; - }, - {} as Record, - ); -} From be9ad21097e4124207b4c2c2fc6fd50b633b3125 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 02:22:04 +0700 Subject: [PATCH 026/115] feat: shift click max button for actual max Signed-off-by: Griko Nibras --- src/components/AssetInput.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index 3c59a153..8001c5cb 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -219,11 +219,16 @@ const AssetInput: FC = ({ "transition-transform enabled:hover:scale-110 enabled:hover:rotate-2 disabled:cursor-not-allowed", )} disabled={maxButtonDisabled} - onClick={() => { + onClick={(event) => { if (!selectedAssetBalance || !chain || !asset) return; let amount = new BigNumber(selectedAssetBalance); + if (event.shiftKey) { + onAmountChange?.(amount.toString()); + return; + } + const feeDenom = getFeeDenom(chain.chainID)!; const { gasPrice } = chain.feeAssets.find( (a) => a.denom === feeDenom.denom, From a6d4165cfe272fe9e09f13d591afb1945edf0c2c Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 02:23:13 +0700 Subject: [PATCH 027/115] feat: improve form values persisting Signed-off-by: Griko Nibras --- src/components/SwapWidget/useSwapWidget.ts | 86 ++++++++++++++------ src/components/__tests__/SwapWidget.test.tsx | 3 +- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/components/SwapWidget/useSwapWidget.ts b/src/components/SwapWidget/useSwapWidget.ts index bfd8df66..ef70f4a4 100644 --- a/src/components/SwapWidget/useSwapWidget.ts +++ b/src/components/SwapWidget/useSwapWidget.ts @@ -1,7 +1,12 @@ +import { BigNumber } from "bignumber.js"; import { ethers } from "ethers"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useNetwork, useSwitchNetwork } from "wagmi"; -import { subscribeWithSelector } from "zustand/middleware"; +import { + createJSONStorage, + persist, + subscribeWithSelector, +} from "zustand/middleware"; import { shallow } from "zustand/shallow"; import { createWithEqualityFn as create } from "zustand/traditional"; @@ -283,17 +288,33 @@ export interface FormValues { direction: "swap-in" | "swap-out"; } +const defaultValues: FormValues = { + amountIn: "", + amountOut: "", + direction: "swap-in", +}; + const useFormValuesStore = create( - subscribeWithSelector(() => ({ - amountIn: "", - amountOut: "", - direction: "swap-in", - })), + subscribeWithSelector( + persist(() => defaultValues, { + name: "ibc-dot-fun//form-values", + storage: createJSONStorage(() => sessionStorage), + partialize: (state): Partial => ({ + sourceChain: state.sourceChain, + sourceAsset: state.sourceAsset, + destinationChain: state.destinationChain, + destinationAsset: state.destinationAsset, + }), + skipHydration: true, + }), + ), ); // useFormValues returns a set of form values that are used to populate the swap widget // and handles logic regarding setting initial values based on local storage and other form values. function useFormValues() { + useEffect(() => void useFormValuesStore.persist.rehydrate(), []); + const { data: chains } = useChains(); const { assetsByChainID, getFeeDenom } = useAssets(); @@ -301,6 +322,28 @@ function useFormValues() { const [userSelectedDestinationAsset, setUserSelectedDestinationAsset] = useState(false); + useEffect(() => { + return useFormValuesStore.subscribe( + (state) => state.amountIn, + (current, prev) => { + if ((!current || current == "0") && prev) { + useFormValuesStore.setState({ amountOut: "" }); + } + }, + ); + }, []); + + useEffect(() => { + return useFormValuesStore.subscribe( + (state) => state.amountOut, + (current, prev) => { + if ((!current || current == "0") && prev) { + useFormValuesStore.setState({ amountIn: "" }); + } + }, + ); + }, []); + // Select initial source chain. // - If chainID exists in local storage, use that. // - Otherwise, default to cosmoshub-4. @@ -309,8 +352,7 @@ function useFormValues() { (state) => state.sourceChain, (sourceChain) => { if (!sourceChain && (chains ?? []).length > 0) { - const chainID = - localStorage.getItem(LAST_SOURCE_CHAIN_KEY) ?? "cosmoshub-4"; + const chainID = "cosmoshub-4"; useFormValuesStore.setState({ sourceChain: (chains ?? []).find( (chain) => chain.chainID === chainID, @@ -325,22 +367,6 @@ function useFormValues() { ); }, [chains]); - // When source chain changes, save to local storage. - useEffect(() => { - return useFormValuesStore.subscribe( - (state) => state.sourceChain, - (sourceChain) => { - if (sourceChain) { - localStorage.setItem(LAST_SOURCE_CHAIN_KEY, sourceChain.chainID); - } - }, - { - equalityFn: shallow, - fireImmediately: true, - }, - ); - }, []); - // Select initial source asset. // - If fee denom exists for source chain, use that. // - Otherwise, default to first asset in list. @@ -455,8 +481,11 @@ function findEquivalentAsset( function getAmountWei(asset?: AssetWithMetadata, amount?: string) { if (!asset || !amount) return "0"; try { - return ethers.parseUnits(amount, asset.decimals).toString(); - } catch { + return new BigNumber(amount).shiftedBy(asset.decimals ?? 6).toFixed(0); + } catch (err) { + if (process.env.NODE_ENV === "development") { + console.error(err); + } return "0"; } } @@ -465,7 +494,10 @@ function parseAmountWei(amount?: string, decimals = 6) { if (!amount) return "0"; try { return ethers.formatUnits(amount, decimals ?? 6); - } catch { + } catch (err) { + if (process.env.NODE_ENV === "development") { + console.error(err); + } return "0"; } } diff --git a/src/components/__tests__/SwapWidget.test.tsx b/src/components/__tests__/SwapWidget.test.tsx index 52e69c0e..c1f492dc 100644 --- a/src/components/__tests__/SwapWidget.test.tsx +++ b/src/components/__tests__/SwapWidget.test.tsx @@ -7,7 +7,6 @@ import { act, fireEvent, render, screen, waitFor, within } from "@/test"; import { ASSETS_RESPONSE } from "../../../fixtures/assets"; import { CHAINS_RESPONSE } from "../../../fixtures/chains"; import { SwapWidget } from "../SwapWidget"; -import { LAST_SOURCE_CHAIN_KEY } from "../SwapWidget/useSwapWidget"; const handlers = [ rest.get(`${API_URL}/v1/info/chains`, (_, res, ctx) => { @@ -117,7 +116,7 @@ describe.skip("SwapWidget", () => { expect(sourceChainButton).toHaveTextContent("Osmosis"); // Source chain is stored in local storage - expect(localStorage.getItem(LAST_SOURCE_CHAIN_KEY)).toEqual("osmosis-1"); + // expect(localStorage.getItem(LAST_SOURCE_CHAIN_KEY)).toEqual("osmosis-1"); // Source asset is now OSMO expect( From e4b7118a9e2575f0cb0b290b6e1483edc37c1ca4 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 03:47:59 +0700 Subject: [PATCH 028/115] feat: add match-sorter deps Signed-off-by: Griko Nibras --- package-lock.json | 15 +++++++++++++++ package.json | 1 + 2 files changed, 16 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2662a065..762b5034 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "date-fns": "^3.1.0", "download": "^8.0.0", "ethers": "^6.9.2", + "match-sorter": "^6.3.1", "next": "^14.0.4", "postcss": "^8.4.33", "react": "^18.2.0", @@ -18147,6 +18148,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -20675,6 +20685,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index 1fe98a00..5d1bd3e8 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "date-fns": "^3.1.0", "download": "^8.0.0", "ethers": "^6.9.2", + "match-sorter": "^6.3.1", "next": "^14.0.4", "postcss": "^8.4.33", "react": "^18.2.0", From 8fc48d0bd1a7989c613f9f063e06b29829fd4db1 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 03:48:34 +0700 Subject: [PATCH 029/115] feat: improve chain and asset search Signed-off-by: Griko Nibras --- .../AssetSelect/AssetSelectContent.tsx | 82 ++++++++----------- .../ChainSelect/ChainSelectContent.tsx | 19 +---- 2 files changed, 37 insertions(+), 64 deletions(-) diff --git a/src/components/AssetSelect/AssetSelectContent.tsx b/src/components/AssetSelect/AssetSelectContent.tsx index bf4a75b4..86145183 100644 --- a/src/components/AssetSelect/AssetSelectContent.tsx +++ b/src/components/AssetSelect/AssetSelectContent.tsx @@ -1,7 +1,8 @@ /* eslint-disable @next/next/no-img-element */ import { ArrowLeftIcon } from "@heroicons/react/20/solid"; import { formatUnits, toBigInt } from "ethers"; -import { FC, useEffect, useRef, useState } from "react"; +import { matchSorter } from "match-sorter"; +import { FC, useEffect, useMemo, useRef, useState } from "react"; import { AssetWithMetadata } from "@/context/assets"; import { useWindowSize } from "@/hooks/useWindowSize"; @@ -34,54 +35,39 @@ const AssetSelectContent: FC = ({ const [searchValue, setSearchValue] = useState(""); - const sortedAssets = assets - ?.sort((a, b) => { - if (!a.symbol) { - return 1; - } - - if (!b.symbol) { - return -1; - } - - if (a.symbol > b.symbol) { - return 1; - } - - if (a.symbol < b.symbol) { - return -1; - } - - return 0; - }) - .filter((asset) => { - if ( - asset.originChainID === "sifchain-1" && - asset.originDenom !== "rowan" - ) { - return false; - } - return true; - }) - .sort((a, b) => { - const balanceA = balances[a.denom] ? toBigInt(balances[a.denom]) : 0n; - const balanceB = balances[b.denom] ? toBigInt(balances[b.denom]) : 0n; - - if (balanceA > balanceB) return -1; - if (balanceA < balanceB) return 1; - - return 0; + const sortedAssets = useMemo(() => { + return assets + ?.sort((a, b) => { + if (!a.symbol) return 1; + if (!b.symbol) return -1; + if (a.symbol > b.symbol) return 1; + if (a.symbol < b.symbol) return -1; + return 0; + }) + .filter((asset) => { + if ( + asset.originChainID === "sifchain-1" && + asset.originDenom !== "rowan" + ) { + return false; + } + return true; + }) + .sort((a, b) => { + const balanceA = balances[a.denom] ? toBigInt(balances[a.denom]) : 0n; + const balanceB = balances[b.denom] ? toBigInt(balances[b.denom]) : 0n; + if (balanceA > balanceB) return -1; + if (balanceA < balanceB) return 1; + return 0; + }); + }, [assets, balances]); + + const filteredAssets = useMemo(() => { + if (!searchValue) return sortedAssets; + return matchSorter(sortedAssets || [], searchValue, { + keys: ["symbol", "denom"], }); - - const filteredAssets = sortedAssets?.filter((asset) => { - if (!searchValue) return true; - - if (asset.symbol?.toLowerCase().includes(searchValue.toLowerCase())) { - return true; - } - - return asset.denom.toLowerCase().includes(searchValue.toLowerCase()); - }); + }, [searchValue, sortedAssets]); return (
diff --git a/src/components/ChainSelect/ChainSelectContent.tsx b/src/components/ChainSelect/ChainSelectContent.tsx index 1efab85c..9880df9f 100644 --- a/src/components/ChainSelect/ChainSelectContent.tsx +++ b/src/components/ChainSelect/ChainSelectContent.tsx @@ -1,5 +1,6 @@ /* eslint-disable @next/next/no-img-element */ import { ArrowLeftIcon } from "@heroicons/react/20/solid"; +import { matchSorter } from "match-sorter"; import { FC, useEffect, useMemo, useRef, useState } from "react"; import { Chain } from "@/hooks/useChains"; @@ -27,22 +28,8 @@ const ChainSelectContent: FC = ({ chains, onChange, onClose }) => { const filteredChains = useMemo(() => { if (!searchValue) return chains; - return chains.filter((chain) => { - if ( - chain.chainID && - chain.chainID.toLowerCase().includes(searchValue.toLowerCase()) - ) { - return true; - } - - if ( - chain.chainName && - chain.chainName.toLowerCase().includes(searchValue.toLowerCase()) - ) { - return true; - } - - return chain.chainName.toLowerCase().includes(searchValue.toLowerCase()); + return matchSorter(chains, searchValue, { + keys: ["chainID", "chainName"], }); }, [chains, searchValue]); From 9fe5330423631006100f4c183334f86636fc50f3 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 04:26:23 +0700 Subject: [PATCH 030/115] feat: add react-hot-toast deps --- package-lock.json | 24 ++++++++++++++++++++++++ package.json | 1 + 2 files changed, 25 insertions(+) diff --git a/package-lock.json b/package-lock.json index 762b5034..62b5288f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "postcss": "^8.4.33", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "tailwindcss": "^3.4.0", "tinykeys": "^2.1.0", "undici": "^6.2.1", @@ -14752,6 +14753,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", + "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/google-protobuf": { "version": "3.21.2", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", @@ -20351,6 +20360,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 5d1bd3e8..cc9fdd4b 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "postcss": "^8.4.33", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "tailwindcss": "^3.4.0", "tinykeys": "^2.1.0", "undici": "^6.2.1", From 17685e4e0507ce3be70ef83678fec3cd40efff25 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 04:29:31 +0700 Subject: [PATCH 031/115] refactor: drop custom toast --- package-lock.json | 35 ------------ package.json | 1 - src/components/AssetInput.tsx | 10 +--- src/components/Toast.tsx | 41 -------------- .../TransactionDialogContent.tsx | 22 ++------ src/context/toast.tsx | 53 ------------------ src/pages/_app.tsx | 54 +++++++++---------- src/solve/context.tsx | 6 +-- 8 files changed, 32 insertions(+), 190 deletions(-) delete mode 100644 src/components/Toast.tsx delete mode 100644 src/context/toast.tsx diff --git a/package-lock.json b/package-lock.json index 62b5288f..fbbae165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-scroll-area": "^1.0.5", - "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@react-stately/table": "^3.11.4", @@ -5793,40 +5792,6 @@ } } }, - "node_modules/@radix-ui/react-toast": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz", - "integrity": "sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-visually-hidden": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-toggle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", diff --git a/package.json b/package.json index cc9fdd4b..d9a4b62b 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-scroll-area": "^1.0.5", - "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@react-stately/table": "^3.11.4", diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index 8001c5cb..42f9b9a9 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -2,9 +2,8 @@ import { PencilSquareIcon } from "@heroicons/react/20/solid"; import { BigNumber } from "bignumber.js"; import { clsx } from "clsx"; import { ethers } from "ethers"; -import { FC, Fragment, useMemo, useState } from "react"; +import { FC, Fragment, useMemo } from "react"; -import Toast from "@/components/Toast"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { disclosure } from "@/context/disclosures"; import { useSettingsStore } from "@/context/settings"; @@ -47,8 +46,6 @@ const AssetInput: FC = ({ showSlippage, context, }) => { - const [isError, setIsError] = useState(false); - const { assetsByChainID, getNativeAssets, getFeeDenom } = useAssets(); const assets = useMemo(() => { @@ -268,11 +265,6 @@ const AssetInput: FC = ({
- ); }; diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx deleted file mode 100644 index 0c60d939..00000000 --- a/src/components/Toast.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as RadixToast from "@radix-ui/react-toast"; -import { FC, Fragment } from "react"; - -interface Props { - open?: boolean; - setOpen?: (open: boolean) => void; - title?: string; - description?: string; - type?: "success" | "error"; -} - -const DEFAULT_TITLE = "Uh oh! Something went wrong."; -const DEFAULT_DESCRIPTION = "There was an unexpected error. Please try again."; - -const Toast: FC = ({ - open, - setOpen, - title = DEFAULT_TITLE, - description = DEFAULT_DESCRIPTION, - type = "error", -}) => { - return ( - - - {title} - -

{description}

-
-
-
- ); -}; - -export default Toast; diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index f5df942f..47c237ef 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -2,11 +2,10 @@ import { ArrowLeftIcon, CheckCircleIcon } from "@heroicons/react/20/solid"; import { RouteResponse } from "@skip-router/core"; import { clsx } from "clsx"; import { FC, Fragment, useState } from "react"; +import { toast } from "react-hot-toast"; import { useAccount } from "wagmi"; -import Toast from "@/components/Toast"; import { useSettingsStore } from "@/context/settings"; -import { useToast } from "@/context/toast"; import { addTxHistory, addTxStatus, @@ -44,8 +43,6 @@ const TransactionDialogContent: FC = ({ insufficentBalance, transactionCount, }) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { toast } = useToast(); const { data: chains = [] } = useChains(); const skipClient = useSkipClient(); @@ -53,9 +50,6 @@ const TransactionDialogContent: FC = ({ const [transacting, setTransacting] = useState(false); - const [isError, setIsError] = useState(false); - const [txError, setTxError] = useState(null); - const [txComplete, setTxComplete] = useState(false); const [numberOfBroadcastedTransactions, setNumberOfBroadcastedTransactions] = useState(0); @@ -172,18 +166,13 @@ const TransactionDialogContent: FC = ({ }, }); - toast( - "Transaction Successful", - "Your transaction was successful", - "success", - ); - setTxComplete(true); } catch (err: unknown) { - console.error(err); + if (process.env.NODE_ENV === "development") { + console.error(err); + } if (err instanceof Error) { - setTxError(err.message); - setIsError(true); + toast.error(err.message, { duration: Infinity }); } failTxHistory(historyId); setTxStatuses((statuses) => { @@ -378,7 +367,6 @@ const TransactionDialogContent: FC = ({ )}
- ); }; diff --git a/src/context/toast.tsx b/src/context/toast.tsx deleted file mode 100644 index b683d533..00000000 --- a/src/context/toast.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { - createContext, - FC, - PropsWithChildren, - useContext, - useState, -} from "react"; - -import Toast from "@/components/Toast"; - -interface ToastContext { - toast: (title: string, message: string, type: "success" | "error") => void; -} - -export const ToastContext = createContext({ - toast: () => {}, -}); - -interface ToastConfig { - title: string; - message: string; - type: "success" | "error"; -} - -export const ToastProvider: FC = ({ children }) => { - const [toasts, setToasts] = useState([]); - - function addToast( - title: string, - message: string, - type: "success" | "error" = "error", - ) { - setToasts((toasts) => [...toasts, { title, message, type }]); - } - - return ( - - {children} - {toasts.map((toast, index) => ( - - ))} - - ); -}; - -export function useToast() { - return useContext(ToastContext); -} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 0e8be220..f1a6c4b3 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,20 +2,19 @@ import "@/styles/globals.css"; import "@interchain-ui/react/styles"; import { ChainProvider } from "@cosmos-kit/react"; -import * as RadixToast from "@radix-ui/react-toast"; import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; import { Analytics } from "@vercel/analytics/react"; import { AppProps } from "next/app"; import Head from "next/head"; import { ComponentProps } from "react"; +import { Toaster } from "react-hot-toast"; import { WagmiConfig } from "wagmi"; import { getAssetLists, getChains } from "@/chains"; import { BuildInfo } from "@/components/BuildInfo"; import MainLayout from "@/components/MainLayout"; import { AssetsProvider } from "@/context/assets"; -import { ToastProvider } from "@/context/toast"; import { wallets } from "@/lib/cosmos-kit"; import { queryClient } from "@/lib/react-query"; import { wagmiConfig } from "@/lib/wagmi"; @@ -42,34 +41,31 @@ export default function App({ Component, pageProps }: AppProps) { content="Interchain transfers and swaps on any Cosmos chain" /> -
- + - - - - - - - - - - - - - - - - - -
+ + + + + + + + + + + + diff --git a/src/solve/context.tsx b/src/solve/context.tsx index d1401bb0..5d670181 100644 --- a/src/solve/context.tsx +++ b/src/solve/context.tsx @@ -70,11 +70,7 @@ export const SkipProvider: FC = ({ children }) => { }); return ( - + {children} ); From 1890cee501992299ec2164828af6c01b23b415c2 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 04:30:47 +0700 Subject: [PATCH 032/115] feat: optimize contexes --- src/context/assets.tsx | 93 +++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/src/context/assets.tsx b/src/context/assets.tsx index 5f2774c9..7a7417f0 100644 --- a/src/context/assets.tsx +++ b/src/context/assets.tsx @@ -3,6 +3,7 @@ import { createContext, FC, PropsWithChildren, + useCallback, useContext, useMemo, } from "react"; @@ -129,66 +130,64 @@ function getAssetSymbol( export const AssetsProvider: FC = ({ children }) => { const { data: chains } = useChains(); - const { data: solveAssets } = useSolveAssets(); const assets = useMemo(() => { - if (!solveAssets || !chains) { - return {}; - } + const data: Record = {}; + + if (!solveAssets || !chains) return data; - return Object.entries(solveAssets).reduce( - (acc, [chainID, assets]) => { + for (const [chainID, assets] of Object.entries(solveAssets)) { + data[chainID] = filterAssetsWithMetadata(assets).map((asset) => { + const logoURI = + asset.originDenom === "utia" + ? "https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/celestia/asset/tia.png" + : asset.logoURI; return { - ...acc, - [chainID]: filterAssetsWithMetadata(assets).map((asset) => ({ - ...asset, - symbol: getAssetSymbol(asset, assets, chains), - logoURI: - asset.originDenom === "utia" - ? "https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/celestia/asset/tia.png" - : asset.logoURI, - })), + ...asset, + symbol: getAssetSymbol(asset, assets, chains), + logoURI, }; - }, - {} as Record, - ); - }, [chains, solveAssets]); - - function assetsByChainID(chainID: string) { - return assets[chainID] || []; - } - - function getAsset(denom: string, chainID: string) { - const asset = assets[chainID]?.find((asset) => asset.denom === denom); + }); + } - return asset; - } + return data; + }, [chains, solveAssets]); - function getFeeDenom(chainID: string) { - const chain = (chains ?? []).find((c) => c.chainID === chainID); + const assetsByChainID = useCallback( + (chainID: string) => { + return assets[chainID] || []; + }, + [assets], + ); - if (!chain || chain.feeAssets.length === 0) { - return undefined; - } + const getAsset = useCallback( + (denom: string, chainID: string) => { + const asset = assets[chainID]?.find((asset) => asset.denom === denom); + return asset; + }, + [assets], + ); - // prioritize non-ibc assets - const sortedFeeDenoms = [...chain.feeAssets].sort((a, b) => { - if (a.denom.includes("ibc/")) { - return 1; - } + const getFeeDenom = useCallback( + (chainID: string) => { + const chain = (chains ?? []).find((chain) => chain.chainID === chainID); - if (b.denom.includes("ibc/")) { - return -1; - } + if (!chain || chain.feeAssets.length === 0) return undefined; - return 0; - }); + // prioritize non-ibc assets + const sortedFeeDenoms = [...chain.feeAssets].sort((a, b) => { + if (a.denom.includes("ibc/")) return 1; + if (b.denom.includes("ibc/")) return -1; + return 0; + }); - return getAsset(sortedFeeDenoms[0].denom, chainID); - } + return getAsset(sortedFeeDenoms[0].denom, chainID); + }, + [chains, getAsset], + ); - function getNativeAssets() { + const getNativeAssets = useCallback(() => { const nativeAssets: AssetWithMetadata[] = []; for (const chainAssetList of Object.values(assets)) { @@ -200,7 +199,7 @@ export const AssetsProvider: FC = ({ children }) => { } return nativeAssets; - } + }, [assets]); const isReady = useMemo(() => Object.keys(assets).length > 0, [assets]); From fa97345da38cd60f2299401fcf253a8132b1ea7d Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 05:11:22 +0700 Subject: [PATCH 033/115] chore: configure toast durations --- src/components/TransactionDialog/TransactionDialogContent.tsx | 2 +- src/pages/_app.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TransactionDialog/TransactionDialogContent.tsx b/src/components/TransactionDialog/TransactionDialogContent.tsx index 47c237ef..8b65351f 100644 --- a/src/components/TransactionDialog/TransactionDialogContent.tsx +++ b/src/components/TransactionDialog/TransactionDialogContent.tsx @@ -172,7 +172,7 @@ const TransactionDialogContent: FC = ({ console.error(err); } if (err instanceof Error) { - toast.error(err.message, { duration: Infinity }); + toast.error(err.message); } failTxHistory(historyId); setTxStatuses((statuses) => { diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index f1a6c4b3..2088faa3 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -59,7 +59,7 @@ export default function App({ Component, pageProps }: AppProps) { From 5f197feb71ba9f6a66a4d78573671c4c6e7dd6c8 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 05:11:34 +0700 Subject: [PATCH 034/115] fix: update enableChains --- src/utils/chain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/chain.ts b/src/utils/chain.ts index 4375b11d..1acb3b17 100644 --- a/src/utils/chain.ts +++ b/src/utils/chain.ts @@ -47,7 +47,7 @@ export async function enableChains( } // CosmosSnapClient - if ("handleConnect" in walletClient) { + if ("snapInstalled" in walletClient && walletClient.snapInstalled) { return walletClient.handleConnect(); } From 93522e40a678ef108882f8479ff0cfe908df6fd3 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 05:12:19 +0700 Subject: [PATCH 035/115] feat: improve wallet modal --- src/components/WalletModal/WalletModal.tsx | 82 ++++++++++++++++++++-- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/src/components/WalletModal/WalletModal.tsx b/src/components/WalletModal/WalletModal.tsx index c3ec445c..beb1186a 100644 --- a/src/components/WalletModal/WalletModal.tsx +++ b/src/components/WalletModal/WalletModal.tsx @@ -1,13 +1,17 @@ -import { useManager } from "@cosmos-kit/react"; -import { ArrowLeftIcon } from "@heroicons/react/20/solid"; -import { FC } from "react"; +import { useManager, useWalletClient } from "@cosmos-kit/react"; +import { ArrowLeftIcon, FaceFrownIcon } from "@heroicons/react/20/solid"; +import { clsx } from "clsx"; +import { ComponentProps, FC, ReactNode, useEffect, useMemo } from "react"; import { useAccount, useConnect, useDisconnect } from "wagmi"; +import { create } from "zustand"; -import { useChainByID } from "@/hooks/useChains"; import { chainIdToName } from "@/chains/types"; -import { EVM_WALLET_LOGOS, INJECTED_EVM_WALLET_LOGOS } from "@/constants/wagmi"; import { DialogContent } from "@/components/Dialog"; +import { EVM_WALLET_LOGOS, INJECTED_EVM_WALLET_LOGOS } from "@/constants/wagmi"; +import { useChainByID } from "@/hooks/useChains"; +import { MergedWalletClient } from "@/lib/cosmos-kit"; +import { AdaptiveLink } from "../AdaptiveLink"; import { useWalletModal } from "./context"; export interface MinimalWallet { @@ -26,12 +30,16 @@ interface Props { onClose: () => void; } +const useStore = create>(() => ({})); + export const WalletModal: FC = ({ onClose, wallets }) => { async function onWalletConnect(wallet: MinimalWallet) { await wallet.connect(); onClose(); } + const totalWallets = useStore((state) => Object.keys(state).length); + return (
@@ -47,8 +55,31 @@ export const WalletModal: FC = ({ onClose, wallets }) => {
+ {totalWallets < 1 && ( +
+ +

No Wallets Available

+

+ Please install or enable your preferred wallet extension. +
+ + View available wallets in Cosmos + +

+
+ )} {wallets.map((wallet) => ( -
+ ) : null} -
+ ))}
); }; +const WalletListItem = ({ + children, + walletName, + ...props +}: ComponentProps<"div"> & { + children: ReactNode; + walletName: string; +}) => { + const { client } = useWalletClient(walletName); + const walletClient = client as MergedWalletClient | undefined; + + const show = useMemo(() => { + if (!walletClient) return false; + if ("snapInstalled" in walletClient) { + return walletClient.snapInstalled; + } + return true; + }, [walletClient]); + + useEffect(() => { + const unregister = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + useStore.setState(({ [walletName]: _, ...latest }) => latest, true); + }; + if (show) useStore.setState({ [walletName]: true }); + else unregister(); + return unregister; + }, [show, walletName]); + + // return
{show ? children : null}
; + return ( +
+ {children} +
+ ); +}; + const WalletModalWithContext: FC = () => { const { connector: currentConnector } = useAccount(); const { chainID } = useWalletModal(); From 0fcd024ce89ae4ccbbecfb07db4b202e7fd20ab4 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 05:13:39 +0700 Subject: [PATCH 036/115] chore: update gas setting --- src/context/settings.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/context/settings.ts b/src/context/settings.ts index 67f3d301..fb3215d1 100644 --- a/src/context/settings.ts +++ b/src/context/settings.ts @@ -7,12 +7,13 @@ interface SettingsStore { } export const defaultValues: SettingsStore = { - gas: (150_000).toString(), + gas: (200_000).toString(), slippage: (3).toString(), }; export const useSettingsStore = create()( persist(() => defaultValues, { name: "SettingsStore", + version: 1, }), ); From 0d2f202d8b375db9a22527f29500f97d0a7bdfdf Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 05:14:42 +0700 Subject: [PATCH 037/115] Revert "chore: update gas setting" This reverts commit 0fcd024ce89ae4ccbbecfb07db4b202e7fd20ab4. --- src/context/settings.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/context/settings.ts b/src/context/settings.ts index fb3215d1..67f3d301 100644 --- a/src/context/settings.ts +++ b/src/context/settings.ts @@ -7,13 +7,12 @@ interface SettingsStore { } export const defaultValues: SettingsStore = { - gas: (200_000).toString(), + gas: (150_000).toString(), slippage: (3).toString(), }; export const useSettingsStore = create()( persist(() => defaultValues, { name: "SettingsStore", - version: 1, }), ); From 84646677e0d2e4ef2ede043c48bbbc9dbffe0eea Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 05:28:34 +0700 Subject: [PATCH 038/115] fix: handle if max button actually reached negative --- src/components/AssetInput.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/AssetInput.tsx b/src/components/AssetInput.tsx index 42f9b9a9..d5044697 100644 --- a/src/components/AssetInput.tsx +++ b/src/components/AssetInput.tsx @@ -3,6 +3,7 @@ import { BigNumber } from "bignumber.js"; import { clsx } from "clsx"; import { ethers } from "ethers"; import { FC, Fragment, useMemo } from "react"; +import toast from "react-hot-toast"; import { AssetWithMetadata, useAssets } from "@/context/assets"; import { disclosure } from "@/context/disclosures"; @@ -240,6 +241,17 @@ const AssetInput: FC = ({ .shiftedBy(-(feeDenom.decimals ?? 6)); // denom decimals amount = amount.minus(fee); + if (amount.isNegative()) { + amount = new BigNumber(0); + toast.error( +

+ Insufficient Balance +
+ You need to have at least ≈{fee.toString()} to + accommodate gas fees. +

, + ); + } } onAmountChange?.(amount.toString()); From 0f6b886d4d309c090a97148e227f3d86ff2d80e7 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 21:12:23 +0700 Subject: [PATCH 039/115] feat: add next-seo deps Signed-off-by: Griko Nibras --- package-lock.json | 11 +++++++++++ package.json | 1 + 2 files changed, 12 insertions(+) diff --git a/package-lock.json b/package-lock.json index fbbae165..e4420587 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "ethers": "^6.9.2", "match-sorter": "^6.3.1", "next": "^14.0.4", + "next-seo": "^6.4.0", "postcss": "^8.4.33", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -18566,6 +18567,16 @@ } } }, + "node_modules/next-seo": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/next-seo/-/next-seo-6.4.0.tgz", + "integrity": "sha512-XQFxkOL2hw0YE+P100HbI3EAvcludlHPxuzMgaIjKb7kPK0CvjGvLFjd9hszZFEDc5oiQkGFA8+cuWcnip7eYA==", + "peerDependencies": { + "next": "^8.1.1-canary.54 || >=9.0.0", + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", diff --git a/package.json b/package.json index d9a4b62b..bbfa5967 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "ethers": "^6.9.2", "match-sorter": "^6.3.1", "next": "^14.0.4", + "next-seo": "^6.4.0", "postcss": "^8.4.33", "react": "^18.2.0", "react-dom": "^18.2.0", From 565b4dc543593c988542f6c2838e674cc39a6493 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 21:12:47 +0700 Subject: [PATCH 040/115] feat: add @fontsource/jost deps Signed-off-by: Griko Nibras --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index e4420587..2659260e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "@evmos/proto": "^0.2.1", "@evmos/provider": "^0.3.1", "@evmos/transactions": "^0.3.2", + "@fontsource/jost": "^5.0.16", "@graz-sh/types": "^0.0.14", "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.1.1", @@ -3008,6 +3009,11 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" }, + "node_modules/@fontsource/jost": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/@fontsource/jost/-/jost-5.0.16.tgz", + "integrity": "sha512-3eyBs4pMxRnrLhTPrhSgeeRP3B9XC1b8NXhI7VPzUuvDoqviqdUl7nOpih0dwDGkS+hUCVD35LWMNp0uSyNHDQ==" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", diff --git a/package.json b/package.json index bbfa5967..aa9cb9af 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@evmos/proto": "^0.2.1", "@evmos/provider": "^0.3.1", "@evmos/transactions": "^0.3.2", + "@fontsource/jost": "^5.0.16", "@graz-sh/types": "^0.0.14", "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.1.1", From d7c896249aa4320f01b8e5b4b58af295c5ada362 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 21:13:09 +0700 Subject: [PATCH 041/115] feat: use local font source Signed-off-by: Griko Nibras --- src/pages/_app.tsx | 2 +- src/pages/_document.tsx | 20 +++----------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 2088faa3..a43e686e 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,5 +1,5 @@ +import "@fontsource/jost/latin.css"; import "@/styles/globals.css"; -import "@interchain-ui/react/styles"; import { ChainProvider } from "@cosmos-kit/react"; import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 3d8361c4..ac94f57c 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -1,27 +1,13 @@ -/* eslint-disable @next/next/no-img-element */ import { Head, Html, Main, NextScript } from "next/document"; export default function Document() { return ( - - - + + - - +
From 0f3625fa1280346a0983089cf2ba2f8abfea59d9 Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 21:14:02 +0700 Subject: [PATCH 042/115] refactor: rework default seo Signed-off-by: Griko Nibras --- src/components/DefaultSeo.tsx | 29 +++++++++++++++++++++++++++++ src/constants/api.ts | 5 ++--- src/constants/seo.ts | 19 +++++++++++++++++++ src/pages/_app.tsx | 11 ++--------- 4 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 src/components/DefaultSeo.tsx create mode 100644 src/constants/seo.ts diff --git a/src/components/DefaultSeo.tsx b/src/components/DefaultSeo.tsx new file mode 100644 index 00000000..dd3ade23 --- /dev/null +++ b/src/components/DefaultSeo.tsx @@ -0,0 +1,29 @@ +import { useRouter } from "next/router"; +import { DefaultSeo as NextDefaultSeo, DefaultSeoProps } from "next-seo"; + +import { metadata } from "@/constants/seo"; + +export function DefaultSeo(props: DefaultSeoProps) { + const { asPath } = useRouter(); + return ( + + ); +} diff --git a/src/constants/api.ts b/src/constants/api.ts index 7c165eba..2877a8ae 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -1,6 +1,5 @@ -import { SKIP_API_URL } from "@skip-router/core"; - -export const API_URL = process.env.NEXT_PUBLIC_API_URL || SKIP_API_URL; +export const API_URL = + process.env.NEXT_PUBLIC_API_URL || "https://api.skip.money"; export const APP_URL = process.env.APP_URL; diff --git a/src/constants/seo.ts b/src/constants/seo.ts new file mode 100644 index 00000000..954cb9b3 --- /dev/null +++ b/src/constants/seo.ts @@ -0,0 +1,19 @@ +import { APP_DOMAIN, APP_PROTOCOL } from "./api"; + +export const metadata = { + name: "ibc.fun", + shortName: "ibc.fun", + description: "Interchain transfers and swaps on any Cosmos chain", + domain: APP_DOMAIN, + email: "support@skip.money", + url: `${APP_PROTOCOL}://${APP_DOMAIN}`, + github: { + username: "skip-mev", + url: "https://github.com/skip-mev/ibc-dot-fun", + }, + twitter: { + username: "@SkipProtocol", + url: "https://twitter.com/SkipProtocol", + }, + themeColor: "#ef4444", +}; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a43e686e..925ce239 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -14,6 +14,7 @@ import { WagmiConfig } from "wagmi"; import { getAssetLists, getChains } from "@/chains"; import { BuildInfo } from "@/components/BuildInfo"; import MainLayout from "@/components/MainLayout"; +import { DefaultSeo } from "@/components/DefaultSeo"; import { AssetsProvider } from "@/context/assets"; import { wallets } from "@/lib/cosmos-kit"; import { queryClient } from "@/lib/react-query"; @@ -32,15 +33,7 @@ const chains = getChains() as ChainProviderProps["chains"]; export default function App({ Component, pageProps }: AppProps) { return ( <> - - - ibc.fun | Interchain transfers and swaps on any Cosmos chain - - - + Date: Sat, 6 Jan 2024 21:14:36 +0700 Subject: [PATCH 043/115] chore: update api utils Signed-off-by: Griko Nibras --- src/utils/api.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index 14bd3ab8..f033841b 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,5 +1,7 @@ +import { APP_URL } from "@/constants/api"; + export function getNodeProxyEndpoint(...args: string[]) { - return `${process.env.APP_URL}/api/nodes/${args.join("/")}`; + return `${APP_URL}/api/nodes/${args.join("/")}`; } export function getPolkachuAuthHeader() { From de240a1463a669eb6ff4d55b89b7975c2fdb3d7b Mon Sep 17 00:00:00 2001 From: Griko Nibras Date: Sat, 6 Jan 2024 21:14:50 +0700 Subject: [PATCH 044/115] feat: add social image Signed-off-by: Griko Nibras --- public/social.png | Bin 0 -> 286004 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/social.png diff --git a/public/social.png b/public/social.png new file mode 100644 index 0000000000000000000000000000000000000000..d1383297924fc5f823314b7f55ff89a9710e9e0b GIT binary patch literal 286004 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