From 6d4e56f46c3b4c7021f4a46bf1106d968098d010 Mon Sep 17 00:00:00 2001 From: Jennie Date: Tue, 21 Nov 2023 01:43:16 +0400 Subject: [PATCH 01/36] wip: Tenscan gateway --- .../api/ten-gateway/.eslintrc.json | 3 + .../api/ten-gateway/.gitignore | 36 + .../walletextension/api/ten-gateway/README.md | 40 + .../api/ten-gateway/next.config.js | 6 + .../api/ten-gateway/package-lock.json | 6518 +++++++++++++++++ .../api/ten-gateway/package.json | 48 + .../api/ten-gateway/postcss.config.js | 6 + .../api/ten-gateway/public/favicon.ico | Bin 0 -> 996 bytes .../api/ten-gateway/public/next.svg | 1 + .../api/ten-gateway/public/vercel.svg | 1 + .../src/components/layouts/default-layout.tsx | 21 + .../src/components/layouts/footer.tsx | 49 + .../src/components/layouts/header.tsx | 62 + .../ten-gateway/src/components/main-nav.tsx | 96 + .../src/components/mode-toggle.tsx | 40 + .../modules/common/connect-wallet.tsx | 31 + .../modules/common/network-status.tsx | 41 + .../modules/common/truncated-address.tsx | 55 + .../src/components/modules/home/index.tsx | 7 + .../components/providers/theme-provider.tsx | 9 + .../components/providers/wallet-provider.tsx | 90 + .../ten-gateway/src/components/ui/alert.tsx | 64 + .../ten-gateway/src/components/ui/badge.tsx | 38 + .../ten-gateway/src/components/ui/button.tsx | 58 + .../ten-gateway/src/components/ui/card.tsx | 86 + .../ten-gateway/src/components/ui/dialog.tsx | 120 + .../src/components/ui/dropdown-menu.tsx | 198 + .../ten-gateway/src/components/ui/select.tsx | 119 + .../src/components/ui/separator.tsx | 29 + .../src/components/ui/skeleton.tsx | 15 + .../ten-gateway/src/components/ui/table.tsx | 114 + .../ten-gateway/src/components/ui/toast.tsx | 127 + .../ten-gateway/src/components/ui/toaster.tsx | 33 + .../ten-gateway/src/components/ui/tooltip.tsx | 28 + .../src/components/ui/use-toast.ts | 189 + .../api/ten-gateway/src/hooks/useCopy.ts | 56 + .../api/ten-gateway/src/lib/constants.ts | 5 + .../api/ten-gateway/src/lib/utils.ts | 12 + .../api/ten-gateway/src/pages/_app.tsx | 25 + .../api/ten-gateway/src/pages/_document.tsx | 13 + .../api/ten-gateway/src/pages/api/hello.ts | 13 + .../api/ten-gateway/src/pages/index.tsx | 17 + .../api/ten-gateway/src/routes/index.ts | 55 + .../src/styles/fonts/CloudSoft-Bold_700.otf | Bin 0 -> 134448 bytes .../src/styles/fonts/CloudSoft-Light_300.otf | Bin 0 -> 133320 bytes .../ten-gateway/src/styles/fonts/README.txt | 4 + .../api/ten-gateway/src/styles/globals.css | 169 + .../src/types/interfaces/WalletInterfaces.ts | 21 + .../ten-gateway/src/types/interfaces/index.ts | 62 + .../api/ten-gateway/tailwind.config.ts | 20 + .../api/ten-gateway/tsconfig.json | 22 + 51 files changed, 8872 insertions(+) create mode 100644 tools/walletextension/api/ten-gateway/.eslintrc.json create mode 100644 tools/walletextension/api/ten-gateway/.gitignore create mode 100644 tools/walletextension/api/ten-gateway/README.md create mode 100644 tools/walletextension/api/ten-gateway/next.config.js create mode 100644 tools/walletextension/api/ten-gateway/package-lock.json create mode 100644 tools/walletextension/api/ten-gateway/package.json create mode 100644 tools/walletextension/api/ten-gateway/postcss.config.js create mode 100644 tools/walletextension/api/ten-gateway/public/favicon.ico create mode 100644 tools/walletextension/api/ten-gateway/public/next.svg create mode 100644 tools/walletextension/api/ten-gateway/public/vercel.svg create mode 100644 tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/layouts/footer.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/layouts/header.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/main-nav.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/mode-toggle.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/modules/common/connect-wallet.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/modules/common/network-status.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/providers/theme-provider.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/alert.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/button.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/card.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/dialog.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/dropdown-menu.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/select.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/separator.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/skeleton.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/table.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/toaster.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/tooltip.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/ui/use-toast.ts create mode 100644 tools/walletextension/api/ten-gateway/src/hooks/useCopy.ts create mode 100644 tools/walletextension/api/ten-gateway/src/lib/constants.ts create mode 100644 tools/walletextension/api/ten-gateway/src/lib/utils.ts create mode 100644 tools/walletextension/api/ten-gateway/src/pages/_app.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/pages/_document.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/pages/api/hello.ts create mode 100644 tools/walletextension/api/ten-gateway/src/pages/index.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/routes/index.ts create mode 100644 tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Bold_700.otf create mode 100644 tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Light_300.otf create mode 100644 tools/walletextension/api/ten-gateway/src/styles/fonts/README.txt create mode 100644 tools/walletextension/api/ten-gateway/src/styles/globals.css create mode 100644 tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts create mode 100644 tools/walletextension/api/ten-gateway/src/types/interfaces/index.ts create mode 100644 tools/walletextension/api/ten-gateway/tailwind.config.ts create mode 100644 tools/walletextension/api/ten-gateway/tsconfig.json diff --git a/tools/walletextension/api/ten-gateway/.eslintrc.json b/tools/walletextension/api/ten-gateway/.eslintrc.json new file mode 100644 index 0000000000..bffb357a71 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/tools/walletextension/api/ten-gateway/.gitignore b/tools/walletextension/api/ten-gateway/.gitignore new file mode 100644 index 0000000000..fd3dbb571a --- /dev/null +++ b/tools/walletextension/api/ten-gateway/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/tools/walletextension/api/ten-gateway/README.md b/tools/walletextension/api/ten-gateway/README.md new file mode 100644 index 0000000000..a75ac52488 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/README.md @@ -0,0 +1,40 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/tools/walletextension/api/ten-gateway/next.config.js b/tools/walletextension/api/ten-gateway/next.config.js new file mode 100644 index 0000000000..a843cbee09 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +} + +module.exports = nextConfig diff --git a/tools/walletextension/api/ten-gateway/package-lock.json b/tools/walletextension/api/ten-gateway/package-lock.json new file mode 100644 index 0000000000..a968f348f9 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/package-lock.json @@ -0,0 +1,6518 @@ +{ + "name": "ten-gateway", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ten-gateway", + "version": "0.1.0", + "dependencies": { + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-tooltip": "^1.0.7", + "@tanstack/react-query": "^5.8.1", + "@tanstack/react-query-devtools": "^5.8.1", + "axios": "^1.6.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", + "cmdk": "^0.2.0", + "date-fns": "^2.30.0", + "ethers": "^5.7.2", + "lucide-react": "^0.292.0", + "next": "14.0.1", + "next-themes": "^0.2.1", + "path-to-regexp": "^6.2.1", + "react": "^18", + "react-dom": "^18", + "tailwind-merge": "^2.0.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.0.3", + "postcss": "^8", + "tailwindcss": "^3.3.0", + "typescript": "^5" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz", + "integrity": "sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.1.tgz", + "integrity": "sha512-Ms8ZswqY65/YfcjrlcIwMPD7Rg/dVjdLapMcSHG26W6O67EJDF435ShW4H4LXi1xKO1oRc97tLXUpx8jpLe86A==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.3.tgz", + "integrity": "sha512-j4K0n+DcmQYCVnSAM+UByTVfIHnYQy2ODozfQP+4RdwtRDfobrIvKq1K4Exb2koJ79HSSa7s6B2SA8T/1YR3RA==", + "dev": true, + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.1.tgz", + "integrity": "sha512-JyxnGCS4qT67hdOKQ0CkgFTp+PXub5W1wsGvIq98TNbF3YEIN7iDekYhYsZzc8Ov0pWEsghQt+tANdidITCLaw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.1.tgz", + "integrity": "sha512-625Z7bb5AyIzswF9hvfZWa+HTwFZw+Jn3lOBNZB87lUS0iuCYDHqk3ujuHCkiyPtSC0xFBtYDLcrZ11mF/ap3w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.1.tgz", + "integrity": "sha512-iVpn3KG3DprFXzVHM09kvb//4CNNXBQ9NB/pTm8LO+vnnnaObnzFdS5KM+w1okwa32xH0g8EvZIhoB3fI3mS1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.1.tgz", + "integrity": "sha512-mVsGyMxTLWZXyD5sen6kGOTYVOO67lZjLApIj/JsTEEohDDt1im2nkspzfV5MvhfS7diDw6Rp/xvAQaWZTv1Ww==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.1.tgz", + "integrity": "sha512-wMqf90uDWN001NqCM/auRl3+qVVeKfjJdT9XW+RMIOf+rhUzadmYJu++tp2y+hUbb6GTRhT+VjQzcgg/QTD9NQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.1.tgz", + "integrity": "sha512-ol1X1e24w4j4QwdeNjfX0f+Nza25n+ymY0T2frTyalVczUmzkVD7QGgPTZMHfR1aLrO69hBs0G3QBYaj22J5GQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.1.tgz", + "integrity": "sha512-WEmTEeWs6yRUEnUlahTgvZteh5RJc4sEjCQIodJlZZ5/VJwVP8p2L7l6VhzQhT4h7KvLx/Ed4UViBdne6zpIsw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.1.tgz", + "integrity": "sha512-oFpHphN4ygAgZUKjzga7SoH2VGbEJXZa/KL8bHCAwCjDWle6R1SpiGOdUdA8EJ9YsG1TYWpzY6FTbUA+iAJeww==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.1.tgz", + "integrity": "sha512-FFp3nOJ/5qSpeWT0BZQ+YE1pSMk4IMpkME/1DwKBwhg4mJLB9L+6EXuJi4JEwaJdl5iN+UUlmUD3IsR1kx5fAg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", + "integrity": "sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", + "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "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-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "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-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@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-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "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-direction": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", + "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "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-dropdown-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", + "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-menu": "2.0.6", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "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-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "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-icons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", + "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", + "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", + "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-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "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-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" + }, + "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-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "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-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "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-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "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-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "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-direction": "1.0.1", + "@radix-ui/react-id": "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" + }, + "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-select": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz", + "integrity": "sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.1", + "@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-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@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-use-previous": "1.0.1", + "@radix-ui/react-visually-hidden": "1.0.3", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "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-separator": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz", + "integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "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-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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-tooltip": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz", + "integrity": "sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@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-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "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-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz", + "integrity": "sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", + "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", + "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "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/rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", + "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", + "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.8.3.tgz", + "integrity": "sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.8.4.tgz", + "integrity": "sha512-F1dRbITNt9tMUoM9WCH8WQ2c54116hv52m/PKK8ZiN/pO2wGVzTZtKuLanF8pFpwmNchjIixcMw/a57HY5ivcw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.8.4.tgz", + "integrity": "sha512-CD+AkXzg8J72JrE6ocmuBEJfGzEzu/bzkD6sFXFDDB5yji9N20JofXZlN6n0+CaPJuIi+e4YLCbGsyPFKkfNQA==", + "dependencies": { + "@tanstack/query-core": "5.8.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.8.4.tgz", + "integrity": "sha512-mffs51FJqXU/5rwhbwv393DccL6et7uK2pRLwOcmMrWbPyW8vpxr9oidaghHX4cdVeP/7u5owW9yMpBhBAJfcQ==", + "dependencies": { + "@tanstack/query-devtools": "5.8.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.8.4", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.10", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz", + "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==", + "devOptional": true + }, + "node_modules/@types/react": { + "version": "18.2.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz", + "integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.15", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz", + "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==", + "devOptional": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", + "integrity": "sha512-Vlktnchmkylvc9SnwwwozTv04L/e1NykF5vgoQ0XTmI8DD+wxfjQuHuvHS3p0r2jz2x2ghPs2h1FVeDirIteWA==", + "devOptional": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz", + "integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.12.0", + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/typescript-estree": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz", + "integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz", + "integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz", + "integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz", + "integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.12.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", + "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/autoprefixer": { + "version": "10.4.16", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001563", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", + "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.0.tgz", + "integrity": "sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==", + "dependencies": { + "@radix-ui/react-dialog": "1.0.0", + "command-score": "0.1.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/primitive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", + "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-context": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz", + "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dialog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz", + "integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.0", + "@radix-ui/react-focus-guards": "1.0.0", + "@radix-ui/react-focus-scope": "1.0.0", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-portal": "1.0.0", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-slot": "1.0.0", + "@radix-ui/react-use-controllable-state": "1.0.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.4" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz", + "integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-escape-keydown": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz", + "integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz", + "integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", + "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-portal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz", + "integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-presence": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", + "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-primitive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz", + "integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-slot": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz", + "integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", + "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz", + "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz", + "integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", + "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/cmdk/node_modules/react-remove-scroll": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz", + "integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/command-score": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz", + "integrity": "sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "devOptional": true + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.589", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.589.tgz", + "integrity": "sha512-zF6y5v/YfoFIgwf2dDfAqVlPPsyQeWNpEWXbAlDUS8Ax4Z2VoiiZpAPC0Jm9hXEkJm2vIZpwB6rc4KnLTQffbQ==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", + "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.0.3.tgz", + "integrity": "sha512-IKPhpLdpSUyKofmsXUfrvBC49JMUTdeaD8ZIH4v9Vk0sC1X6URTuTJCLtA0Vwuj7V/CQh0oISuSTvNn5//Buew==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "14.0.3", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "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==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lucide-react": { + "version": "0.292.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.292.0.tgz", + "integrity": "sha512-rRgUkpEHWpa5VCT66YscInCQmQuPCB1RFRzkkxMxg4b+jaL0V12E3riWWR2Sh5OIiUhCwGW/ZExuEO4Az32E6Q==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/next": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/next/-/next-14.0.1.tgz", + "integrity": "sha512-s4YaLpE4b0gmb3ggtmpmV+wt+lPRuGtANzojMQ2+gmBpgX9w5fTbjsy6dXByBuENsdCX5pukZH/GxdFgO62+pA==", + "dependencies": { + "@next/env": "14.0.1", + "@swc/helpers": "0.5.2", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.31", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.0.1", + "@next/swc-darwin-x64": "14.0.1", + "@next/swc-linux-arm64-gnu": "14.0.1", + "@next/swc-linux-arm64-musl": "14.0.1", + "@next/swc-linux-x64-gnu": "14.0.1", + "@next/swc-linux-x64-musl": "14.0.1", + "@next/swc-win32-arm64-msvc": "14.0.1", + "@next/swc-win32-ia32-msvc": "14.0.1", + "@next/swc-win32-x64-msvc": "14.0.1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "peerDependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + } + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.0.0.tgz", + "integrity": "sha512-WO8qghn9yhsldLSg80au+3/gY9E4hFxIvQ3qOmlpXnqpDKoMruKfi/56BbbMg6fHTQJ9QD3cc79PoWqlaQE4rw==", + "dependencies": { + "@babel/runtime": "^7.23.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", + "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tools/walletextension/api/ten-gateway/package.json b/tools/walletextension/api/ten-gateway/package.json new file mode 100644 index 0000000000..663b872fe6 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/package.json @@ -0,0 +1,48 @@ +{ + "name": "ten-gateway", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-tooltip": "^1.0.7", + "@tanstack/react-query": "^5.8.1", + "@tanstack/react-query-devtools": "^5.8.1", + "axios": "^1.6.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", + "cmdk": "^0.2.0", + "date-fns": "^2.30.0", + "ethers": "^5.7.2", + "lucide-react": "^0.292.0", + "next": "14.0.1", + "next-themes": "^0.2.1", + "path-to-regexp": "^6.2.1", + "react": "^18", + "react-dom": "^18", + "tailwind-merge": "^2.0.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.0.3", + "postcss": "^8", + "tailwindcss": "^3.3.0", + "typescript": "^5" + } +} diff --git a/tools/walletextension/api/ten-gateway/postcss.config.js b/tools/walletextension/api/ten-gateway/postcss.config.js new file mode 100644 index 0000000000..33ad091d26 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/tools/walletextension/api/ten-gateway/public/favicon.ico b/tools/walletextension/api/ten-gateway/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1bb8f324a18a956628274ccbacffa64f1eeabc5f GIT binary patch literal 996 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA19?eAK~!i%?U_#~ zJy8_L&-2LhzsUcxV_`wrVId1ik%*KXODVFjKv~Jw#!6W}DK?~}Y$R4F3Qmu&@xu#>QZ6Z4C|%4q#_z2cpp^bar+^I2`7v z-(!^1QdU${gfuWZ866!J#(r5>R~J4$K612|?d@$WC@2v2l{BH#Wl2d%I5aeb4-XGK z?0?3`$Ax_iYiw-9_xE>>^k;8x4V$FMS=?QS9vo;I0SyMr%ggMIPjz)QD}eR&byjXpPEO$R@{+II zePof^Mrp1By&%4dPkMSf(2m7d0LRD2th_kejF%h^Ykz!IR7m=!>6ZLMjs3=a>p0ysN6gWcU-Q&aH>R0u+SeLW;3#D7Hl zSQeR4I?#=_>Gz(v;vRtJZ02Ys1XUOe`rW z!KtY!Zn5a=>$AtC?+o@LPESwUejgJ$ZR_dj;akZuJ3DLlr4Juh-Q>r{#)e(UWoKuz z*W&K(&c2_>;{Ja!GBQ}nk(HIj3Set%3)6-(tuC7dN zaCCIUN=)1zIE>S=zrP=Hb933_kFonui40Wxv&SMQCkJ|adx5G=^Yilc_0_C_78e&` zWo5;4fK^sj!rDtj^(ZA5BMkk;eoq%d|0; \ No newline at end of file diff --git a/tools/walletextension/api/ten-gateway/public/vercel.svg b/tools/walletextension/api/ten-gateway/public/vercel.svg new file mode 100644 index 0000000000..d2f8422273 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx b/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx new file mode 100644 index 0000000000..25ed6a8201 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import Header from "./header"; +import Footer from "./footer"; + +interface LayoutProps { + children: React.ReactNode; +} + +const Layout = ({ children }: LayoutProps) => { + return ( +
+
+
+
{children}
+
+
+
+ ); +}; + +export default Layout; diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/footer.tsx b/tools/walletextension/api/ten-gateway/src/components/layouts/footer.tsx new file mode 100644 index 0000000000..05e48f44a9 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/layouts/footer.tsx @@ -0,0 +1,49 @@ +import { socialLinks } from "@/lib/constants"; +import { + GitHubLogoIcon, + TwitterLogoIcon, + DiscordLogoIcon, +} from "@radix-ui/react-icons"; + +export default function Footer() { + return ( + + ); +} diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/header.tsx b/tools/walletextension/api/ten-gateway/src/components/layouts/header.tsx new file mode 100644 index 0000000000..6e6c09a2f7 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/layouts/header.tsx @@ -0,0 +1,62 @@ +import Image from "next/image"; +import { MainNav } from "../main-nav"; +import { ModeToggle } from "../mode-toggle"; +import ConnectWalletButton from "../modules/common/connect-wallet"; +import Link from "next/link"; +import { HamburgerMenuIcon } from "@radix-ui/react-icons"; +import { useState } from "react"; +import { Button } from "../ui/button"; + +export default function Header() { + return ( +
+
+ + Ten Logo + +
+ +
+ + +
+
+
+ +
+
+
+ ); +} + +const MobileMenu = () => { + const [menuOpen, setMenuOpen] = useState(false); + + return ( +
+ + + {menuOpen && ( +
+
+
+ + +
+
+
+ )} +
+ ); +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/main-nav.tsx b/tools/walletextension/api/ten-gateway/src/components/main-nav.tsx new file mode 100644 index 0000000000..ba59b97c7a --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/main-nav.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import Link from "next/link"; +import { useRouter } from "next/router"; + +import { cn } from "@/lib/utils"; +import { Button } from "./ui/button"; +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, +} from "./ui/dropdown-menu"; + +import { ChevronDownIcon } from "@radix-ui/react-icons"; +import { NavLink } from "@/types/interfaces"; +import { NavLinks } from "../routes"; + +const NavItem = ({ navLink }: { navLink: NavLink }) => { + const router = useRouter(); + + const isDropdownActive = (navLink: NavLink) => { + return navLink.subNavLinks?.some( + (subNavLink: NavLink) => + subNavLink.href && router.pathname.includes(subNavLink.href) + ); + }; + if (navLink.isDropdown) { + return ( + + + + + + + {navLink.subNavLinks && + navLink.subNavLinks.map((subNavLink: NavLink) => ( + + + + ))} + + + + ); + } else if (navLink.isExternal) { + return ( + + {navLink.label} + + ); + } else { + return ( + + {navLink.label} + + ); + } +}; + +export const MainNav = ({ + className, + ...props +}: React.HTMLAttributes) => ( + +); diff --git a/tools/walletextension/api/ten-gateway/src/components/mode-toggle.tsx b/tools/walletextension/api/ten-gateway/src/components/mode-toggle.tsx new file mode 100644 index 0000000000..92640161e7 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/mode-toggle.tsx @@ -0,0 +1,40 @@ +"use client"; + +import * as React from "react"; +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export function ModeToggle() { + const { setTheme } = useTheme(); + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ); +} diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/connect-wallet.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/common/connect-wallet.tsx new file mode 100644 index 0000000000..60874f110f --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/modules/common/connect-wallet.tsx @@ -0,0 +1,31 @@ +import { useWalletConnection } from "@/components/providers/wallet-provider"; +import { Button } from "@/components/ui/button"; +import { Link2Icon, LinkBreak2Icon } from "@radix-ui/react-icons"; +import React from "react"; +const ConnectWalletButton = () => { + const { walletConnected, connectWallet, disconnectWallet } = + useWalletConnection(); + + return ( + + ); +}; + +export default ConnectWalletButton; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/network-status.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/common/network-status.tsx new file mode 100644 index 0000000000..9fa5815233 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/modules/common/network-status.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +const MessageContent = ( +

+ You seem to be offline +
Please check your internet connection. +

+); + +export const NetworkStatus = ({ message = MessageContent }) => { + const [isOnline, setIsOnline] = React.useState(true); + + React.useEffect(() => { + const setOnlineStatus = () => { + setIsOnline(navigator.onLine); + }; + + window.addEventListener("online", setOnlineStatus); + window.addEventListener("offline", setOnlineStatus); + + return () => { + window.removeEventListener("online", setOnlineStatus); + window.removeEventListener("offline", setOnlineStatus); + }; + }, []); + + return ( +
+
setIsOnline(true)} + > + {message} +
+
+ ); +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx new file mode 100644 index 0000000000..9843f83deb --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx @@ -0,0 +1,55 @@ +import React from "react"; + +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +import { useCopy } from "@/hooks/useCopy"; +import { CopyIcon } from "@radix-ui/react-icons"; + +const TruncatedAddress = ({ + address, + prefixLength, + suffixLength, +}: { + address: string; + prefixLength?: number; + suffixLength?: number; +}) => { + const truncatedAddress = `${address?.substring( + 0, + prefixLength || 6 + )}...${address?.substring(address.length - (suffixLength || 4))}`; + + const { copyToClipboard } = useCopy(); + + return ( +
+ {address ? ( + <> + + + {truncatedAddress} + +

{address}

+
+
+
+ + + ) : ( +
N/A
+ )} +
+ ); +}; + +export default TruncatedAddress; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx new file mode 100644 index 0000000000..f22b93dcbc --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Home = () => { + return
Home
; +}; + +export default Home; diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/theme-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/theme-provider.tsx new file mode 100644 index 0000000000..d4b4bbfd90 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/providers/theme-provider.tsx @@ -0,0 +1,9 @@ +'use client' + +import * as React from 'react' +import { ThemeProvider as NextThemesProvider } from 'next-themes' +import { type ThemeProviderProps } from 'next-themes/dist/types' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx new file mode 100644 index 0000000000..9dda4398d5 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx @@ -0,0 +1,90 @@ +import { createContext, useContext, useEffect, useState } from "react"; +import { ethers } from "ethers"; +import { + WalletConnectionContextType, + WalletConnectionProviderProps, +} from "@/types/interfaces/WalletInterfaces"; +import { useToast } from "../ui/use-toast"; + +const WalletConnectionContext = + createContext(null); + +export const useWalletConnection = (): WalletConnectionContextType => { + const context = useContext(WalletConnectionContext); + if (!context) { + throw new Error( + "useWalletConnection must be used within a WalletConnectionProvider" + ); + } + return context; +}; + +export const WalletConnectionProvider = ({ + children, +}: WalletConnectionProviderProps) => { + const { toast } = useToast(); + + const [walletConnected, setWalletConnected] = useState(false); + const [walletAddress, setWalletAddress] = useState(null); + const [provider, setProvider] = + useState(null); + + const connectWallet = async () => { + if ((window as any).ethereum) { + const ethProvider = new ethers.providers.Web3Provider( + (window as any).ethereum + ); + setProvider(ethProvider); + + try { + await ethProvider.send("eth_requestAccounts", []); + const signer = ethProvider.getSigner(); + const address = await signer.getAddress(); + setWalletAddress(address); + setWalletConnected(true); + } catch (error: any) { + toast({ description: "Error connecting to wallet:" + error.message }); + } + } else { + toast({ description: "No ethereum object found." }); + } + }; + + const disconnectWallet = () => { + if (provider) { + provider.removeAllListeners(); + setWalletConnected(false); + setWalletAddress(null); + setProvider(null); + } + }; + + useEffect(() => { + const ethereum = (window as any).ethereum; + const handleAccountsChanged = (accounts: string[]) => { + if (accounts.length === 0) { + toast({ description: "Please connect to MetaMask." }); + } else if (accounts[0] !== walletAddress) { + setWalletAddress(accounts[0]); + } + }; + ethereum.on("accountsChanged", handleAccountsChanged); + return () => { + ethereum.removeListener("accountsChanged", handleAccountsChanged); + }; + }); + + const walletConnectionContextValue: WalletConnectionContextType = { + provider, + walletConnected, + walletAddress, + connectWallet, + disconnectWallet, + }; + + return ( + + {children} + + ); +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/alert.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/alert.tsx new file mode 100644 index 0000000000..4447c835c6 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/alert.tsx @@ -0,0 +1,64 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + info: "border-info/50 text-info dark:border-info [&>svg]:text-info", + success: + "border-success/50 text-success dark:border-success [&>svg]:text-success", + warning: + "border-warning/50 text-warning dark:border-warning [&>svg]:text-warning", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx new file mode 100644 index 0000000000..6079f58828 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx @@ -0,0 +1,38 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + success: + "border-transparent bg-success text-success-foreground hover:bg-success/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx new file mode 100644 index 0000000000..150645b649 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + clear: "bg-transparent text-primary-foreground outline-none", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10 p-1", + wrap: "h-full w-full", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/card.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/card.tsx new file mode 100644 index 0000000000..8db8c4e4dd --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/card.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/dialog.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/dialog.tsx new file mode 100644 index 0000000000..ed732ceb7d --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/dropdown-menu.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000000..5c1e59d372 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,198 @@ +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/select.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/select.tsx new file mode 100644 index 0000000000..7d3600420d --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/select.tsx @@ -0,0 +1,119 @@ +import * as React from "react"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + {children} + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/separator.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/separator.tsx new file mode 100644 index 0000000000..11b8741882 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/separator.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import * as SeparatorPrimitive from "@radix-ui/react-separator"; + +import { cn } from "@/lib/utils"; + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +); +Separator.displayName = SeparatorPrimitive.Root.displayName; + +export { Separator }; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/skeleton.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/skeleton.tsx new file mode 100644 index 0000000000..2cdf440dcb --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils"; + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ); +} + +export { Skeleton }; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/table.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/table.tsx new file mode 100644 index 0000000000..1031493c9f --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/table.tsx @@ -0,0 +1,114 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx new file mode 100644 index 0000000000..3875c705ef --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx @@ -0,0 +1,127 @@ +import * as React from "react"; +import * as ToastPrimitives from "@radix-ui/react-toast"; +import { cva, type VariantProps } from "class-variance-authority"; +import { X } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const ToastProvider = ToastPrimitives.Provider; + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastViewport.displayName = ToastPrimitives.Viewport.displayName; + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ); +}); +Toast.displayName = ToastPrimitives.Root.displayName; + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastAction.displayName = ToastPrimitives.Action.displayName; + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +ToastClose.displayName = ToastPrimitives.Close.displayName; + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastTitle.displayName = ToastPrimitives.Title.displayName; + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastDescription.displayName = ToastPrimitives.Description.displayName; + +type ToastProps = React.ComponentPropsWithoutRef; + +type ToastActionElement = React.ReactElement; + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/toaster.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/toaster.tsx new file mode 100644 index 0000000000..5ff57090a7 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/toaster.tsx @@ -0,0 +1,33 @@ +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast"; +import { useToast } from "@/components/ui/use-toast"; + +export function Toaster() { + const { toasts } = useToast(); + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ); + })} + +
+ ); +} diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/tooltip.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/tooltip.tsx new file mode 100644 index 0000000000..13a05434be --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/use-toast.ts b/tools/walletextension/api/ten-gateway/src/components/ui/use-toast.ts new file mode 100644 index 0000000000..edc829679d --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/ui/use-toast.ts @@ -0,0 +1,189 @@ +// Inspired by react-hot-toast library +import * as React from "react"; + +import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; + +const TOAST_LIMIT = 1; +const TOAST_REMOVE_DELAY = 1000000; + +type ToasterToast = ToastProps & { + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + action?: ToastActionElement; +}; + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const; + +let count = 0; + +function genId() { + count = (count + 1) % Number.MAX_VALUE; + return count.toString(); +} + +type ActionType = typeof actionTypes; + +type Action = + | { + type: ActionType["ADD_TOAST"]; + toast: ToasterToast; + } + | { + type: ActionType["UPDATE_TOAST"]; + toast: Partial; + } + | { + type: ActionType["DISMISS_TOAST"]; + toastId?: ToasterToast["id"]; + } + | { + type: ActionType["REMOVE_TOAST"]; + toastId?: ToasterToast["id"]; + }; + +interface State { + toasts: ToasterToast[]; +} + +const toastTimeouts = new Map>(); + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return; + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId); + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }); + }, TOAST_REMOVE_DELAY); + + toastTimeouts.set(toastId, timeout); +}; + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + }; + + case "DISMISS_TOAST": { + const { toastId } = action; + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId); + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id); + }); + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + }; + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + }; + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +}; + +const listeners: Array<(state: State) => void> = []; + +let memoryState: State = { toasts: [] }; + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action); + listeners.forEach((listener) => { + listener(memoryState); + }); +} + +type Toast = Omit; + +function toast({ ...props }: Toast) { + const id = genId(); + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }); + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss(); + }, + }, + }); + + return { + id: id, + dismiss, + update, + }; +} + +function useToast() { + const [state, setState] = React.useState(memoryState); + + React.useEffect(() => { + listeners.push(setState); + return () => { + const index = listeners.indexOf(setState); + if (index > -1) { + listeners.splice(index, 1); + } + }; + }, [state]); + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + }; +} + +export { useToast, toast }; diff --git a/tools/walletextension/api/ten-gateway/src/hooks/useCopy.ts b/tools/walletextension/api/ten-gateway/src/hooks/useCopy.ts new file mode 100644 index 0000000000..d86878b5d3 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/hooks/useCopy.ts @@ -0,0 +1,56 @@ +import { useToast } from "@/components/ui/use-toast"; +import React from "react"; + +export const useCopy = () => { + const { toast } = useToast(); + const [copied, setCopied] = React.useState(false); + + const copyToClipboard = (text: string, name?: string) => { + copyText(text) + .catch(() => fallbackCopyTextToClipboard(text)) + .then(() => { + toast({ description: `${name ? name : ""} Copied!` }); + setCopied(true); + }) + .catch(() => { + toast({ description: `Couldn't copy ${name ? name : "Text"}!!!` }); + }) + .finally(() => { + setTimeout(() => setCopied(false), 1000); + }); + }; + + return { + copyToClipboard, + copied, + }; +}; + +const copyText = async (text: string) => { + return navigator.clipboard.writeText(text); +}; + +const fallbackCopyTextToClipboard = (text: string) => { + return new Promise((res, rej) => { + var textArea = document.createElement("textarea"); + textArea.value = text; + + // Avoid scrolling to bottom + textArea.style.top = "0"; + textArea.style.left = "0"; + textArea.style.position = "fixed"; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand("copy"); + } catch (err) { + rej(err); + } + + document.body.removeChild(textArea); + res(text); + }); +}; diff --git a/tools/walletextension/api/ten-gateway/src/lib/constants.ts b/tools/walletextension/api/ten-gateway/src/lib/constants.ts new file mode 100644 index 0000000000..b2148c2718 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/lib/constants.ts @@ -0,0 +1,5 @@ +export const socialLinks = { + github: "https://github.com/obscuronet", + discord: "https://discord.gg/2JQ2Z3r", + twitter: "https://twitter.com/obscuronet", +}; diff --git a/tools/walletextension/api/ten-gateway/src/lib/utils.ts b/tools/walletextension/api/ten-gateway/src/lib/utils.ts new file mode 100644 index 0000000000..628a129218 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/lib/utils.ts @@ -0,0 +1,12 @@ +import { type ClassValue, clsx } from "clsx"; +import { formatDistanceToNow } from "date-fns"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function formatTimeAgo(unixTimestampSeconds: string) { + const date = new Date(Number(unixTimestampSeconds) * 1000); + return formatDistanceToNow(date, { addSuffix: true }); +} diff --git a/tools/walletextension/api/ten-gateway/src/pages/_app.tsx b/tools/walletextension/api/ten-gateway/src/pages/_app.tsx new file mode 100644 index 0000000000..173cbe32fb --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/pages/_app.tsx @@ -0,0 +1,25 @@ +import { ThemeProvider } from "@/components/providers/theme-provider"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import "@/styles/globals.css"; +import type { AppProps } from "next/app"; +import { Toaster } from "@/components/ui/toaster"; +import { WalletConnectionProvider } from "@/components/providers/wallet-provider"; +import { NetworkStatus } from "@/components/modules/common/network-status"; + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + + + + + + ); +} diff --git a/tools/walletextension/api/ten-gateway/src/pages/_document.tsx b/tools/walletextension/api/ten-gateway/src/pages/_document.tsx new file mode 100644 index 0000000000..54e8bf3e2a --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/tools/walletextension/api/ten-gateway/src/pages/api/hello.ts b/tools/walletextension/api/ten-gateway/src/pages/api/hello.ts new file mode 100644 index 0000000000..f8bcc7e5ca --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/pages/api/hello.ts @@ -0,0 +1,13 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next' + +type Data = { + name: string +} + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.status(200).json({ name: 'John Doe' }) +} diff --git a/tools/walletextension/api/ten-gateway/src/pages/index.tsx b/tools/walletextension/api/ten-gateway/src/pages/index.tsx new file mode 100644 index 0000000000..6864e40283 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/pages/index.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { Metadata } from "next"; +import Layout from "@/components/layouts/default-layout"; +import Home from "@/components/modules/home"; + +export const metadata: Metadata = { + title: "Tenscan Gateway", + description: "Tenscan Gateway: A gateway to the Tenscan ecosystem", +}; + +export default function DashboardPage() { + return ( + + + + ); +} diff --git a/tools/walletextension/api/ten-gateway/src/routes/index.ts b/tools/walletextension/api/ten-gateway/src/routes/index.ts new file mode 100644 index 0000000000..2120379e6a --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/routes/index.ts @@ -0,0 +1,55 @@ +import { NavLink } from "../types/interfaces"; + +export const NavLinks: NavLink[] = [ + { + href: "/", + label: "Home", + isExternal: false, + isDropdown: false, + }, + { + href: "/personal", + label: "Personal", + isExternal: false, + isDropdown: false, + }, + { + label: "Blockchain", + isExternal: false, + isDropdown: true, + subNavLinks: [ + { + href: "/transactions", + label: "Transactions", + isExternal: false, + }, + { + href: "/blocks", + label: "Blocks", + isExternal: false, + }, + { + href: "/batches", + label: "Batches", + isExternal: false, + }, + ], + }, + { + label: "Resources", + isExternal: false, + isDropdown: true, + subNavLinks: [ + { + href: "/resources/decrypt", + label: "Decrypt", + isExternal: false, + }, + { + href: "/resources/verified-data", + label: "Verified Data", + isExternal: false, + }, + ], + }, +]; diff --git a/tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Bold_700.otf b/tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Bold_700.otf new file mode 100644 index 0000000000000000000000000000000000000000..ed9d1c401484de3eb2d13e67b2dd4365ec65e7b7 GIT binary patch literal 134448 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�S^V;gwZ)!Ozs7D3$sGuGd)T4rWR8Wr! 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 Promise; + disconnectWallet: () => void; +} + +export interface Props { + children: React.ReactNode; +} + +export interface State { + hasError: boolean; +} + +export interface WalletConnectionProviderProps { + children: React.ReactNode; +} diff --git a/tools/walletextension/api/ten-gateway/src/types/interfaces/index.ts b/tools/walletextension/api/ten-gateway/src/types/interfaces/index.ts new file mode 100644 index 0000000000..7558d165ed --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/types/interfaces/index.ts @@ -0,0 +1,62 @@ +import React from "react"; + +export interface ErrorType { + statusCode?: number; + showRedirectText?: boolean; + heading?: string; + statusText?: string; + message?: string; + redirectText?: string; + customPageTitle?: string; + isFullWidth?: boolean; + style?: React.CSSProperties; + hasGetInitialPropsRun?: boolean; + err?: Error; + showMessage?: boolean; + showStatusText?: boolean; + isModal?: boolean; + redirectLink?: string; + children?: React.ReactNode; + [key: string]: any; +} + +export interface IconProps { + width?: string; + height?: string; + fill?: string; + stroke?: string; + strokeWidth?: string; + className?: string; + isActive?: boolean; + onClick?: () => void; +} + +export interface GetInfinitePagesInterface { + nextId?: number; + previousId?: number; + data: T; + count: number; +} + +export interface PaginationInterface { + page: number; + perPage: number; + total: number; + totalPages: number; +} + +export interface ResponseDataInterface { + result: T; + item: T; + message: string; + pagination?: PaginationInterface; + success: string; +} + +export type NavLink = { + label: string; + href?: string; + isDropdown?: boolean; + isExternal?: boolean; + subNavLinks?: NavLink[]; +}; diff --git a/tools/walletextension/api/ten-gateway/tailwind.config.ts b/tools/walletextension/api/ten-gateway/tailwind.config.ts new file mode 100644 index 0000000000..1af3b8f019 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/tailwind.config.ts @@ -0,0 +1,20 @@ +import type { Config } from 'tailwindcss' + +const config: Config = { + content: [ + './src/pages/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}', + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': + 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + }, + }, + }, + plugins: [], +} +export default config diff --git a/tools/walletextension/api/ten-gateway/tsconfig.json b/tools/walletextension/api/ten-gateway/tsconfig.json new file mode 100644 index 0000000000..3ca6a9a50c --- /dev/null +++ b/tools/walletextension/api/ten-gateway/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} From f9ccd8ee2cd33e0b912ffb65a9a4d618c8e38a2a Mon Sep 17 00:00:00 2001 From: Jennie Date: Tue, 21 Nov 2023 16:36:24 +0400 Subject: [PATCH 02/36] wip: gateway revamp --- .../api/ten-gateway/package-lock.json | 62 ---- .../api/ten-gateway/package.json | 2 - .../src/components/layouts/default-layout.tsx | 2 +- .../src/components/modules/home/connected.tsx | 65 ++++ .../components/modules/home/disconnected.tsx | 43 +++ .../src/components/modules/home/index.tsx | 14 +- .../components/providers/wallet-provider.tsx | 338 +++++++++++++++++- .../api/ten-gateway/src/lib/utils.ts | 53 +++ .../api/ten-gateway/src/pages/_app.tsx | 2 - .../api/ten-gateway/src/pages/index.tsx | 6 +- .../api/ten-gateway/src/routes/index.ts | 54 +-- .../src/types/interfaces/WalletInterfaces.ts | 3 +- .../api/ten-gateway/tailwind.config.js | 84 +++++ .../api/ten-gateway/tailwind.config.ts | 93 ++++- 14 files changed, 682 insertions(+), 139 deletions(-) create mode 100644 tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx create mode 100644 tools/walletextension/api/ten-gateway/tailwind.config.js diff --git a/tools/walletextension/api/ten-gateway/package-lock.json b/tools/walletextension/api/ten-gateway/package-lock.json index a968f348f9..b2eba165d8 100644 --- a/tools/walletextension/api/ten-gateway/package-lock.json +++ b/tools/walletextension/api/ten-gateway/package-lock.json @@ -16,8 +16,6 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", - "@tanstack/react-query": "^5.8.1", - "@tanstack/react-query-devtools": "^5.8.1", "axios": "^1.6.1", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", @@ -1863,66 +1861,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@tanstack/query-core": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.8.3.tgz", - "integrity": "sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/query-devtools": { - "version": "5.8.4", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.8.4.tgz", - "integrity": "sha512-F1dRbITNt9tMUoM9WCH8WQ2c54116hv52m/PKK8ZiN/pO2wGVzTZtKuLanF8pFpwmNchjIixcMw/a57HY5ivcw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.8.4", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.8.4.tgz", - "integrity": "sha512-CD+AkXzg8J72JrE6ocmuBEJfGzEzu/bzkD6sFXFDDB5yji9N20JofXZlN6n0+CaPJuIi+e4YLCbGsyPFKkfNQA==", - "dependencies": { - "@tanstack/query-core": "5.8.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0", - "react-native": "*" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@tanstack/react-query-devtools": { - "version": "5.8.4", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.8.4.tgz", - "integrity": "sha512-mffs51FJqXU/5rwhbwv393DccL6et7uK2pRLwOcmMrWbPyW8vpxr9oidaghHX4cdVeP/7u5owW9yMpBhBAJfcQ==", - "dependencies": { - "@tanstack/query-devtools": "5.8.4" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "@tanstack/react-query": "^5.8.4", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", diff --git a/tools/walletextension/api/ten-gateway/package.json b/tools/walletextension/api/ten-gateway/package.json index 663b872fe6..d3c8054979 100644 --- a/tools/walletextension/api/ten-gateway/package.json +++ b/tools/walletextension/api/ten-gateway/package.json @@ -17,8 +17,6 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", - "@tanstack/react-query": "^5.8.1", - "@tanstack/react-query-devtools": "^5.8.1", "axios": "^1.6.1", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx b/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx index 25ed6a8201..af5d0ef13a 100644 --- a/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx @@ -8,7 +8,7 @@ interface LayoutProps { const Layout = ({ children }: LayoutProps) => { return ( -

+
{children}
diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx new file mode 100644 index 0000000000..c4df3e202e --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx @@ -0,0 +1,65 @@ +import { useWalletConnection } from "@/components/providers/wallet-provider"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import React from "react"; + +const Accounts = [ + { + name: "Account 1", + connected: true, + }, + { + name: "Account 2", + connected: true, + }, + { + name: "Account 3", + connected: true, + }, + { + name: "Account 4", + connected: false, + }, +]; + +const Connected = () => { + const { accounts } = useWalletConnection(); + + return ( + + + + Account + Connected + Actions + + + + {Accounts.map((account, i) => ( + + {account.name} + + + {account.connected ? "Yes" : "No"} + + + + + + + ))} + +
+ ); +}; +export default Connected; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx new file mode 100644 index 0000000000..49c2eddf19 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx @@ -0,0 +1,43 @@ +import { useWalletConnection } from "@/components/providers/wallet-provider"; +import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { + TooltipProvider, + Tooltip, + TooltipTrigger, + TooltipContent, +} from "@radix-ui/react-tooltip"; +import { Terminal, Badge, CopyIcon } from "lucide-react"; +import React from "react"; + +const CONNECTION_STEPS = [ + "Hit Connect to Ten and start your journey", + "Allow MetaMask to switch networks to the Ten Testnet", + "Sign the Signature Request (this is not a transaction)", +]; + +const Disconnected = () => { + const { connectToTenTestnet } = useWalletConnection(); + return ( +
+

Welcome to the Ten Gateway!

+

+ Three clicks to setup encrypted communication between MetaMask and TEN. +

+
    + {CONNECTION_STEPS.map((step, index) => ( +
  1. + +
  2. + ))} +
+ +
+ ); +}; + +export default Disconnected; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx index f22b93dcbc..ef3ebacd3f 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx @@ -1,7 +1,19 @@ import React from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { Terminal } from "lucide-react"; +import { useWalletConnection } from "@/components/providers/wallet-provider"; +import Connected from "./connected"; +import Disconnected from "./disconnected"; const Home = () => { - return
Home
; + const { walletConnected } = useWalletConnection(); + + return ( +
+ {walletConnected ? : } +
+ ); }; export default Home; diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx index 9dda4398d5..a5c992290b 100644 --- a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx @@ -5,6 +5,21 @@ import { WalletConnectionProviderProps, } from "@/types/interfaces/WalletInterfaces"; import { useToast } from "../ui/use-toast"; +import { + getNetworkName, + getRPCFromUrl, + getRandomIntAsString, + isValidUserIDFormat, + metamaskPersonalSign, + pathAuthenticate, + pathJoin, + pathQuery, + pathRevoke, + pathVersion, + tenChainIDHex, + tenGatewayVersion, +} from "@/lib/utils"; +import { set } from "date-fns"; const WalletConnectionContext = createContext(null); @@ -25,6 +40,9 @@ export const WalletConnectionProvider = ({ const { toast } = useToast(); const [walletConnected, setWalletConnected] = useState(false); + const [userID, setUserID] = useState(null); + const [version, setVersion] = useState(null); + const [accounts, setAccounts] = useState(null); const [walletAddress, setWalletAddress] = useState(null); const [provider, setProvider] = useState(null); @@ -59,13 +77,311 @@ export const WalletConnectionProvider = ({ } }; + async function getUserID() { + if (!provider) { + return null; + } + try { + if (await isTenChain()) { + return await provider.send("eth_getStorageAt", [ + "getUserID", + getRandomIntAsString(0, 1000), + null, + ]); + } else { + return null; + } + } catch (e) { + console.log(e); + return null; + } + } + + async function isTenChain() { + let currentChain = await (window as any).ethereum.request({ + method: "eth_chainId", + }); + return currentChain === tenChainIDHex; + } + + function checkIfMetamaskIsLoaded() { + if (window && (window as any).ethereum) { + handleEthereum(); + } else { + toast({ description: "Connecting to Metamask..." }); + window.addEventListener("ethereum#initialized", handleEthereum, { + once: true, + }); + + // If the event is not dispatched by the end of the timeout, + // the user probably doesn't have MetaMask installed. + setTimeout(handleEthereum, 3000); // 3 seconds + } + } + + function handleEthereum() { + const { ethereum } = window as any; + if (ethereum && ethereum.isMetaMask) { + initialize(); + } else { + toast({ description: "Please install MetaMask to use Obscuro Gateway." }); + } + } + + const initialize = async () => { + // getUserID from the gateway with getStorageAt method + let userID = await getUserID(); + + await displayCorrectScreenBasedOnMetamaskAndUserID(); + await fetchAndDisplayVersion(); + await displayCorrectScreenBasedOnMetamaskAndUserID(); + }; + + const revokeUserID = async () => { + const queryAccountUserID = pathRevoke + "?u=" + userID; + const revokeResponse = await fetch(queryAccountUserID, { + method: "get", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + }); + + if (revokeResponse.ok) { + setWalletConnected(false); + } else { + toast({ description: "Revoking UserID failed" }); + } + }; + + async function fetchAndDisplayVersion() { + try { + const versionResp = await fetch(pathVersion, { + method: "get", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + }); + if (!versionResp.ok) { + toast({ + description: "Failed to fetch the version. Please try again later.", + }); + throw new Error("Failed to fetch the version"); + } + + let response = await versionResp.text(); + setVersion(response); + } catch (error) { + console.error("Error fetching the version:", error); + toast({ description: `Error fetching the version: ${error}` }); + } + } + + async function displayCorrectScreenBasedOnMetamaskAndUserID() { + console.log("displayCorrectScreenBasedOnMetamaskAndUserID"); + // check if we are on Obscuro Chain + if (await isTenChain()) { + // check if we have valid userID in rpcURL + if (!provider) { + return; + } + if (userID && isValidUserIDFormat(userID)) { + const accounts = await provider.listAccounts(); + setAccounts(accounts); + } + setWalletConnected(true); + return; + } + setWalletConnected(false); + } + + async function switchToTenNetwork() { + try { + await (window as any).ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: tenChainIDHex }], + }); + return 0; + } catch (switchError: any) { + return switchError.code; + } + return -1; + } + + async function addNetworkToMetaMask() { + // add network to MetaMask + try { + await (window as any).ethereum.request({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: tenChainIDHex, + chainName: getNetworkName(), + nativeCurrency: { + name: "Sepolia Ether", + symbol: "ETH", + decimals: 18, + }, + rpcUrls: [ + getRPCFromUrl() + "/" + tenGatewayVersion + "/?u=" + userID, + ], + blockExplorerUrls: ["https://testnet.obscuroscan.io"], + }, + ], + }); + } catch (error) { + console.error(error); + return false; + } + return true; + } + + async function connectAccounts() { + try { + return await (window as any).ethereum.request({ + method: "eth_requestAccounts", + }); + } catch (error) { + // TODO: Display warning to user to allow it and refresh page... + console.error("User denied account access:", error); + toast({ description: `User denied account access: ${error}` }); + return null; + } + } + + async function isMetamaskConnected() { + let accounts; + if (!provider) { + return false; + } + try { + accounts = await provider.listAccounts(); + return accounts.length > 0; + } catch (error) { + console.log("Unable to get accounts"); + } + return false; + } + + async function accountIsAuthenticated(account: string) { + const queryAccountUserID = pathQuery + "?u=" + userID + "&a=" + account; + const isAuthenticatedResponse = await fetch(queryAccountUserID, { + method: "get", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + }); + let response = await isAuthenticatedResponse.text(); + let jsonResponseObject = JSON.parse(response); + return jsonResponseObject.status; + } + + async function authenticateAccountWithTenGateway(account: string) { + const isAuthenticated = await accountIsAuthenticated(account); + if (isAuthenticated) { + return "Account is already authenticated"; + } + + const textToSign = "Register " + userID + " for " + account.toLowerCase(); + const signature = await (window as any).ethereum + .request({ + method: metamaskPersonalSign, + params: [textToSign, account], + }) + .catch((error: any) => { + return -1; + }); + if (signature === -1) { + return "Signing failed"; + } + + const authenticateUserURL = pathAuthenticate + "?u=" + userID; + const authenticateFields = { signature: signature, message: textToSign }; + const authenticateResp = await fetch(authenticateUserURL, { + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(authenticateFields), + }); + return await authenticateResp.text(); + } + + const connectToTenTestnet = async () => { + // check if we are on an Ten chain + if (await isTenChain()) { + const user = await getUserID(); + setUserID(user); + if (!isValidUserIDFormat(user)) { + toast({ + description: + "Existing Ten network detected in MetaMask. Please remove before hitting begin", + }); + } + } else { + // we are not on a Ten network - try to switch + let switched = await switchToTenNetwork(); + // error 4902 means that the chain does not exist + if (switched === 4902 || !isValidUserIDFormat(await getUserID())) { + // join the network + const joinResp = await fetch(pathJoin, { + method: "get", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + }); + if (!joinResp.ok) { + console.log("Error joining Ten Gateway"); + toast({ + description: "Error joining Ten Gateway. Please try again later.", + }); + return; + } + const user = await joinResp.text(); + setUserID(user); + + // add Ten network + await addNetworkToMetaMask(); + } + + // we have to check if user has accounts connected with metamask - and prompt to connect if not + if (!(await isMetamaskConnected())) { + await connectAccounts(); + } + + if (!provider) { + return; + } + + // connect all accounts + // Get an accounts and prompt user to sign joining with a selected account + const accounts = await provider.listAccounts(); + if (accounts.length === 0) { + toast({ description: "No MetaMask accounts found." }); + return; + } + + for (const account of accounts) { + await authenticateAccountWithTenGateway(account); + } + } + }; + useEffect(() => { const ethereum = (window as any).ethereum; - const handleAccountsChanged = (accounts: string[]) => { + const handleAccountsChanged = async (accounts: string[]) => { if (accounts.length === 0) { toast({ description: "Please connect to MetaMask." }); - } else if (accounts[0] !== walletAddress) { - setWalletAddress(accounts[0]); + } else { + if (userID && isValidUserIDFormat(userID)) { + for (const account of accounts) { + await authenticateAccountWithTenGateway(account); + } + } } }; ethereum.on("accountsChanged", handleAccountsChanged); @@ -74,12 +390,26 @@ export const WalletConnectionProvider = ({ }; }); + const user: any = async () => await getUserID(); + useEffect(() => { + if (window) { + const ethProvider = new ethers.providers.Web3Provider( + (window as any).ethereum + ); + setProvider(ethProvider); + setUserID(user); + fetchAndDisplayVersion(); + displayCorrectScreenBasedOnMetamaskAndUserID(); + } + }, []); + const walletConnectionContextValue: WalletConnectionContextType = { - provider, walletConnected, walletAddress, connectWallet, disconnectWallet, + connectToTenTestnet, + accounts, }; return ( diff --git a/tools/walletextension/api/ten-gateway/src/lib/utils.ts b/tools/walletextension/api/ten-gateway/src/lib/utils.ts index 628a129218..db6e67b0b0 100644 --- a/tools/walletextension/api/ten-gateway/src/lib/utils.ts +++ b/tools/walletextension/api/ten-gateway/src/lib/utils.ts @@ -10,3 +10,56 @@ export function formatTimeAgo(unixTimestampSeconds: string) { const date = new Date(Number(unixTimestampSeconds) * 1000); return formatDistanceToNow(date, { addSuffix: true }); } + +const gatewayaddress = "https://testnet.obscu.ro"; +export const tenscanLink = "https://testnet.tenscan.com"; +export const tenGatewayVersion = "v1"; +export const pathJoin = gatewayaddress + "/" + tenGatewayVersion + "/join/"; +export const pathAuthenticate = + gatewayaddress + "/" + tenGatewayVersion + "/authenticate/"; +export const pathQuery = gatewayaddress + "/" + tenGatewayVersion + "/query/"; +export const pathRevoke = gatewayaddress + "/" + tenGatewayVersion + "/revoke/"; +export const pathVersion = gatewayaddress + "/" + "version/"; +export const tenChainIDDecimal = 443; + +export const metamaskPersonalSign = "personal_sign"; +export const tenChainIDHex = "0x" + tenChainIDDecimal.toString(16); // Convert to hexadecimal and prefix with '0x' + +export function isValidUserIDFormat(value: string) { + return typeof value === "string" && value.length === 64; +} + +export let tenGatewayAddress = gatewayaddress; + +export function getRandomIntAsString(min: number, max: number) { + min = Math.ceil(min); + max = Math.floor(max); + const randomInt = Math.floor(Math.random() * (max - min + 1)) + min; + return randomInt.toString(); +} +export function getNetworkName() { + switch (tenGatewayAddress) { + case "https://uat-testnet.obscu.ro": + return "Ten UAT-Testnet"; + case "https://dev-testnet.obscu.ro": + return "Ten Dev-Testnet"; + default: + return "Ten Testnet"; + } +} + +export function getRPCFromUrl() { + // get the correct RPC endpoint for each network + switch (tenGatewayAddress) { + // case 'https://testnet.obscu.ro': + // return 'https://rpc.sepolia-testnet.obscu.ro' + case "https://sepolia-testnet.obscu.ro": + return "https://rpc.sepolia-testnet.obscu.ro"; + case "https://uat-testnet.obscu.ro": + return "https://rpc.uat-testnet.obscu.ro"; + case "https://dev-testnet.obscu.ro": + return "https://rpc.dev-testnet.obscu.ro"; + default: + return tenGatewayAddress; + } +} diff --git a/tools/walletextension/api/ten-gateway/src/pages/_app.tsx b/tools/walletextension/api/ten-gateway/src/pages/_app.tsx index 173cbe32fb..e297ff07e3 100644 --- a/tools/walletextension/api/ten-gateway/src/pages/_app.tsx +++ b/tools/walletextension/api/ten-gateway/src/pages/_app.tsx @@ -1,5 +1,4 @@ import { ThemeProvider } from "@/components/providers/theme-provider"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import "@/styles/globals.css"; import type { AppProps } from "next/app"; import { Toaster } from "@/components/ui/toaster"; @@ -18,7 +17,6 @@ export default function App({ Component, pageProps }: AppProps) { - ); diff --git a/tools/walletextension/api/ten-gateway/src/pages/index.tsx b/tools/walletextension/api/ten-gateway/src/pages/index.tsx index 6864e40283..3966e09fa2 100644 --- a/tools/walletextension/api/ten-gateway/src/pages/index.tsx +++ b/tools/walletextension/api/ten-gateway/src/pages/index.tsx @@ -8,10 +8,12 @@ export const metadata: Metadata = { description: "Tenscan Gateway: A gateway to the Tenscan ecosystem", }; -export default function DashboardPage() { +export default function HomePage() { return ( - +
+ +
); } diff --git a/tools/walletextension/api/ten-gateway/src/routes/index.ts b/tools/walletextension/api/ten-gateway/src/routes/index.ts index 2120379e6a..691771f090 100644 --- a/tools/walletextension/api/ten-gateway/src/routes/index.ts +++ b/tools/walletextension/api/ten-gateway/src/routes/index.ts @@ -1,55 +1,3 @@ import { NavLink } from "../types/interfaces"; -export const NavLinks: NavLink[] = [ - { - href: "/", - label: "Home", - isExternal: false, - isDropdown: false, - }, - { - href: "/personal", - label: "Personal", - isExternal: false, - isDropdown: false, - }, - { - label: "Blockchain", - isExternal: false, - isDropdown: true, - subNavLinks: [ - { - href: "/transactions", - label: "Transactions", - isExternal: false, - }, - { - href: "/blocks", - label: "Blocks", - isExternal: false, - }, - { - href: "/batches", - label: "Batches", - isExternal: false, - }, - ], - }, - { - label: "Resources", - isExternal: false, - isDropdown: true, - subNavLinks: [ - { - href: "/resources/decrypt", - label: "Decrypt", - isExternal: false, - }, - { - href: "/resources/verified-data", - label: "Verified Data", - isExternal: false, - }, - ], - }, -]; +export const NavLinks: NavLink[] = []; diff --git a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts index c9cbb55c0b..e1371a8da3 100644 --- a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts @@ -1,7 +1,8 @@ import { ethers } from "ethers"; export interface WalletConnectionContextType { - provider: ethers.providers.Web3Provider | null; + connectToTenTestnet: () => Promise; + accounts: string[] | null; walletConnected: boolean; walletAddress: string | null; connectWallet: () => Promise; diff --git a/tools/walletextension/api/ten-gateway/tailwind.config.js b/tools/walletextension/api/ten-gateway/tailwind.config.js new file mode 100644 index 0000000000..9ed2625d76 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/tailwind.config.js @@ -0,0 +1,84 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: [ + "./pages/**/*.{ts,tsx}", + "./src/components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", + ], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + success: { + DEFAULT: "hsl(var(--success))", + foreground: "hsl(var(--success-foreground))", + }, + warning: { + DEFAULT: "hsl(var(--warning))", + foreground: "hsl(var(--warning-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: 0 }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: 0 }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; diff --git a/tools/walletextension/api/ten-gateway/tailwind.config.ts b/tools/walletextension/api/ten-gateway/tailwind.config.ts index 1af3b8f019..89c2e4711e 100644 --- a/tools/walletextension/api/ten-gateway/tailwind.config.ts +++ b/tools/walletextension/api/ten-gateway/tailwind.config.ts @@ -1,20 +1,91 @@ -import type { Config } from 'tailwindcss' +import type { Config } from "tailwindcss"; +const { fontFamily } = require("tailwindcss/defaultTheme"); const config: Config = { + darkMode: ["class"], content: [ - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - './src/app/**/*.{js,ts,jsx,tsx,mdx}', + "./src/**/*.{ts,tsx}", + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, extend: { - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': - 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + success: { + DEFAULT: "hsl(var(--success))", + foreground: "hsl(var(--success-foreground))", + }, + warning: { + DEFAULT: "hsl(var(--warning))", + foreground: "hsl(var(--warning-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive) / )", + foreground: "hsl(var(--destructive-foreground) / )", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + xl: `calc(var(--radius) + 4px)`, + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: ["Cloud Soft", ...fontFamily.sans], + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", }, }, }, - plugins: [], -} -export default config + plugins: [require("tailwindcss-animate")], +}; +export default config; From dd41b92dcc575b8a1e8a9bcd7775237b733b7982 Mon Sep 17 00:00:00 2001 From: Jennie Date: Wed, 22 Nov 2023 04:22:01 +0400 Subject: [PATCH 03/36] refactor: UI and logic --- .../src/components/layouts/default-layout.tsx | 2 +- .../src/components/modules/home/connected.tsx | 121 ++++++---- .../components/providers/wallet-provider.tsx | 222 +++++++++--------- .../ten-gateway/src/components/ui/button.tsx | 23 +- .../api/ten-gateway/src/lib/utils.ts | 7 + .../src/types/interfaces/WalletInterfaces.ts | 13 +- 6 files changed, 220 insertions(+), 168 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx b/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx index af5d0ef13a..2c4b262837 100644 --- a/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx @@ -9,7 +9,7 @@ interface LayoutProps { const Layout = ({ children }: LayoutProps) => { return (
-
+
{children}
diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx index c4df3e202e..c8cda32d5e 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx @@ -1,6 +1,6 @@ import { useWalletConnection } from "@/components/providers/wallet-provider"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Button, LinkButton } from "@/components/ui/button"; import { Table, TableBody, @@ -9,57 +9,86 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { Account } from "@/types/interfaces/WalletInterfaces"; import React from "react"; - -const Accounts = [ - { - name: "Account 1", - connected: true, - }, - { - name: "Account 2", - connected: true, - }, - { - name: "Account 3", - connected: true, - }, - { - name: "Account 4", - connected: false, - }, -]; +import TruncatedAddress from "../common/truncated-address"; +import { socialLinks } from "@/lib/constants"; +import { Skeleton } from "@/components/ui/skeleton"; const Connected = () => { - const { accounts } = useWalletConnection(); + const { accounts, connectAccount, disconnectAccount, revokeAccounts } = + useWalletConnection(); + console.log("🚀 ~ file: connected.tsx:20 ~ Connected ~ accounts:", accounts); return ( - - - - Account - Connected - Actions - - - - {Accounts.map((account, i) => ( - - {account.name} - - - {account.connected ? "Yes" : "No"} - - - - - + <> +
+

Connected Accounts

+

+ Manage the accounts you have connected to the Ten Gateway. You can + revoke access to your accounts at any time and request new tokens from + the Ten Discord. +

+
+ + Request Tokens + + +
+
+
+ + + Account + Connected + Actions - ))} - -
+ + + {!accounts ? ( + + + + ) : accounts.length === 0 ? ( + + + No accounts connected + + + ) : ( + accounts.map((account: Account, i: number) => ( + + + + + + + {account.connected ? "Yes" : "No"} + + + + + + + )) + )} + +
+ ); }; export default Connected; diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx index a5c992290b..72fb20a4d2 100644 --- a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx @@ -3,12 +3,14 @@ import { ethers } from "ethers"; import { WalletConnectionContextType, WalletConnectionProviderProps, + Account, } from "@/types/interfaces/WalletInterfaces"; import { useToast } from "../ui/use-toast"; import { getNetworkName, getRPCFromUrl, getRandomIntAsString, + isTenChain, isValidUserIDFormat, metamaskPersonalSign, pathAuthenticate, @@ -18,8 +20,8 @@ import { pathVersion, tenChainIDHex, tenGatewayVersion, + tenscanLink, } from "@/lib/utils"; -import { set } from "date-fns"; const WalletConnectionContext = createContext(null); @@ -42,70 +44,39 @@ export const WalletConnectionProvider = ({ const [walletConnected, setWalletConnected] = useState(false); const [userID, setUserID] = useState(null); const [version, setVersion] = useState(null); - const [accounts, setAccounts] = useState(null); - const [walletAddress, setWalletAddress] = useState(null); + const [accounts, setAccounts] = useState(null); const [provider, setProvider] = useState(null); - const connectWallet = async () => { - if ((window as any).ethereum) { - const ethProvider = new ethers.providers.Web3Provider( - (window as any).ethereum - ); - setProvider(ethProvider); - - try { - await ethProvider.send("eth_requestAccounts", []); - const signer = ethProvider.getSigner(); - const address = await signer.getAddress(); - setWalletAddress(address); - setWalletConnected(true); - } catch (error: any) { - toast({ description: "Error connecting to wallet:" + error.message }); - } - } else { - toast({ description: "No ethereum object found." }); - } - }; - - const disconnectWallet = () => { - if (provider) { - provider.removeAllListeners(); - setWalletConnected(false); - setWalletAddress(null); - setProvider(null); - } - }; - - async function getUserID() { - if (!provider) { - return null; - } - try { - if (await isTenChain()) { - return await provider.send("eth_getStorageAt", [ - "getUserID", - getRandomIntAsString(0, 1000), - null, - ]); + useEffect(() => { + const ethereum = (window as any).ethereum; + const handleAccountsChanged = async (accounts: string[]) => { + if (accounts.length === 0) { + toast({ description: "Please connect to MetaMask." }); } else { - return null; + if (userID && isValidUserIDFormat(userID)) { + for (const account of accounts) { + await authenticateAccountWithTenGateway(account); + } + } } - } catch (e) { - console.log(e); - return null; - } - } + }; + ethereum.on("accountsChanged", handleAccountsChanged); + return () => { + ethereum.removeListener("accountsChanged", handleAccountsChanged); + }; + }); - async function isTenChain() { - let currentChain = await (window as any).ethereum.request({ - method: "eth_chainId", - }); - return currentChain === tenChainIDHex; - } + useEffect(() => { + checkIfMetamaskIsLoaded(); + }, []); function checkIfMetamaskIsLoaded() { if (window && (window as any).ethereum) { + const provider = new ethers.providers.Web3Provider( + (window as any).ethereum + ); + setProvider(provider); handleEthereum(); } else { toast({ description: "Connecting to Metamask..." }); @@ -124,36 +95,38 @@ export const WalletConnectionProvider = ({ if (ethereum && ethereum.isMetaMask) { initialize(); } else { - toast({ description: "Please install MetaMask to use Obscuro Gateway." }); + toast({ description: "Please install MetaMask to use Ten Gateway." }); + } + } + + async function getUserID() { + if (!provider) { + return null; + } + try { + if (await isTenChain()) { + return await provider.send("eth_getStorageAt", [ + "getUserID", + getRandomIntAsString(0, 1000), + null, + ]); + } else { + return null; + } + } catch (e) { + console.log(e); + return null; } } const initialize = async () => { // getUserID from the gateway with getStorageAt method let userID = await getUserID(); - - await displayCorrectScreenBasedOnMetamaskAndUserID(); + setUserID(userID); await fetchAndDisplayVersion(); await displayCorrectScreenBasedOnMetamaskAndUserID(); }; - const revokeUserID = async () => { - const queryAccountUserID = pathRevoke + "?u=" + userID; - const revokeResponse = await fetch(queryAccountUserID, { - method: "get", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - }); - - if (revokeResponse.ok) { - setWalletConnected(false); - } else { - toast({ description: "Revoking UserID failed" }); - } - }; - async function fetchAndDisplayVersion() { try { const versionResp = await fetch(pathVersion, { @@ -179,8 +152,7 @@ export const WalletConnectionProvider = ({ } async function displayCorrectScreenBasedOnMetamaskAndUserID() { - console.log("displayCorrectScreenBasedOnMetamaskAndUserID"); - // check if we are on Obscuro Chain + // check if we are on Ten Chain if (await isTenChain()) { // check if we have valid userID in rpcURL if (!provider) { @@ -188,7 +160,15 @@ export const WalletConnectionProvider = ({ } if (userID && isValidUserIDFormat(userID)) { const accounts = await provider.listAccounts(); - setAccounts(accounts); + const formattedAccounts: Account[] = []; + for (const account of accounts) { + const isConnected = await accountIsAuthenticated(account); + formattedAccounts.push({ + name: account, + connected: isConnected, + }); + } + setAccounts(formattedAccounts); } setWalletConnected(true); return; @@ -226,7 +206,7 @@ export const WalletConnectionProvider = ({ rpcUrls: [ getRPCFromUrl() + "/" + tenGatewayVersion + "/?u=" + userID, ], - blockExplorerUrls: ["https://testnet.obscuroscan.io"], + blockExplorerUrls: [tenscanLink], }, ], }); @@ -310,6 +290,23 @@ export const WalletConnectionProvider = ({ return await authenticateResp.text(); } + const revokeAccounts = async () => { + const queryAccountUserID = pathRevoke + "?u=" + userID; + const revokeResponse = await fetch(queryAccountUserID, { + method: "get", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + }); + + if (revokeResponse.ok) { + setWalletConnected(false); + } else { + toast({ description: "Revoking UserID failed" }); + } + }; + const connectToTenTestnet = async () => { // check if we are on an Ten chain if (await isTenChain()) { @@ -364,52 +361,45 @@ export const WalletConnectionProvider = ({ toast({ description: "No MetaMask accounts found." }); return; } - - for (const account of accounts) { - await authenticateAccountWithTenGateway(account); - } } }; - useEffect(() => { - const ethereum = (window as any).ethereum; - const handleAccountsChanged = async (accounts: string[]) => { - if (accounts.length === 0) { - toast({ description: "Please connect to MetaMask." }); - } else { - if (userID && isValidUserIDFormat(userID)) { - for (const account of accounts) { - await authenticateAccountWithTenGateway(account); - } - } - } - }; - ethereum.on("accountsChanged", handleAccountsChanged); - return () => { - ethereum.removeListener("accountsChanged", handleAccountsChanged); - }; - }); + const connectAccount = async (account: string) => { + await authenticateAccountWithTenGateway(account); + }; - const user: any = async () => await getUserID(); - useEffect(() => { - if (window) { - const ethProvider = new ethers.providers.Web3Provider( - (window as any).ethereum - ); - setProvider(ethProvider); - setUserID(user); - fetchAndDisplayVersion(); - displayCorrectScreenBasedOnMetamaskAndUserID(); + const disconnectAccount = async (account: string) => { + const revokeAccountURL = pathRevoke + "?u=" + userID + "&a=" + account; + const revokeAccountResp = await fetch(revokeAccountURL, { + method: "get", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + }); + + if (revokeAccountResp.ok) { + const formattedAccounts: Account[] = []; + for (const account of accounts!) { + const isConnected = await accountIsAuthenticated(account.name); + formattedAccounts.push({ + name: account.name, + connected: isConnected, + }); + } + setAccounts(formattedAccounts); + } else { + toast({ description: "Revoking account failed" }); } - }, []); + }; const walletConnectionContextValue: WalletConnectionContextType = { walletConnected, - walletAddress, - connectWallet, - disconnectWallet, connectToTenTestnet, accounts, + revokeAccounts, + connectAccount, + disconnectAccount, }; return ( diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx index 150645b649..d04c1d48c9 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx @@ -55,4 +55,25 @@ const Button = React.forwardRef( ); Button.displayName = "Button"; -export { Button, buttonVariants }; +interface LinkButtonProps + extends React.AnchorHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const LinkButton = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "a"; + return ( + + ); + } +); + +LinkButton.displayName = "LinkButton"; + +export { Button, buttonVariants, LinkButton }; diff --git a/tools/walletextension/api/ten-gateway/src/lib/utils.ts b/tools/walletextension/api/ten-gateway/src/lib/utils.ts index db6e67b0b0..8f13fc9869 100644 --- a/tools/walletextension/api/ten-gateway/src/lib/utils.ts +++ b/tools/walletextension/api/ten-gateway/src/lib/utils.ts @@ -63,3 +63,10 @@ export function getRPCFromUrl() { return tenGatewayAddress; } } + +export async function isTenChain() { + let currentChain = await (window as any).ethereum.request({ + method: "eth_chainId", + }); + return currentChain === tenChainIDHex; +} diff --git a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts index e1371a8da3..188f237fcc 100644 --- a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts @@ -2,11 +2,11 @@ import { ethers } from "ethers"; export interface WalletConnectionContextType { connectToTenTestnet: () => Promise; - accounts: string[] | null; + accounts: Account[] | null; walletConnected: boolean; - walletAddress: string | null; - connectWallet: () => Promise; - disconnectWallet: () => void; + connectAccount: (account: string) => Promise; + disconnectAccount: (account: string) => Promise; + revokeAccounts: () => Promise; } export interface Props { @@ -20,3 +20,8 @@ export interface State { export interface WalletConnectionProviderProps { children: React.ReactNode; } + +export type Account = { + name: string; + connected: boolean; +}; From 305219153e7ec75935270c6a13f1fb23cd207c51 Mon Sep 17 00:00:00 2001 From: Jennie Date: Wed, 22 Nov 2023 04:53:39 +0400 Subject: [PATCH 04/36] chore: move constants and account connections --- .../src/components/modules/home/connected.tsx | 27 ++- .../components/providers/wallet-provider.tsx | 163 ++++++++---------- .../api/ten-gateway/src/lib/utils.ts | 1 + .../src/types/interfaces/WalletInterfaces.ts | 1 - 4 files changed, 86 insertions(+), 106 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx index c8cda32d5e..098d45fad6 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx @@ -16,9 +16,7 @@ import { socialLinks } from "@/lib/constants"; import { Skeleton } from "@/components/ui/skeleton"; const Connected = () => { - const { accounts, connectAccount, disconnectAccount, revokeAccounts } = - useWalletConnection(); - console.log("🚀 ~ file: connected.tsx:20 ~ Connected ~ accounts:", accounts); + const { accounts, connectAccount, revokeAccounts } = useWalletConnection(); return ( <> @@ -30,7 +28,7 @@ const Connected = () => { the Ten Discord.

- + Request Tokens + {!account.connected && ( + + )} )) diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx index 72fb20a4d2..4dd69ecad2 100644 --- a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx @@ -7,6 +7,7 @@ import { } from "@/types/interfaces/WalletInterfaces"; import { useToast } from "../ui/use-toast"; import { + METAMASK_CONNECTION_TIMEOUT, getNetworkName, getRPCFromUrl, getRandomIntAsString, @@ -50,28 +51,27 @@ export const WalletConnectionProvider = ({ useEffect(() => { const ethereum = (window as any).ethereum; + const handleAccountsChanged = async (accounts: string[]) => { if (accounts.length === 0) { toast({ description: "Please connect to MetaMask." }); - } else { - if (userID && isValidUserIDFormat(userID)) { - for (const account of accounts) { - await authenticateAccountWithTenGateway(account); - } - } + } else if (userID && isValidUserIDFormat(userID)) { + await Promise.all(accounts.map(authenticateAccountWithTenGateway)); } }; + ethereum.on("accountsChanged", handleAccountsChanged); + return () => { ethereum.removeListener("accountsChanged", handleAccountsChanged); }; - }); + }, [userID]); useEffect(() => { checkIfMetamaskIsLoaded(); }, []); - function checkIfMetamaskIsLoaded() { + async function checkIfMetamaskIsLoaded() { if (window && (window as any).ethereum) { const provider = new ethers.providers.Web3Provider( (window as any).ethereum @@ -79,19 +79,18 @@ export const WalletConnectionProvider = ({ setProvider(provider); handleEthereum(); } else { - toast({ description: "Connecting to Metamask..." }); + toast({ description: "Connecting to MetaMask..." }); + window.addEventListener("ethereum#initialized", handleEthereum, { once: true, }); - - // If the event is not dispatched by the end of the timeout, - // the user probably doesn't have MetaMask installed. - setTimeout(handleEthereum, 3000); // 3 seconds + setTimeout(handleEthereum, METAMASK_CONNECTION_TIMEOUT); } } function handleEthereum() { const { ethereum } = window as any; + if (ethereum && ethereum.isMetaMask) { initialize(); } else { @@ -100,28 +99,24 @@ export const WalletConnectionProvider = ({ } async function getUserID() { - if (!provider) { + if (!provider || !(await isTenChain())) { return null; } + try { - if (await isTenChain()) { - return await provider.send("eth_getStorageAt", [ - "getUserID", - getRandomIntAsString(0, 1000), - null, - ]); - } else { - return null; - } + return await provider.send("eth_getStorageAt", [ + "getUserID", + getRandomIntAsString(0, 1000), + null, + ]); } catch (e) { - console.log(e); + console.error(e); return null; } } const initialize = async () => { - // getUserID from the gateway with getStorageAt method - let userID = await getUserID(); + const userID = await getUserID(); setUserID(userID); await fetchAndDisplayVersion(); await displayCorrectScreenBasedOnMetamaskAndUserID(); @@ -136,44 +131,37 @@ export const WalletConnectionProvider = ({ "Content-Type": "application/json", }, }); + if (!versionResp.ok) { - toast({ - description: "Failed to fetch the version. Please try again later.", - }); - throw new Error("Failed to fetch the version"); + handleFetchError("Failed to fetch the version"); + return; } - let response = await versionResp.text(); + const response = await versionResp.text(); setVersion(response); } catch (error) { - console.error("Error fetching the version:", error); - toast({ description: `Error fetching the version: ${error}` }); + handleFetchError(`Error fetching the version: ${error}`); } } async function displayCorrectScreenBasedOnMetamaskAndUserID() { - // check if we are on Ten Chain if (await isTenChain()) { - // check if we have valid userID in rpcURL - if (!provider) { - return; - } - if (userID && isValidUserIDFormat(userID)) { + if (provider && userID && isValidUserIDFormat(userID)) { const accounts = await provider.listAccounts(); - const formattedAccounts: Account[] = []; - for (const account of accounts) { - const isConnected = await accountIsAuthenticated(account); - formattedAccounts.push({ + const formattedAccounts = await Promise.all( + accounts.map(async (account) => ({ name: account, - connected: isConnected, - }); - } + connected: await accountIsAuthenticated(account), + })) + ); + setAccounts(formattedAccounts); } + setWalletConnected(true); - return; + } else { + setWalletConnected(false); } - setWalletConnected(false); } async function switchToTenNetwork() { @@ -182,15 +170,14 @@ export const WalletConnectionProvider = ({ method: "wallet_switchEthereumChain", params: [{ chainId: tenChainIDHex }], }); + return 0; } catch (switchError: any) { return switchError.code; } - return -1; } async function addNetworkToMetaMask() { - // add network to MetaMask try { await (window as any).ethereum.request({ method: "wallet_addEthereumChain", @@ -203,9 +190,7 @@ export const WalletConnectionProvider = ({ symbol: "ETH", decimals: 18, }, - rpcUrls: [ - getRPCFromUrl() + "/" + tenGatewayVersion + "/?u=" + userID, - ], + rpcUrls: [`${getRPCFromUrl()}/${tenGatewayVersion}/?u=${userID}`], blockExplorerUrls: [tenscanLink], }, ], @@ -214,6 +199,7 @@ export const WalletConnectionProvider = ({ console.error(error); return false; } + return true; } @@ -223,7 +209,6 @@ export const WalletConnectionProvider = ({ method: "eth_requestAccounts", }); } catch (error) { - // TODO: Display warning to user to allow it and refresh page... console.error("User denied account access:", error); toast({ description: `User denied account access: ${error}` }); return null; @@ -231,21 +216,22 @@ export const WalletConnectionProvider = ({ } async function isMetamaskConnected() { - let accounts; if (!provider) { return false; } + try { - accounts = await provider.listAccounts(); + const accounts = await provider.listAccounts(); return accounts.length > 0; } catch (error) { console.log("Unable to get accounts"); } + return false; } async function accountIsAuthenticated(account: string) { - const queryAccountUserID = pathQuery + "?u=" + userID + "&a=" + account; + const queryAccountUserID = `${pathQuery}?u=${userID}&a=${account}`; const isAuthenticatedResponse = await fetch(queryAccountUserID, { method: "get", headers: { @@ -253,32 +239,33 @@ export const WalletConnectionProvider = ({ "Content-Type": "application/json", }, }); - let response = await isAuthenticatedResponse.text(); - let jsonResponseObject = JSON.parse(response); + + const response = await isAuthenticatedResponse.text(); + const jsonResponseObject = JSON.parse(response); return jsonResponseObject.status; } async function authenticateAccountWithTenGateway(account: string) { const isAuthenticated = await accountIsAuthenticated(account); + if (isAuthenticated) { return "Account is already authenticated"; } - const textToSign = "Register " + userID + " for " + account.toLowerCase(); + const textToSign = `Register ${userID} for ${account.toLowerCase()}`; const signature = await (window as any).ethereum .request({ method: metamaskPersonalSign, params: [textToSign, account], }) - .catch((error: any) => { - return -1; - }); + .catch((error: any) => -1); + if (signature === -1) { return "Signing failed"; } - const authenticateUserURL = pathAuthenticate + "?u=" + userID; - const authenticateFields = { signature: signature, message: textToSign }; + const authenticateUserURL = `${pathAuthenticate}?u=${userID}`; + const authenticateFields = { signature, message: textToSign }; const authenticateResp = await fetch(authenticateUserURL, { method: "post", headers: { @@ -287,11 +274,12 @@ export const WalletConnectionProvider = ({ }, body: JSON.stringify(authenticateFields), }); + return await authenticateResp.text(); } const revokeAccounts = async () => { - const queryAccountUserID = pathRevoke + "?u=" + userID; + const queryAccountUserID = `${pathRevoke}?u=${userID}`; const revokeResponse = await fetch(queryAccountUserID, { method: "get", headers: { @@ -308,10 +296,10 @@ export const WalletConnectionProvider = ({ }; const connectToTenTestnet = async () => { - // check if we are on an Ten chain if (await isTenChain()) { const user = await getUserID(); setUserID(user); + if (!isValidUserIDFormat(user)) { toast({ description: @@ -319,11 +307,9 @@ export const WalletConnectionProvider = ({ }); } } else { - // we are not on a Ten network - try to switch - let switched = await switchToTenNetwork(); - // error 4902 means that the chain does not exist + const switched = await switchToTenNetwork(); + if (switched === 4902 || !isValidUserIDFormat(await getUserID())) { - // join the network const joinResp = await fetch(pathJoin, { method: "get", headers: { @@ -331,21 +317,18 @@ export const WalletConnectionProvider = ({ "Content-Type": "application/json", }, }); + if (!joinResp.ok) { - console.log("Error joining Ten Gateway"); - toast({ - description: "Error joining Ten Gateway. Please try again later.", - }); + handleFetchError("Error joining Ten Gateway"); return; } + const user = await joinResp.text(); setUserID(user); - // add Ten network await addNetworkToMetaMask(); } - // we have to check if user has accounts connected with metamask - and prompt to connect if not if (!(await isMetamaskConnected())) { await connectAccounts(); } @@ -354,9 +337,8 @@ export const WalletConnectionProvider = ({ return; } - // connect all accounts - // Get an accounts and prompt user to sign joining with a selected account const accounts = await provider.listAccounts(); + if (accounts.length === 0) { toast({ description: "No MetaMask accounts found." }); return; @@ -369,7 +351,7 @@ export const WalletConnectionProvider = ({ }; const disconnectAccount = async (account: string) => { - const revokeAccountURL = pathRevoke + "?u=" + userID + "&a=" + account; + const revokeAccountURL = `${pathRevoke}?u=${userID}&a=${account}`; const revokeAccountResp = await fetch(revokeAccountURL, { method: "get", headers: { @@ -379,14 +361,13 @@ export const WalletConnectionProvider = ({ }); if (revokeAccountResp.ok) { - const formattedAccounts: Account[] = []; - for (const account of accounts!) { - const isConnected = await accountIsAuthenticated(account.name); - formattedAccounts.push({ - name: account.name, - connected: isConnected, - }); - } + const formattedAccounts = await Promise.all( + accounts!.map(async (acc) => ({ + name: acc.name, + connected: await accountIsAuthenticated(acc.name), + })) + ); + setAccounts(formattedAccounts); } else { toast({ description: "Revoking account failed" }); @@ -399,7 +380,6 @@ export const WalletConnectionProvider = ({ accounts, revokeAccounts, connectAccount, - disconnectAccount, }; return ( @@ -407,4 +387,9 @@ export const WalletConnectionProvider = ({ {children} ); + + function handleFetchError(errorMessage: string) { + console.error(`Error: ${errorMessage}`); + toast({ description: `${errorMessage}. Please try again later.` }); + } }; diff --git a/tools/walletextension/api/ten-gateway/src/lib/utils.ts b/tools/walletextension/api/ten-gateway/src/lib/utils.ts index 8f13fc9869..513a270319 100644 --- a/tools/walletextension/api/ten-gateway/src/lib/utils.ts +++ b/tools/walletextension/api/ten-gateway/src/lib/utils.ts @@ -24,6 +24,7 @@ export const tenChainIDDecimal = 443; export const metamaskPersonalSign = "personal_sign"; export const tenChainIDHex = "0x" + tenChainIDDecimal.toString(16); // Convert to hexadecimal and prefix with '0x' +export const METAMASK_CONNECTION_TIMEOUT = 3000; export function isValidUserIDFormat(value: string) { return typeof value === "string" && value.length === 64; diff --git a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts index 188f237fcc..4492f1f4e5 100644 --- a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts @@ -5,7 +5,6 @@ export interface WalletConnectionContextType { accounts: Account[] | null; walletConnected: boolean; connectAccount: (account: string) => Promise; - disconnectAccount: (account: string) => Promise; revokeAccounts: () => Promise; } From 3fca27eca7f6952eff2ae26717be41b7fea11ae0 Mon Sep 17 00:00:00 2001 From: Jennie Date: Wed, 22 Nov 2023 13:55:17 +0400 Subject: [PATCH 05/36] refactor: services and wallet context --- .../src/components/modules/common/copy.tsx | 22 ++ .../modules/common/truncated-address.tsx | 10 +- .../api/ten-gateway/package-lock.json | 2 +- .../api/ten-gateway/package.json | 2 +- .../api/ten-gateway/src/api/gateway.ts | 130 +++++++++ .../api/ten-gateway/src/api/index.ts | 69 +++++ .../src/components/modules/common/copy.tsx | 22 ++ .../modules/common/truncated-address.tsx | 9 +- .../src/components/modules/home/connected.tsx | 9 +- .../components/modules/home/disconnected.tsx | 87 +++++- .../components/providers/wallet-provider.tsx | 273 ++---------------- .../api/ten-gateway/src/lib/constants.ts | 35 +++ .../api/ten-gateway/src/lib/utils.ts | 44 +-- .../api/ten-gateway/src/pages/api/hello.ts | 13 - .../api/ten-gateway/src/routes/index.ts | 9 + .../api/ten-gateway/src/routes/router.ts | 5 + .../src/services/useGatewayService.ts | 91 ++++++ .../src/types/interfaces/WalletInterfaces.ts | 7 +- 18 files changed, 524 insertions(+), 315 deletions(-) create mode 100644 tools/obscuroscan_v3/frontend/src/components/modules/common/copy.tsx create mode 100644 tools/walletextension/api/ten-gateway/src/api/gateway.ts create mode 100644 tools/walletextension/api/ten-gateway/src/api/index.ts create mode 100644 tools/walletextension/api/ten-gateway/src/components/modules/common/copy.tsx delete mode 100644 tools/walletextension/api/ten-gateway/src/pages/api/hello.ts create mode 100644 tools/walletextension/api/ten-gateway/src/routes/router.ts create mode 100644 tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts diff --git a/tools/obscuroscan_v3/frontend/src/components/modules/common/copy.tsx b/tools/obscuroscan_v3/frontend/src/components/modules/common/copy.tsx new file mode 100644 index 0000000000..391562623f --- /dev/null +++ b/tools/obscuroscan_v3/frontend/src/components/modules/common/copy.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { CopyIcon, CheckIcon } from "@radix-ui/react-icons"; +import { useCopy } from "@/src/hooks/useCopy"; +import { Button } from "../../ui/button"; + +const Copy = ({ value }: { value: string | number }) => { + const { copyToClipboard, copied } = useCopy(); + return ( + + ); +}; + +export default Copy; diff --git a/tools/obscuroscan_v3/frontend/src/components/modules/common/truncated-address.tsx b/tools/obscuroscan_v3/frontend/src/components/modules/common/truncated-address.tsx index 9603d26c6b..bb2af83600 100644 --- a/tools/obscuroscan_v3/frontend/src/components/modules/common/truncated-address.tsx +++ b/tools/obscuroscan_v3/frontend/src/components/modules/common/truncated-address.tsx @@ -9,6 +9,8 @@ import { import { useCopy } from "@/src/hooks/useCopy"; import { CopyIcon } from "@radix-ui/react-icons"; +import { Button } from "../../ui/button"; +import Copy from "./copy"; const TruncatedAddress = ({ address, @@ -38,12 +40,8 @@ const TruncatedAddress = ({ - + + ) : (
N/A
diff --git a/tools/walletextension/api/ten-gateway/package-lock.json b/tools/walletextension/api/ten-gateway/package-lock.json index b2eba165d8..d98a7dbf5a 100644 --- a/tools/walletextension/api/ten-gateway/package-lock.json +++ b/tools/walletextension/api/ten-gateway/package-lock.json @@ -16,7 +16,7 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", - "axios": "^1.6.1", + "axios": "^1.6.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", diff --git a/tools/walletextension/api/ten-gateway/package.json b/tools/walletextension/api/ten-gateway/package.json index d3c8054979..d3785d7440 100644 --- a/tools/walletextension/api/ten-gateway/package.json +++ b/tools/walletextension/api/ten-gateway/package.json @@ -17,7 +17,7 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", - "axios": "^1.6.1", + "axios": "^1.6.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", diff --git a/tools/walletextension/api/ten-gateway/src/api/gateway.ts b/tools/walletextension/api/ten-gateway/src/api/gateway.ts new file mode 100644 index 0000000000..1946cafd01 --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/api/gateway.ts @@ -0,0 +1,130 @@ +import { apiRoutes } from "@/routes"; +import { httpRequest } from "."; +import { pathToUrl } from "@/routes/router"; +import { getNetworkName } from "@/lib/utils"; +import { + metamaskPersonalSign, + tenChainIDHex, + tenscanLink, +} from "@/lib/constants"; + +export async function switchToTenNetwork() { + try { + await (window as any).ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: tenChainIDHex }], + }); + + return 0; + } catch (switchError: any) { + return switchError.code; + } +} + +export async function fetchVersion(): Promise { + const data = await httpRequest({ + method: "get", + url: pathToUrl(apiRoutes.version), + }); + console.log("🚀 ~ file: gateway.ts:46 ~ fetchVersion ~ data:", data); + return data; +} + +export async function accountIsAuthenticated( + userID: string, + account: string +): Promise { + const data = await httpRequest({ + method: "get", + url: pathToUrl(apiRoutes.queryAccountUserID), + searchParams: { + u: userID, + a: account, + }, + }); + console.log( + "🚀 ~ file: gateway.ts:54 ~ accountIsAuthenticated ~ data:", + data + ); + return data; +} + +export async function authenticateAccountWithTenGateway( + userID: string, + account: string +): Promise { + const textToSign = `Register ${userID} for ${account.toLowerCase()}`; + const signature = await (window as any).ethereum + .request({ + method: metamaskPersonalSign, + params: [textToSign, account], + }) + .catch((error: any) => -1); + + if (signature === -1) { + return "Signing failed"; + } + + const data = await httpRequest({ + method: "post", + url: pathToUrl(apiRoutes.authenticate), + data: { + signature, + message: textToSign, + }, + searchParams: { + u: userID, + }, + }); + console.log( + "🚀 ~ file: gateway.ts:82 ~ authenticateAccountWithTenGateway ~ data:", + data + ); + return data; +} + +export async function revokeAccountsApi(userID: string): Promise { + const data = await httpRequest({ + method: "get", + url: pathToUrl(apiRoutes.revoke), + searchParams: { + u: userID, + }, + }); + console.log("🚀 ~ file: gateway.ts:95 ~ revokeAccounts ~ data:", data); +} + +export async function joinTestnet(): Promise { + const data = await httpRequest({ + method: "get", + url: pathToUrl(apiRoutes.join), + }); + console.log("🚀 ~ file: gateway.ts:102 ~ joinTestnet ~ data:", data); + return data; +} + +export async function addNetworkToMetaMask(rpcUrls: string[]) { + try { + await (window as any).ethereum.request({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: tenChainIDHex, + chainName: getNetworkName(), + nativeCurrency: { + name: "Sepolia Ether", + symbol: "ETH", + decimals: 18, + }, + rpcUrls, + blockExplorerUrls: [tenscanLink], + }, + ], + }); + } catch (error) { + console.error(error); + return false; + } + + return true; +} diff --git a/tools/walletextension/api/ten-gateway/src/api/index.ts b/tools/walletextension/api/ten-gateway/src/api/index.ts new file mode 100644 index 0000000000..a8b5a4730a --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/api/index.ts @@ -0,0 +1,69 @@ +import { tenGatewayAddress } from "@/lib/constants"; +import axios, { AxiosInstance, AxiosRequestConfig } from "axios"; + +type HttpMethod = "get" | "post" | "put" | "patch" | "delete"; + +interface HttpOptions { + method?: HttpMethod; + url: string; + data?: Record; + params?: Record; + headers?: Record; + timeout?: number; + responseType?: + | "json" + | "arraybuffer" + | "blob" + | "document" + | "text" + | undefined; + searchParams?: Record; +} + +const baseConfig: AxiosRequestConfig = { + // baseURL: process.env.BASE_URL, + baseURL: tenGatewayAddress, + timeout: 10000, +}; + +export const https: AxiosInstance = axios.create(baseConfig); + +export const httpRequest = async ( + options: HttpOptions, + config: AxiosRequestConfig = {} +): Promise => { + const { + method = "get", + url, + data, + params, + headers, + timeout, + responseType, + searchParams, + } = options; + let query = ""; + if (searchParams) { + const filteredParams = Object.fromEntries( + Object.entries(searchParams).filter( + ([, value]) => value !== undefined && value !== null && value !== "" + ) + ); + if (Object.keys(filteredParams).length) { + query = new URLSearchParams(filteredParams).toString(); + } + } + + const httpConfig: AxiosRequestConfig = { + method, + url: query ? `${url}?${query}` : url, + data, + params, + headers: { ...(headers || {}) }, + timeout, + responseType: responseType, + ...config, + }; + const response = await https(httpConfig); + return response.data as ResponseData; +}; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/copy.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/common/copy.tsx new file mode 100644 index 0000000000..1233f2aadc --- /dev/null +++ b/tools/walletextension/api/ten-gateway/src/components/modules/common/copy.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { Button } from "@/components/ui/button"; +import { useCopy } from "@/hooks/useCopy"; +import { CopyIcon, CheckIcon } from "@radix-ui/react-icons"; + +const Copy = ({ value }: { value: string | number }) => { + const { copyToClipboard, copied } = useCopy(); + return ( + + ); +}; + +export default Copy; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx index 9843f83deb..a6df550d0f 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx @@ -9,6 +9,8 @@ import { import { useCopy } from "@/hooks/useCopy"; import { CopyIcon } from "@radix-ui/react-icons"; +import { Button } from "@/components/ui/button"; +import Copy from "./copy"; const TruncatedAddress = ({ address, @@ -38,12 +40,7 @@ const TruncatedAddress = ({ - + ) : (
N/A
diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx index 098d45fad6..82af3eea01 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx @@ -16,7 +16,8 @@ import { socialLinks } from "@/lib/constants"; import { Skeleton } from "@/components/ui/skeleton"; const Connected = () => { - const { accounts, connectAccount, revokeAccounts } = useWalletConnection(); + const { accounts, connectAccount, version, revokeAccounts } = + useWalletConnection(); return ( <> @@ -83,6 +84,12 @@ const Connected = () => { )} + +
+

+ Version: {version || "Unknown"} +

+
); }; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx index 49c2eddf19..f121742951 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx @@ -1,15 +1,22 @@ -import { useWalletConnection } from "@/components/providers/wallet-provider"; import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; +import useGatewayService from "@/services/useGatewayService"; +import { Terminal, Badge } from "lucide-react"; +import React from "react"; + import { Button } from "@/components/ui/button"; -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { - TooltipProvider, - Tooltip, - TooltipTrigger, - TooltipContent, -} from "@radix-ui/react-tooltip"; -import { Terminal, Badge, CopyIcon } from "lucide-react"; -import React from "react"; + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import Copy from "../common/copy"; +import { testnetUrls, tenChainIDDecimal } from "@/lib/constants"; +import Link from "next/link"; const CONNECTION_STEPS = [ "Hit Connect to Ten and start your journey", @@ -18,7 +25,7 @@ const CONNECTION_STEPS = [ ]; const Disconnected = () => { - const { connectToTenTestnet } = useWalletConnection(); + const { connectToTenTestnet } = useGatewayService(); return (

Welcome to the Ten Gateway!

@@ -32,6 +39,66 @@ const Disconnected = () => { ))} + + + + + + + + How does the Ten Gateway work? + + +
+

+ By connecting your wallet to Ten and signing the signature request + you will get a unique user id, which is also your{" "} + viewing key. It is contained in the RPC link and unique for + each user. +

+ + + + Do not share your viewing key unless you want others to see the + details of your transactions. + + +

+ Signing the Signature Request is completely secure. It’s not a + transaction so cannot spend any of your assets and it doesn’t give + Ten control over your account. +

+
+ +

+ RPC URL:{" "} + + {testnetUrls.default.url} + +

+ +
+
+ +

Chain ID: {tenChainIDDecimal}

+ +
+
+ + + + + +
+
+ diff --git a/tools/walletextension/api/ten-gateway/src/styles/globals.css b/tools/walletextension/api/ten-gateway/src/styles/globals.css index a29b0febd5..ed24847cf1 100644 --- a/tools/walletextension/api/ten-gateway/src/styles/globals.css +++ b/tools/walletextension/api/ten-gateway/src/styles/globals.css @@ -2,19 +2,38 @@ @tailwind components; @tailwind utilities; -@font-face { - font-family: "CloudSoft"; - font-weight: 400; - src: url("./fonts/CloudSoft-Light_300.otf") format("opentype"); -} +@layer base { + @font-face { + font-family: "CloudSoft"; + font-weight: 400; + src: url("./fonts/CloudSoft-Light_300.otf") format("opentype"); + } -@font-face { - font-family: "CloudSoft"; - font-weight: 700; - src: url("./fonts/CloudSoft-Bold_700.otf") format("opentype"); -} + @font-face { + font-family: "CloudSoft"; + font-weight: 500; + src: url("./fonts/CloudSoft-Bold_700.otf") format("opentype"); + } + + @font-face { + font-family: "CloudSoft"; + font-weight: 700; + src: url("./fonts/CloudSoft-Bold_700.otf") format("opentype"); + } + + html { + font-family: "CloudSoft", sans-serif; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-weight: 500; + } -@layer base { :root { --background: 0 0% 100%; --foreground: 222.2 47.4% 11.2%; diff --git a/tools/walletextension/api/ten-gateway/tailwind.config.js b/tools/walletextension/api/ten-gateway/tailwind.config.js index 9ed2625d76..fbdcb05125 100644 --- a/tools/walletextension/api/ten-gateway/tailwind.config.js +++ b/tools/walletextension/api/ten-gateway/tailwind.config.js @@ -64,6 +64,20 @@ module.exports = { md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, + fontSize: { + 8: "8px", + 10: "10px", + 12: "12px", + 13: "13px", + 14: "14px", + 16: "16px", + 18: "18px", + 20: "20px", + 22: "22px", + 24: "24px", + 30: "30px", + 40: "40px", + }, keyframes: { "accordion-down": { from: { height: 0 }, diff --git a/tools/walletextension/api/ten-gateway/tailwind.config.ts b/tools/walletextension/api/ten-gateway/tailwind.config.ts index 89c2e4711e..f66d7aa04b 100644 --- a/tools/walletextension/api/ten-gateway/tailwind.config.ts +++ b/tools/walletextension/api/ten-gateway/tailwind.config.ts @@ -1,5 +1,4 @@ import type { Config } from "tailwindcss"; -const { fontFamily } = require("tailwindcss/defaultTheme"); const config: Config = { darkMode: ["class"], @@ -67,8 +66,19 @@ const config: Config = { md: `calc(var(--radius) - 2px)`, sm: "calc(var(--radius) - 4px)", }, - fontFamily: { - sans: ["Cloud Soft", ...fontFamily.sans], + fontSize: { + 8: "8px", + 10: "10px", + 12: "12px", + 13: "13px", + 14: "14px", + 16: "16px", + 18: "18px", + 20: "20px", + 22: "22px", + 24: "24px", + 30: "30px", + 40: "40px", }, keyframes: { "accordion-down": { From 87b1bdbde18ddf93e10f7e3fd292cf0fe301d950 Mon Sep 17 00:00:00 2001 From: Jennie Date: Wed, 22 Nov 2023 15:05:56 +0400 Subject: [PATCH 07/36] remove logs --- .../api/ten-gateway/src/api/gateway.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/src/api/gateway.ts b/tools/walletextension/api/ten-gateway/src/api/gateway.ts index 1946cafd01..d4615b0384 100644 --- a/tools/walletextension/api/ten-gateway/src/api/gateway.ts +++ b/tools/walletextension/api/ten-gateway/src/api/gateway.ts @@ -26,7 +26,6 @@ export async function fetchVersion(): Promise { method: "get", url: pathToUrl(apiRoutes.version), }); - console.log("🚀 ~ file: gateway.ts:46 ~ fetchVersion ~ data:", data); return data; } @@ -42,10 +41,6 @@ export async function accountIsAuthenticated( a: account, }, }); - console.log( - "🚀 ~ file: gateway.ts:54 ~ accountIsAuthenticated ~ data:", - data - ); return data; } @@ -76,10 +71,6 @@ export async function authenticateAccountWithTenGateway( u: userID, }, }); - console.log( - "🚀 ~ file: gateway.ts:82 ~ authenticateAccountWithTenGateway ~ data:", - data - ); return data; } @@ -91,7 +82,6 @@ export async function revokeAccountsApi(userID: string): Promise { u: userID, }, }); - console.log("🚀 ~ file: gateway.ts:95 ~ revokeAccounts ~ data:", data); } export async function joinTestnet(): Promise { @@ -99,7 +89,6 @@ export async function joinTestnet(): Promise { method: "get", url: pathToUrl(apiRoutes.join), }); - console.log("🚀 ~ file: gateway.ts:102 ~ joinTestnet ~ data:", data); return data; } From 4fe3e0aee35c8e6493f77c5f263703e610fa9cb9 Mon Sep 17 00:00:00 2001 From: Jennie Date: Wed, 22 Nov 2023 17:36:25 +0400 Subject: [PATCH 08/36] fix: metamask error handling --- .../components/providers/wallet-provider.tsx | 60 +++++++++---------- .../src/services/useGatewayService.ts | 8 ++- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx index 4f1506c1aa..551f3bb4d1 100644 --- a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx @@ -45,17 +45,10 @@ export const WalletConnectionProvider = ({ useState(null); useEffect(() => { - const ethereum = (window as any).ethereum; - - const handleAccountsChanged = async (accounts: string[]) => { - if (accounts.length === 0) { - toast({ description: "Please connect to MetaMask." }); - } else if (userID && isValidUserIDFormat(userID)) { - await Promise.all( - accounts.map((account) => - authenticateAccountWithTenGateway(userID, account) - ) - ); + const { ethereum } = window as any; + const handleAccountsChanged = async () => { + if (isValidUserIDFormat(await getUserID())) { + await displayCorrectScreenBasedOnMetamaskAndUserID(); } }; @@ -64,7 +57,7 @@ export const WalletConnectionProvider = ({ return () => { ethereum.removeListener("accountsChanged", handleAccountsChanged); }; - }, [userID]); + }, []); useEffect(() => { checkIfMetamaskIsLoaded(); @@ -76,7 +69,7 @@ export const WalletConnectionProvider = ({ (window as any).ethereum ); setProvider(provider); - handleEthereum(); + await handleEthereum(); } else { toast({ description: "Connecting to MetaMask..." }); @@ -87,11 +80,11 @@ export const WalletConnectionProvider = ({ } } - function handleEthereum() { + async function handleEthereum() { const { ethereum } = window as any; if (ethereum && ethereum.isMetaMask) { - initialize(); + await displayCorrectScreenBasedOnMetamaskAndUserID(); } else { toast({ description: "Please install MetaMask to use Ten Gateway." }); } @@ -101,28 +94,36 @@ export const WalletConnectionProvider = ({ if (!provider || !(await isTenChain())) { return null; } - try { - return await provider.send("eth_getStorageAt", [ - "getUserID", - getRandomIntAsString(0, 1000), - null, - ]); - } catch (e) { + if (await isTenChain()) { + const id = await provider.send("eth_getStorageAt", [ + "getUserID", + getRandomIntAsString(0, 1000), + null, + ]); + return id; + } else { + return null; + } + } catch (e: any) { + toast({ + description: + `${e.message} ${e.data?.message}` || + "Error: Could not fetch your userID. Please try again later.", + }); console.error(e); return null; } } - const initialize = async () => { + async function displayCorrectScreenBasedOnMetamaskAndUserID() { const userID = await getUserID(); + setUserID(userID); setVersion(await fetchVersion()); - await displayCorrectScreenBasedOnMetamaskAndUserID(); - }; - async function displayCorrectScreenBasedOnMetamaskAndUserID() { if (await isTenChain()) { + console.log("isTenChain"); if (provider && userID && isValidUserIDFormat(userID)) { const accounts = await provider.listAccounts(); const formattedAccounts = await Promise.all( @@ -132,7 +133,7 @@ export const WalletConnectionProvider = ({ })) ); setAccounts(formattedAccounts); - setWalletConnected(true); + return setWalletConnected(true); } } else { setWalletConnected(false); @@ -170,9 +171,4 @@ export const WalletConnectionProvider = ({ {children} ); - - function handleFetchError(errorMessage: string) { - console.error(`Error: ${errorMessage}`); - toast({ description: `${errorMessage}. Please try again later.` }); - } }; diff --git a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts index 433f42ef06..ffca39f941 100644 --- a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts +++ b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts @@ -42,7 +42,7 @@ const useGatewayService = () => { const connectToTenTestnet = async () => { if (await isTenChain()) { - if (userID && !isValidUserIDFormat(userID)) { + if (!userID || !isValidUserIDFormat(userID)) { toast({ description: "Existing Ten network detected in MetaMask. Please remove before hitting begin", @@ -62,16 +62,18 @@ const useGatewayService = () => { } if (!(await isMetamaskConnected())) { + console.log("No accounts found, connecting..."); await connectAccounts(); } + console.log("Connected to Ten Network"); if (!provider) { return; } - + console.log("Getting accounts..."); const accounts = await provider.listAccounts(); - if (accounts.length === 0) { + console.log("No accounts found"); toast({ description: "No MetaMask accounts found." }); return; } From 45cc49d96c4ffeccc984416833781ba357d74947 Mon Sep 17 00:00:00 2001 From: Jennie Date: Wed, 22 Nov 2023 19:19:01 +0400 Subject: [PATCH 09/36] fix: conection method --- .../components/providers/wallet-provider.tsx | 43 ++++++++++--------- .../src/services/useGatewayService.ts | 31 +++++-------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx index 551f3bb4d1..18537b288b 100644 --- a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx @@ -47,7 +47,7 @@ export const WalletConnectionProvider = ({ useEffect(() => { const { ethereum } = window as any; const handleAccountsChanged = async () => { - if (isValidUserIDFormat(await getUserID())) { + if (userID && isValidUserIDFormat(userID)) { await displayCorrectScreenBasedOnMetamaskAndUserID(); } }; @@ -60,40 +60,43 @@ export const WalletConnectionProvider = ({ }, []); useEffect(() => { - checkIfMetamaskIsLoaded(); + const initialize = async () => { + await checkIfMetamaskIsLoaded(); + }; + initialize(); }, []); - async function checkIfMetamaskIsLoaded() { + const checkIfMetamaskIsLoaded = async () => { if (window && (window as any).ethereum) { - const provider = new ethers.providers.Web3Provider( - (window as any).ethereum - ); - setProvider(provider); await handleEthereum(); } else { toast({ description: "Connecting to MetaMask..." }); - window.addEventListener("ethereum#initialized", handleEthereum, { once: true, }); setTimeout(handleEthereum, METAMASK_CONNECTION_TIMEOUT); } - } + }; - async function handleEthereum() { + const handleEthereum = async () => { const { ethereum } = window as any; - if (ethereum && ethereum.isMetaMask) { + const provider = new ethers.providers.Web3Provider( + (window as any).ethereum + ); + setProvider(provider); await displayCorrectScreenBasedOnMetamaskAndUserID(); } else { toast({ description: "Please install MetaMask to use Ten Gateway." }); } - } + }; - async function getUserID() { - if (!provider || !(await isTenChain())) { + const getUserID = async () => { + if (!provider) { + console.log("no provider"); return null; } + try { if (await isTenChain()) { const id = await provider.send("eth_getStorageAt", [ @@ -114,16 +117,14 @@ export const WalletConnectionProvider = ({ console.error(e); return null; } - } + }; - async function displayCorrectScreenBasedOnMetamaskAndUserID() { + const displayCorrectScreenBasedOnMetamaskAndUserID = async () => { const userID = await getUserID(); - setUserID(userID); setVersion(await fetchVersion()); if (await isTenChain()) { - console.log("isTenChain"); if (provider && userID && isValidUserIDFormat(userID)) { const accounts = await provider.listAccounts(); const formattedAccounts = await Promise.all( @@ -133,12 +134,14 @@ export const WalletConnectionProvider = ({ })) ); setAccounts(formattedAccounts); - return setWalletConnected(true); + setWalletConnected(true); // Set walletConnected after all async operations + } else { + setWalletConnected(false); } } else { setWalletConnected(false); } - } + }; const connectAccount = async (account: string) => { if (!userID) { diff --git a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts index ffca39f941..56a2598caf 100644 --- a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts +++ b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts @@ -13,42 +13,39 @@ const useGatewayService = () => { const { provider } = useWalletConnection(); const { userID, setUserID } = useWalletConnection(); - async function connectAccounts() { + const connectAccounts = async () => { try { return await (window as any).ethereum.request({ method: "eth_requestAccounts", }); } catch (error) { - console.error("User denied account access:", error); toast({ description: `User denied account access: ${error}` }); return null; } - } + }; - async function isMetamaskConnected() { + const isMetamaskConnected = async () => { if (!provider) { return false; } - try { const accounts = await provider.listAccounts(); return accounts.length > 0; } catch (error) { - console.log("Unable to get accounts"); + toast({ description: "Unable to get accounts" }); } - return false; - } + }; const connectToTenTestnet = async () => { if (await isTenChain()) { if (!userID || !isValidUserIDFormat(userID)) { - toast({ + return toast({ description: "Existing Ten network detected in MetaMask. Please remove before hitting begin", }); } - } else { + const switched = await switchToTenNetwork(); const rpcUrls = [`${getRPCFromUrl()}/${tenGatewayVersion}/?u=${userID}`]; @@ -62,31 +59,25 @@ const useGatewayService = () => { } if (!(await isMetamaskConnected())) { - console.log("No accounts found, connecting..."); + toast({ description: "No accounts found, connecting..." }); await connectAccounts(); } - console.log("Connected to Ten Network"); - + toast({ description: "Connected to Ten Network" }); if (!provider) { return; } - console.log("Getting accounts..."); + toast({ description: "Getting accounts..." }); const accounts = await provider.listAccounts(); if (accounts.length === 0) { - console.log("No accounts found"); + toast({ description: "No accounts found" }); toast({ description: "No MetaMask accounts found." }); return; } } }; - const handleFetchError = (message: string) => { - toast({ description: message }); - }; - return { connectToTenTestnet, - handleFetchError, }; }; From 8e0f99573d5915b784c76088f69f90920c92d6c8 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Fri, 24 Nov 2023 12:48:26 +0400 Subject: [PATCH 10/36] fix connect method --- .../src/services/useGatewayService.ts | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts index 56a2598caf..e9b9682d96 100644 --- a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts +++ b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts @@ -38,6 +38,10 @@ const useGatewayService = () => { }; const connectToTenTestnet = async () => { + console.log( + "🚀 ~ file: useGatewayService.ts:43 ~ connectToTenTestnet ~ userID:", + userID + ); if (await isTenChain()) { if (!userID || !isValidUserIDFormat(userID)) { return toast({ @@ -45,34 +49,34 @@ const useGatewayService = () => { "Existing Ten network detected in MetaMask. Please remove before hitting begin", }); } + } - const switched = await switchToTenNetwork(); - const rpcUrls = [`${getRPCFromUrl()}/${tenGatewayVersion}/?u=${userID}`]; + const switched = await switchToTenNetwork(); + const rpcUrls = [`${getRPCFromUrl()}/${tenGatewayVersion}/?u=${userID}`]; - if ( - switched === SWITCHED_CODE || - (userID && !isValidUserIDFormat(userID)) - ) { - const user = await joinTestnet(); - setUserID(user); - await addNetworkToMetaMask(rpcUrls); - } + if ( + switched === SWITCHED_CODE || + (userID && !isValidUserIDFormat(userID)) + ) { + const user = await joinTestnet(); + setUserID(user); + await addNetworkToMetaMask(rpcUrls); + } - if (!(await isMetamaskConnected())) { - toast({ description: "No accounts found, connecting..." }); - await connectAccounts(); - } - toast({ description: "Connected to Ten Network" }); - if (!provider) { - return; - } - toast({ description: "Getting accounts..." }); - const accounts = await provider.listAccounts(); - if (accounts.length === 0) { - toast({ description: "No accounts found" }); - toast({ description: "No MetaMask accounts found." }); - return; - } + if (!(await isMetamaskConnected())) { + toast({ description: "No accounts found, connecting..." }); + await connectAccounts(); + } + toast({ description: "Connected to Ten Network" }); + if (!provider) { + return; + } + toast({ description: "Getting accounts..." }); + const accounts = await provider.listAccounts(); + if (accounts.length === 0) { + toast({ description: "No accounts found" }); + toast({ description: "No MetaMask accounts found." }); + return; } }; From 256449116e31458fdc0240c2747a37a4bae55a87 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Fri, 24 Nov 2023 15:35:13 +0400 Subject: [PATCH 11/36] feat: error handling --- .../api/ten-gateway/src/api/gateway.ts | 31 +++---- .../components/modules/home/disconnected.tsx | 2 +- .../components/providers/wallet-provider.tsx | 70 ++++++++++++--- .../ten-gateway/src/components/ui/badge.tsx | 1 + .../ten-gateway/src/components/ui/toast.tsx | 3 + .../api/ten-gateway/src/lib/constants.ts | 6 ++ .../api/ten-gateway/src/routes/index.ts | 6 ++ .../src/services/useGatewayService.ts | 85 ++++++++++--------- .../api/ten-gateway/src/styles/globals.css | 6 ++ .../src/types/interfaces/WalletInterfaces.ts | 1 + 10 files changed, 137 insertions(+), 74 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/src/api/gateway.ts b/tools/walletextension/api/ten-gateway/src/api/gateway.ts index d4615b0384..86ac9a6af7 100644 --- a/tools/walletextension/api/ten-gateway/src/api/gateway.ts +++ b/tools/walletextension/api/ten-gateway/src/api/gateway.ts @@ -1,4 +1,4 @@ -import { apiRoutes } from "@/routes"; +import { apiRoutes, requestMethods } from "@/routes"; import { httpRequest } from "."; import { pathToUrl } from "@/routes/router"; import { getNetworkName } from "@/lib/utils"; @@ -6,12 +6,13 @@ import { metamaskPersonalSign, tenChainIDHex, tenscanLink, + nativeCurrency, } from "@/lib/constants"; export async function switchToTenNetwork() { try { await (window as any).ethereum.request({ - method: "wallet_switchEthereumChain", + method: requestMethods.switchNetwork, params: [{ chainId: tenChainIDHex }], }); @@ -22,18 +23,17 @@ export async function switchToTenNetwork() { } export async function fetchVersion(): Promise { - const data = await httpRequest({ + return await httpRequest({ method: "get", url: pathToUrl(apiRoutes.version), }); - return data; } export async function accountIsAuthenticated( userID: string, account: string ): Promise { - const data = await httpRequest({ + return await httpRequest({ method: "get", url: pathToUrl(apiRoutes.queryAccountUserID), searchParams: { @@ -41,7 +41,6 @@ export async function accountIsAuthenticated( a: account, }, }); - return data; } export async function authenticateAccountWithTenGateway( @@ -60,7 +59,7 @@ export async function authenticateAccountWithTenGateway( return "Signing failed"; } - const data = await httpRequest({ + return await httpRequest({ method: "post", url: pathToUrl(apiRoutes.authenticate), data: { @@ -71,11 +70,10 @@ export async function authenticateAccountWithTenGateway( u: userID, }, }); - return data; } -export async function revokeAccountsApi(userID: string): Promise { - const data = await httpRequest({ +export async function revokeAccountsApi(userID: string): Promise { + return await httpRequest({ method: "get", url: pathToUrl(apiRoutes.revoke), searchParams: { @@ -85,26 +83,21 @@ export async function revokeAccountsApi(userID: string): Promise { } export async function joinTestnet(): Promise { - const data = await httpRequest({ + return await httpRequest({ method: "get", url: pathToUrl(apiRoutes.join), }); - return data; } export async function addNetworkToMetaMask(rpcUrls: string[]) { try { await (window as any).ethereum.request({ - method: "wallet_addEthereumChain", + method: requestMethods.addNetwork, params: [ { chainId: tenChainIDHex, chainName: getNetworkName(), - nativeCurrency: { - name: "Sepolia Ether", - symbol: "ETH", - decimals: 18, - }, + nativeCurrency, rpcUrls, blockExplorerUrls: [tenscanLink], }, @@ -112,7 +105,7 @@ export async function addNetworkToMetaMask(rpcUrls: string[]) { }); } catch (error) { console.error(error); - return false; + return error; } return true; diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx b/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx index f121742951..6bbd69d961 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx @@ -1,4 +1,4 @@ -import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import useGatewayService from "@/services/useGatewayService"; import { Terminal, Badge } from "lucide-react"; import React from "react"; diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx index 18537b288b..6e75e5cce7 100644 --- a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx @@ -51,12 +51,12 @@ export const WalletConnectionProvider = ({ await displayCorrectScreenBasedOnMetamaskAndUserID(); } }; - ethereum.on("accountsChanged", handleAccountsChanged); return () => { ethereum.removeListener("accountsChanged", handleAccountsChanged); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { @@ -64,6 +64,7 @@ export const WalletConnectionProvider = ({ await checkIfMetamaskIsLoaded(); }; initialize(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const checkIfMetamaskIsLoaded = async () => { @@ -93,7 +94,6 @@ export const WalletConnectionProvider = ({ const getUserID = async () => { if (!provider) { - console.log("no provider"); return null; } @@ -120,21 +120,13 @@ export const WalletConnectionProvider = ({ }; const displayCorrectScreenBasedOnMetamaskAndUserID = async () => { - const userID = await getUserID(); - setUserID(userID); setVersion(await fetchVersion()); if (await isTenChain()) { + const userID = await getUserID(); + setUserID(userID); if (provider && userID && isValidUserIDFormat(userID)) { - const accounts = await provider.listAccounts(); - const formattedAccounts = await Promise.all( - accounts.map(async (account) => ({ - name: account, - connected: await accountIsAuthenticated(userID, account), - })) - ); - setAccounts(formattedAccounts); - setWalletConnected(true); // Set walletConnected after all async operations + await getAccounts(); } else { setWalletConnected(false); } @@ -155,7 +147,56 @@ export const WalletConnectionProvider = ({ return; } - await revokeAccountsApi(userID); + const revokeResponse = await revokeAccountsApi(userID); + if (revokeResponse === "success") { + toast({ + variant: "success", + description: "Successfully revoked all accounts.", + }); + setAccounts(null); + setWalletConnected(false); + } + }; + + const getAccounts = async () => { + if (!provider) { + toast({ + variant: "destructive", + description: "No provider found. Please try again later.", + }); + return; + } + + toast({ variant: "info", description: "Getting accounts..." }); + if (!(await isTenChain())) { + toast({ + variant: "warning", + description: "Please connect to the Ten chain.", + }); + return; + } + const accounts = await provider.listAccounts(); + + if (accounts.length === 0) { + toast({ + variant: "destructive", + description: "No MetaMask accounts found.", + }); + return; + } + + const user = await getUserID(); + setUserID(user); + + setAccounts( + await Promise.all( + accounts.map(async (account) => ({ + name: account, + connected: await accountIsAuthenticated(user, account), + })) + ) + ); + setWalletConnected(true); }; const walletConnectionContextValue: WalletConnectionContextType = { @@ -167,6 +208,7 @@ export const WalletConnectionProvider = ({ provider, version, revokeAccounts, + getAccounts, }; return ( diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx index 6079f58828..51d80562a1 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx @@ -12,6 +12,7 @@ const badgeVariants = cva( "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + info: "border-transparent bg-info text-info-foreground hover:bg-info/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", success: diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx b/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx index 3875c705ef..4ec3710bb0 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx +++ b/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx @@ -28,6 +28,9 @@ const toastVariants = cva( variants: { variant: { default: "border bg-background text-foreground", + info: "border-info bg-info text-info-foreground", + success: "border-success bg-success text-success-foreground", + warning: "border-warning bg-warning text-foreground", destructive: "destructive group border-destructive bg-destructive text-destructive-foreground", }, diff --git a/tools/walletextension/api/ten-gateway/src/lib/constants.ts b/tools/walletextension/api/ten-gateway/src/lib/constants.ts index 8018a5a7c4..07a64205f8 100644 --- a/tools/walletextension/api/ten-gateway/src/lib/constants.ts +++ b/tools/walletextension/api/ten-gateway/src/lib/constants.ts @@ -38,3 +38,9 @@ export const metamaskPersonalSign = "personal_sign"; export const tenChainIDHex = "0x" + tenChainIDDecimal.toString(16); // Convert to hexadecimal and prefix with '0x' export const METAMASK_CONNECTION_TIMEOUT = 3000; + +export const nativeCurrency = { + name: "Sepolia Ether", + symbol: "ETH", + decimals: 18, +}; diff --git a/tools/walletextension/api/ten-gateway/src/routes/index.ts b/tools/walletextension/api/ten-gateway/src/routes/index.ts index ca1b8543fe..0c16fd1829 100644 --- a/tools/walletextension/api/ten-gateway/src/routes/index.ts +++ b/tools/walletextension/api/ten-gateway/src/routes/index.ts @@ -10,3 +10,9 @@ export const apiRoutes = { revoke: `/${tenGatewayVersion}/revoke/`, version: `/version/`, }; + +export const requestMethods = { + connectAccounts: "eth_requestAccounts", + switchNetwork: "wallet_switchEthereumChain", + addNetwork: "wallet_addEthereumChain", +}; diff --git a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts index e9b9682d96..a3df77c1d0 100644 --- a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts +++ b/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts @@ -7,19 +7,24 @@ import { useWalletConnection } from "@/components/providers/wallet-provider"; import { useToast } from "@/components/ui/use-toast"; import { SWITCHED_CODE, tenGatewayVersion } from "@/lib/constants"; import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "@/lib/utils"; +import { requestMethods } from "@/routes"; const useGatewayService = () => { const { toast } = useToast(); const { provider } = useWalletConnection(); - const { userID, setUserID } = useWalletConnection(); + const { userID, setUserID, getAccounts } = useWalletConnection(); const connectAccounts = async () => { try { - return await (window as any).ethereum.request({ - method: "eth_requestAccounts", + await (window as any).ethereum.request({ + method: requestMethods.connectAccounts, }); + toast({ variant: "success", description: "Connected to Ten Network" }); } catch (error) { - toast({ description: `User denied account access: ${error}` }); + toast({ + variant: "destructive", + description: "Unable to connect to Ten Network", + }); return null; } }; @@ -32,51 +37,51 @@ const useGatewayService = () => { const accounts = await provider.listAccounts(); return accounts.length > 0; } catch (error) { - toast({ description: "Unable to get accounts" }); + toast({ variant: "destructive", description: "Unable to get accounts" }); } return false; }; const connectToTenTestnet = async () => { - console.log( - "🚀 ~ file: useGatewayService.ts:43 ~ connectToTenTestnet ~ userID:", - userID - ); - if (await isTenChain()) { - if (!userID || !isValidUserIDFormat(userID)) { - return toast({ - description: - "Existing Ten network detected in MetaMask. Please remove before hitting begin", - }); + try { + if (await isTenChain()) { + if (!userID || !isValidUserIDFormat(userID)) { + throw new Error( + "Existing Ten network detected in MetaMask. Please remove before hitting begin" + ); + } } - } - const switched = await switchToTenNetwork(); - const rpcUrls = [`${getRPCFromUrl()}/${tenGatewayVersion}/?u=${userID}`]; + const switched = await switchToTenNetwork(); - if ( - switched === SWITCHED_CODE || - (userID && !isValidUserIDFormat(userID)) - ) { - const user = await joinTestnet(); - setUserID(user); - await addNetworkToMetaMask(rpcUrls); - } + if ( + switched === SWITCHED_CODE || + (userID && !isValidUserIDFormat(userID)) + ) { + const user = await joinTestnet(); + setUserID(user); + const rpcUrls = [`${getRPCFromUrl()}/${tenGatewayVersion}/?u=${user}`]; + await addNetworkToMetaMask(rpcUrls); + } - if (!(await isMetamaskConnected())) { - toast({ description: "No accounts found, connecting..." }); - await connectAccounts(); - } - toast({ description: "Connected to Ten Network" }); - if (!provider) { - return; - } - toast({ description: "Getting accounts..." }); - const accounts = await provider.listAccounts(); - if (accounts.length === 0) { - toast({ description: "No accounts found" }); - toast({ description: "No MetaMask accounts found." }); - return; + if (!(await isMetamaskConnected())) { + toast({ + variant: "info", + description: "No accounts found, connecting...", + }); + await connectAccounts(); + } + + if (!provider) { + return; + } + await getAccounts(); + } catch (error: any) { + console.error("Error:", error.message); + toast({ + variant: "destructive", + description: `${error.message}`, + }); } }; diff --git a/tools/walletextension/api/ten-gateway/src/styles/globals.css b/tools/walletextension/api/ten-gateway/src/styles/globals.css index ed24847cf1..9fb737a472 100644 --- a/tools/walletextension/api/ten-gateway/src/styles/globals.css +++ b/tools/walletextension/api/ten-gateway/src/styles/globals.css @@ -65,6 +65,9 @@ --warning: 45 80% 50%; --warning-foreground: 0 0% 20%; + --info: 224, 40%, 54%; + --info-foreground: 222.2 47.4% 11.2%; + --destructive: 0 100% 50%; --destructive-foreground: 210 40% 98%; @@ -104,6 +107,9 @@ --warning: 45 80% 40%; --warning-foreground: 0 0% 80%; + --info: 224, 40%, 54%; + --info-foreground: 222.2 47.4% 11.2%; + --destructive: 0 63% 31%; --destructive-foreground: 210 40% 98%; diff --git a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts index 46c526d17a..f5c803c250 100644 --- a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts @@ -9,6 +9,7 @@ export interface WalletConnectionContextType { provider: ethers.providers.Web3Provider | null; version: string | null; revokeAccounts: () => void; + getAccounts: () => Promise; } export interface Props { From 0e199a044374f91d8d80de13ad4a04f40742d57c Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Fri, 24 Nov 2023 16:01:27 +0400 Subject: [PATCH 12/36] separate gateway frontend concerns to folder --- .../api/ten-gateway/{ => frontend}/.eslintrc.json | 0 .../api/ten-gateway/{ => frontend}/.gitignore | 0 .../api/ten-gateway/{ => frontend}/README.md | 0 .../api/ten-gateway/{ => frontend}/next.config.js | 0 .../ten-gateway/{ => frontend}/package-lock.json | 2 +- .../api/ten-gateway/{ => frontend}/package.json | 2 +- .../ten-gateway/{ => frontend}/postcss.config.js | 0 .../public/assets/images/ten-temp-logo.svg | 0 .../ten-gateway/{ => frontend}/public/favicon.ico | Bin .../ten-gateway/{ => frontend}/src/api/gateway.ts | 8 ++++---- .../ten-gateway/{ => frontend}/src/api/index.ts | 2 +- .../src/components/layouts/default-layout.tsx | 0 .../src/components/layouts/footer.tsx | 2 +- .../src/components/layouts/header.tsx | 0 .../{ => frontend}/src/components/main-nav.tsx | 4 ++-- .../{ => frontend}/src/components/mode-toggle.tsx | 4 ++-- .../components/modules/common/connect-wallet.tsx | 6 +++--- .../src/components/modules/common/copy.tsx | 4 ++-- .../components/modules/common/network-status.tsx | 0 .../modules/common/truncated-address.tsx | 0 .../src/components/modules/home/connected.tsx | 14 +++++++------- .../src/components/modules/home/disconnected.tsx | 10 +++++----- .../src/components/modules/home/index.tsx | 6 +++--- .../src/components/providers/theme-provider.tsx | 0 .../src/components/providers/wallet-provider.tsx | 8 ++++---- .../{ => frontend}/src/components/ui/alert.tsx | 2 +- .../{ => frontend}/src/components/ui/badge.tsx | 2 +- .../{ => frontend}/src/components/ui/button.tsx | 2 +- .../{ => frontend}/src/components/ui/card.tsx | 2 +- .../{ => frontend}/src/components/ui/dialog.tsx | 2 +- .../src/components/ui/dropdown-menu.tsx | 2 +- .../{ => frontend}/src/components/ui/select.tsx | 2 +- .../src/components/ui/separator.tsx | 2 +- .../{ => frontend}/src/components/ui/skeleton.tsx | 2 +- .../{ => frontend}/src/components/ui/table.tsx | 2 +- .../{ => frontend}/src/components/ui/toast.tsx | 2 +- .../{ => frontend}/src/components/ui/toaster.tsx | 4 ++-- .../{ => frontend}/src/components/ui/tooltip.tsx | 2 +- .../{ => frontend}/src/components/ui/use-toast.ts | 2 +- .../{ => frontend}/src/hooks/useCopy.ts | 2 +- .../{ => frontend}/src/lib/constants.ts | 0 .../ten-gateway/{ => frontend}/src/lib/utils.ts | 0 .../ten-gateway/{ => frontend}/src/pages/_app.tsx | 8 ++++---- .../{ => frontend}/src/pages/_document.tsx | 0 .../{ => frontend}/src/pages/index.tsx | 4 ++-- .../{ => frontend}/src/routes/index.ts | 2 +- .../{ => frontend}/src/routes/router.ts | 0 .../src/services/useGatewayService.ts | 12 ++++++------ .../src/styles/fonts/CloudSoft-Bold_700.otf | Bin .../src/styles/fonts/CloudSoft-Light_300.otf | Bin .../{ => frontend}/src/styles/fonts/README.txt | 0 .../{ => frontend}/src/styles/globals.css | 0 .../src/types/interfaces/WalletInterfaces.ts | 0 .../{ => frontend}/src/types/interfaces/index.ts | 0 .../ten-gateway/{ => frontend}/tailwind.config.js | 0 .../ten-gateway/{ => frontend}/tailwind.config.ts | 0 .../api/ten-gateway/{ => frontend}/tsconfig.json | 0 57 files changed, 65 insertions(+), 65 deletions(-) rename tools/walletextension/api/ten-gateway/{ => frontend}/.eslintrc.json (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/.gitignore (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/README.md (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/next.config.js (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/package-lock.json (99%) rename tools/walletextension/api/ten-gateway/{ => frontend}/package.json (98%) rename tools/walletextension/api/ten-gateway/{ => frontend}/postcss.config.js (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/public/assets/images/ten-temp-logo.svg (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/public/favicon.ico (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/api/gateway.ts (92%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/api/index.ts (96%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/layouts/default-layout.tsx (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/layouts/footer.tsx (96%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/layouts/header.tsx (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/main-nav.tsx (96%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/mode-toggle.tsx (92%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/modules/common/connect-wallet.tsx (80%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/modules/common/copy.tsx (85%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/modules/common/network-status.tsx (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/modules/common/truncated-address.tsx (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/modules/home/connected.tsx (87%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/modules/home/disconnected.tsx (92%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/modules/home/index.tsx (64%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/providers/theme-provider.tsx (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/providers/wallet-provider.tsx (97%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/alert.tsx (97%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/badge.tsx (97%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/button.tsx (98%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/card.tsx (98%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/dialog.tsx (99%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/dropdown-menu.tsx (99%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/select.tsx (99%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/separator.tsx (95%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/skeleton.tsx (85%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/table.tsx (98%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/toast.tsx (99%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/toaster.tsx (89%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/tooltip.tsx (96%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/components/ui/use-toast.ts (98%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/hooks/useCopy.ts (95%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/lib/constants.ts (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/lib/utils.ts (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/pages/_app.tsx (61%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/pages/_document.tsx (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/pages/index.tsx (78%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/routes/index.ts (90%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/routes/router.ts (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/services/useGatewayService.ts (88%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/styles/fonts/CloudSoft-Bold_700.otf (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/styles/fonts/CloudSoft-Light_300.otf (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/styles/fonts/README.txt (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/styles/globals.css (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/types/interfaces/WalletInterfaces.ts (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/src/types/interfaces/index.ts (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/tailwind.config.js (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/tailwind.config.ts (100%) rename tools/walletextension/api/ten-gateway/{ => frontend}/tsconfig.json (100%) diff --git a/tools/walletextension/api/ten-gateway/.eslintrc.json b/tools/walletextension/api/ten-gateway/frontend/.eslintrc.json similarity index 100% rename from tools/walletextension/api/ten-gateway/.eslintrc.json rename to tools/walletextension/api/ten-gateway/frontend/.eslintrc.json diff --git a/tools/walletextension/api/ten-gateway/.gitignore b/tools/walletextension/api/ten-gateway/frontend/.gitignore similarity index 100% rename from tools/walletextension/api/ten-gateway/.gitignore rename to tools/walletextension/api/ten-gateway/frontend/.gitignore diff --git a/tools/walletextension/api/ten-gateway/README.md b/tools/walletextension/api/ten-gateway/frontend/README.md similarity index 100% rename from tools/walletextension/api/ten-gateway/README.md rename to tools/walletextension/api/ten-gateway/frontend/README.md diff --git a/tools/walletextension/api/ten-gateway/next.config.js b/tools/walletextension/api/ten-gateway/frontend/next.config.js similarity index 100% rename from tools/walletextension/api/ten-gateway/next.config.js rename to tools/walletextension/api/ten-gateway/frontend/next.config.js diff --git a/tools/walletextension/api/ten-gateway/package-lock.json b/tools/walletextension/api/ten-gateway/frontend/package-lock.json similarity index 99% rename from tools/walletextension/api/ten-gateway/package-lock.json rename to tools/walletextension/api/ten-gateway/frontend/package-lock.json index d98a7dbf5a..83f4a3c474 100644 --- a/tools/walletextension/api/ten-gateway/package-lock.json +++ b/tools/walletextension/api/ten-gateway/frontend/package-lock.json @@ -23,7 +23,7 @@ "date-fns": "^2.30.0", "ethers": "^5.7.2", "lucide-react": "^0.292.0", - "next": "14.0.1", + "next": "^14.0.1", "next-themes": "^0.2.1", "path-to-regexp": "^6.2.1", "react": "^18", diff --git a/tools/walletextension/api/ten-gateway/package.json b/tools/walletextension/api/ten-gateway/frontend/package.json similarity index 98% rename from tools/walletextension/api/ten-gateway/package.json rename to tools/walletextension/api/ten-gateway/frontend/package.json index d3785d7440..d2c9f0dd8b 100644 --- a/tools/walletextension/api/ten-gateway/package.json +++ b/tools/walletextension/api/ten-gateway/frontend/package.json @@ -24,7 +24,7 @@ "date-fns": "^2.30.0", "ethers": "^5.7.2", "lucide-react": "^0.292.0", - "next": "14.0.1", + "next": "^14.0.1", "next-themes": "^0.2.1", "path-to-regexp": "^6.2.1", "react": "^18", diff --git a/tools/walletextension/api/ten-gateway/postcss.config.js b/tools/walletextension/api/ten-gateway/frontend/postcss.config.js similarity index 100% rename from tools/walletextension/api/ten-gateway/postcss.config.js rename to tools/walletextension/api/ten-gateway/frontend/postcss.config.js diff --git a/tools/walletextension/api/ten-gateway/public/assets/images/ten-temp-logo.svg b/tools/walletextension/api/ten-gateway/frontend/public/assets/images/ten-temp-logo.svg similarity index 100% rename from tools/walletextension/api/ten-gateway/public/assets/images/ten-temp-logo.svg rename to tools/walletextension/api/ten-gateway/frontend/public/assets/images/ten-temp-logo.svg diff --git a/tools/walletextension/api/ten-gateway/public/favicon.ico b/tools/walletextension/api/ten-gateway/frontend/public/favicon.ico similarity index 100% rename from tools/walletextension/api/ten-gateway/public/favicon.ico rename to tools/walletextension/api/ten-gateway/frontend/public/favicon.ico diff --git a/tools/walletextension/api/ten-gateway/src/api/gateway.ts b/tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts similarity index 92% rename from tools/walletextension/api/ten-gateway/src/api/gateway.ts rename to tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts index 86ac9a6af7..638ed8e8b8 100644 --- a/tools/walletextension/api/ten-gateway/src/api/gateway.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts @@ -1,13 +1,13 @@ -import { apiRoutes, requestMethods } from "@/routes"; +import { apiRoutes, requestMethods } from "../routes"; import { httpRequest } from "."; -import { pathToUrl } from "@/routes/router"; -import { getNetworkName } from "@/lib/utils"; +import { pathToUrl } from "../routes/router"; +import { getNetworkName } from "../lib/utils"; import { metamaskPersonalSign, tenChainIDHex, tenscanLink, nativeCurrency, -} from "@/lib/constants"; +} from "../lib/constants"; export async function switchToTenNetwork() { try { diff --git a/tools/walletextension/api/ten-gateway/src/api/index.ts b/tools/walletextension/api/ten-gateway/frontend/src/api/index.ts similarity index 96% rename from tools/walletextension/api/ten-gateway/src/api/index.ts rename to tools/walletextension/api/ten-gateway/frontend/src/api/index.ts index a8b5a4730a..610ff3c6d8 100644 --- a/tools/walletextension/api/ten-gateway/src/api/index.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/api/index.ts @@ -1,4 +1,4 @@ -import { tenGatewayAddress } from "@/lib/constants"; +import { tenGatewayAddress } from "../lib/constants"; import axios, { AxiosInstance, AxiosRequestConfig } from "axios"; type HttpMethod = "get" | "post" | "put" | "patch" | "delete"; diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/layouts/default-layout.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/src/components/layouts/default-layout.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/layouts/default-layout.tsx diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/footer.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/layouts/footer.tsx similarity index 96% rename from tools/walletextension/api/ten-gateway/src/components/layouts/footer.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/layouts/footer.tsx index 05e48f44a9..3aaefbffff 100644 --- a/tools/walletextension/api/ten-gateway/src/components/layouts/footer.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/layouts/footer.tsx @@ -1,4 +1,4 @@ -import { socialLinks } from "@/lib/constants"; +import { socialLinks } from "../../lib/constants"; import { GitHubLogoIcon, TwitterLogoIcon, diff --git a/tools/walletextension/api/ten-gateway/src/components/layouts/header.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/layouts/header.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/src/components/layouts/header.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/layouts/header.tsx diff --git a/tools/walletextension/api/ten-gateway/src/components/main-nav.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/main-nav.tsx similarity index 96% rename from tools/walletextension/api/ten-gateway/src/components/main-nav.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/main-nav.tsx index ba59b97c7a..ad03fcd5e9 100644 --- a/tools/walletextension/api/ten-gateway/src/components/main-nav.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/main-nav.tsx @@ -2,7 +2,7 @@ import React from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { cn } from "@/lib/utils"; +import { cn } from "../lib/utils"; import { Button } from "./ui/button"; import { DropdownMenu, @@ -13,7 +13,7 @@ import { } from "./ui/dropdown-menu"; import { ChevronDownIcon } from "@radix-ui/react-icons"; -import { NavLink } from "@/types/interfaces"; +import { NavLink } from "../types/interfaces"; import { NavLinks } from "../routes"; const NavItem = ({ navLink }: { navLink: NavLink }) => { diff --git a/tools/walletextension/api/ten-gateway/src/components/mode-toggle.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/mode-toggle.tsx similarity index 92% rename from tools/walletextension/api/ten-gateway/src/components/mode-toggle.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/mode-toggle.tsx index 92640161e7..653c922006 100644 --- a/tools/walletextension/api/ten-gateway/src/components/mode-toggle.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/mode-toggle.tsx @@ -4,13 +4,13 @@ import * as React from "react"; import { Moon, Sun } from "lucide-react"; import { useTheme } from "next-themes"; -import { Button } from "@/components/ui/button"; +import { Button } from "./ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "./ui/dropdown-menu"; export function ModeToggle() { const { setTheme } = useTheme(); diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/connect-wallet.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/connect-wallet.tsx similarity index 80% rename from tools/walletextension/api/ten-gateway/src/components/modules/common/connect-wallet.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/connect-wallet.tsx index 877f4d45e0..e1c661649b 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/common/connect-wallet.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/connect-wallet.tsx @@ -1,6 +1,6 @@ -import { useWalletConnection } from "@/components/providers/wallet-provider"; -import { Button } from "@/components/ui/button"; -import useGatewayService from "@/services/useGatewayService"; +import { useWalletConnection } from "../../providers/wallet-provider"; +import { Button } from "../../ui/button"; +import useGatewayService from "../../../services/useGatewayService"; import { Link2Icon, LinkBreak2Icon } from "@radix-ui/react-icons"; import React from "react"; const ConnectWalletButton = () => { diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/copy.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/copy.tsx similarity index 85% rename from tools/walletextension/api/ten-gateway/src/components/modules/common/copy.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/copy.tsx index 1233f2aadc..94d8e0123d 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/common/copy.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/copy.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Button } from "@/components/ui/button"; -import { useCopy } from "@/hooks/useCopy"; +import { Button } from "../../ui/button"; +import { useCopy } from "../../../hooks/useCopy"; import { CopyIcon, CheckIcon } from "@radix-ui/react-icons"; const Copy = ({ value }: { value: string | number }) => { diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/network-status.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/network-status.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/src/components/modules/common/network-status.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/network-status.tsx diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/truncated-address.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/src/components/modules/common/truncated-address.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/truncated-address.tsx diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/connected.tsx similarity index 87% rename from tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/connected.tsx index 82af3eea01..65514a6e82 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/connected.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/connected.tsx @@ -1,6 +1,6 @@ -import { useWalletConnection } from "@/components/providers/wallet-provider"; -import { Badge } from "@/components/ui/badge"; -import { Button, LinkButton } from "@/components/ui/button"; +import { useWalletConnection } from "../../providers/wallet-provider"; +import { Badge } from "../../ui/badge"; +import { Button, LinkButton } from "../../ui/button"; import { Table, TableBody, @@ -8,12 +8,12 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import { Account } from "@/types/interfaces/WalletInterfaces"; +} from "../../ui/table"; +import { Account } from "../../../types/interfaces/WalletInterfaces"; import React from "react"; import TruncatedAddress from "../common/truncated-address"; -import { socialLinks } from "@/lib/constants"; -import { Skeleton } from "@/components/ui/skeleton"; +import { socialLinks } from "../../../lib/constants"; +import { Skeleton } from "../../ui/skeleton"; const Connected = () => { const { accounts, connectAccount, version, revokeAccounts } = diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx similarity index 92% rename from tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx index 6bbd69d961..9b1d2b4038 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/disconnected.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx @@ -1,9 +1,9 @@ -import { Alert, AlertDescription } from "@/components/ui/alert"; -import useGatewayService from "@/services/useGatewayService"; +import { Alert, AlertDescription } from "../../ui/alert"; +import useGatewayService from "../../../services/useGatewayService"; import { Terminal, Badge } from "lucide-react"; import React from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "../../ui/button"; import { Dialog, DialogClose, @@ -13,9 +13,9 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; +} from "../../ui/dialog"; import Copy from "../common/copy"; -import { testnetUrls, tenChainIDDecimal } from "@/lib/constants"; +import { testnetUrls, tenChainIDDecimal } from "../../../lib/constants"; import Link from "next/link"; const CONNECTION_STEPS = [ diff --git a/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx similarity index 64% rename from tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx index ef3ebacd3f..511f81d2c1 100644 --- a/tools/walletextension/api/ten-gateway/src/components/modules/home/index.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { Button } from "@/components/ui/button"; -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { Button } from "../../ui/button"; +import { Card, CardHeader, CardTitle, CardContent } from "../../ui/card"; import { Terminal } from "lucide-react"; -import { useWalletConnection } from "@/components/providers/wallet-provider"; +import { useWalletConnection } from "../../providers/wallet-provider"; import Connected from "./connected"; import Disconnected from "./disconnected"; diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/theme-provider.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/providers/theme-provider.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/src/components/providers/theme-provider.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/providers/theme-provider.tsx diff --git a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx similarity index 97% rename from tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx index 6e75e5cce7..12d1a11d65 100644 --- a/tools/walletextension/api/ten-gateway/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx @@ -4,20 +4,20 @@ import { WalletConnectionContextType, WalletConnectionProviderProps, Account, -} from "@/types/interfaces/WalletInterfaces"; +} from "../../types/interfaces/WalletInterfaces"; import { useToast } from "../ui/use-toast"; import { getRandomIntAsString, isTenChain, isValidUserIDFormat, -} from "@/lib/utils"; +} from "../../lib/utils"; import { accountIsAuthenticated, authenticateAccountWithTenGateway, fetchVersion, revokeAccountsApi, -} from "@/api/gateway"; -import { METAMASK_CONNECTION_TIMEOUT } from "@/lib/constants"; +} from "../../api/gateway"; +import { METAMASK_CONNECTION_TIMEOUT } from "../../lib/constants"; const WalletConnectionContext = createContext(null); diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/alert.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/alert.tsx similarity index 97% rename from tools/walletextension/api/ten-gateway/src/components/ui/alert.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/alert.tsx index 4447c835c6..ef4d6dbee7 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/alert.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/alert.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const alertVariants = cva( "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/badge.tsx similarity index 97% rename from tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/badge.tsx index 51d80562a1..afdfb44835 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/badge.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/badge.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/button.tsx similarity index 98% rename from tools/walletextension/api/ten-gateway/src/components/ui/button.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/button.tsx index d04c1d48c9..10c65cc717 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/button.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/button.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/card.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/card.tsx similarity index 98% rename from tools/walletextension/api/ten-gateway/src/components/ui/card.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/card.tsx index 8db8c4e4dd..6abe706a68 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/card.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/card.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const Card = React.forwardRef< HTMLDivElement, diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/dialog.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/dialog.tsx similarity index 99% rename from tools/walletextension/api/ten-gateway/src/components/ui/dialog.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/dialog.tsx index ed732ceb7d..e7fc0c8651 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/dialog.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/dialog.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const Dialog = DialogPrimitive.Root; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/dropdown-menu.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/dropdown-menu.tsx similarity index 99% rename from tools/walletextension/api/ten-gateway/src/components/ui/dropdown-menu.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/dropdown-menu.tsx index 5c1e59d372..a149167bf2 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/dropdown-menu.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/dropdown-menu.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import { Check, ChevronRight, Circle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const DropdownMenu = DropdownMenuPrimitive.Root; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/select.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/select.tsx similarity index 99% rename from tools/walletextension/api/ten-gateway/src/components/ui/select.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/select.tsx index 7d3600420d..5d4c184001 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/select.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/select.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as SelectPrimitive from "@radix-ui/react-select"; import { Check, ChevronDown } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const Select = SelectPrimitive.Root; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/separator.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/separator.tsx similarity index 95% rename from tools/walletextension/api/ten-gateway/src/components/ui/separator.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/separator.tsx index 11b8741882..e3eb575726 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/separator.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/separator.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as SeparatorPrimitive from "@radix-ui/react-separator"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const Separator = React.forwardRef< React.ElementRef, diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/skeleton.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/skeleton.tsx similarity index 85% rename from tools/walletextension/api/ten-gateway/src/components/ui/skeleton.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/skeleton.tsx index 2cdf440dcb..6e10fe239d 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/skeleton.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/skeleton.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; function Skeleton({ className, diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/table.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/table.tsx similarity index 98% rename from tools/walletextension/api/ten-gateway/src/components/ui/table.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/table.tsx index 1031493c9f..b5f5948e64 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/table.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/table.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const Table = React.forwardRef< HTMLTableElement, diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/toast.tsx similarity index 99% rename from tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/toast.tsx index 4ec3710bb0..97a2a0d5fd 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/toast.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/toast.tsx @@ -3,7 +3,7 @@ import * as ToastPrimitives from "@radix-ui/react-toast"; import { cva, type VariantProps } from "class-variance-authority"; import { X } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const ToastProvider = ToastPrimitives.Provider; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/toaster.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/toaster.tsx similarity index 89% rename from tools/walletextension/api/ten-gateway/src/components/ui/toaster.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/toaster.tsx index 5ff57090a7..585f8c1efb 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/toaster.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/toaster.tsx @@ -5,8 +5,8 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@/components/ui/toast"; -import { useToast } from "@/components/ui/use-toast"; +} from "./toast"; +import { useToast } from "./use-toast"; export function Toaster() { const { toasts } = useToast(); diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/tooltip.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/tooltip.tsx similarity index 96% rename from tools/walletextension/api/ten-gateway/src/components/ui/tooltip.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/tooltip.tsx index 13a05434be..2857f2fc57 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/tooltip.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/tooltip.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { cn } from "@/lib/utils"; +import { cn } from "../../lib/utils"; const TooltipProvider = TooltipPrimitive.Provider; diff --git a/tools/walletextension/api/ten-gateway/src/components/ui/use-toast.ts b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts similarity index 98% rename from tools/walletextension/api/ten-gateway/src/components/ui/use-toast.ts rename to tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts index edc829679d..e380dbbf1c 100644 --- a/tools/walletextension/api/ten-gateway/src/components/ui/use-toast.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts @@ -1,7 +1,7 @@ // Inspired by react-hot-toast library import * as React from "react"; -import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; +import type { ToastActionElement, ToastProps } from "./toast"; const TOAST_LIMIT = 1; const TOAST_REMOVE_DELAY = 1000000; diff --git a/tools/walletextension/api/ten-gateway/src/hooks/useCopy.ts b/tools/walletextension/api/ten-gateway/frontend/src/hooks/useCopy.ts similarity index 95% rename from tools/walletextension/api/ten-gateway/src/hooks/useCopy.ts rename to tools/walletextension/api/ten-gateway/frontend/src/hooks/useCopy.ts index d86878b5d3..249e817dfd 100644 --- a/tools/walletextension/api/ten-gateway/src/hooks/useCopy.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/hooks/useCopy.ts @@ -1,4 +1,4 @@ -import { useToast } from "@/components/ui/use-toast"; +import { useToast } from "../components/ui/use-toast"; import React from "react"; export const useCopy = () => { diff --git a/tools/walletextension/api/ten-gateway/src/lib/constants.ts b/tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/src/lib/constants.ts rename to tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts diff --git a/tools/walletextension/api/ten-gateway/src/lib/utils.ts b/tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/src/lib/utils.ts rename to tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts diff --git a/tools/walletextension/api/ten-gateway/src/pages/_app.tsx b/tools/walletextension/api/ten-gateway/frontend/src/pages/_app.tsx similarity index 61% rename from tools/walletextension/api/ten-gateway/src/pages/_app.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/pages/_app.tsx index e297ff07e3..e7dae5ec08 100644 --- a/tools/walletextension/api/ten-gateway/src/pages/_app.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/pages/_app.tsx @@ -1,9 +1,9 @@ -import { ThemeProvider } from "@/components/providers/theme-provider"; +import { ThemeProvider } from "../components/providers/theme-provider"; import "@/styles/globals.css"; import type { AppProps } from "next/app"; -import { Toaster } from "@/components/ui/toaster"; -import { WalletConnectionProvider } from "@/components/providers/wallet-provider"; -import { NetworkStatus } from "@/components/modules/common/network-status"; +import { Toaster } from "../components/ui/toaster"; +import { WalletConnectionProvider } from "../components/providers/wallet-provider"; +import { NetworkStatus } from "../components/modules/common/network-status"; export default function App({ Component, pageProps }: AppProps) { return ( diff --git a/tools/walletextension/api/ten-gateway/src/pages/_document.tsx b/tools/walletextension/api/ten-gateway/frontend/src/pages/_document.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/src/pages/_document.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/pages/_document.tsx diff --git a/tools/walletextension/api/ten-gateway/src/pages/index.tsx b/tools/walletextension/api/ten-gateway/frontend/src/pages/index.tsx similarity index 78% rename from tools/walletextension/api/ten-gateway/src/pages/index.tsx rename to tools/walletextension/api/ten-gateway/frontend/src/pages/index.tsx index 3966e09fa2..9a632c84a2 100644 --- a/tools/walletextension/api/ten-gateway/src/pages/index.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/pages/index.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Metadata } from "next"; -import Layout from "@/components/layouts/default-layout"; -import Home from "@/components/modules/home"; +import Layout from "../components/layouts/default-layout"; +import Home from "../components/modules/home"; export const metadata: Metadata = { title: "Tenscan Gateway", diff --git a/tools/walletextension/api/ten-gateway/src/routes/index.ts b/tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts similarity index 90% rename from tools/walletextension/api/ten-gateway/src/routes/index.ts rename to tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts index 0c16fd1829..6ca4c1695d 100644 --- a/tools/walletextension/api/ten-gateway/src/routes/index.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts @@ -1,4 +1,4 @@ -import { tenGatewayVersion } from "@/lib/constants"; +import { tenGatewayVersion } from "../lib/constants"; import { NavLink } from "../types/interfaces"; export const NavLinks: NavLink[] = []; diff --git a/tools/walletextension/api/ten-gateway/src/routes/router.ts b/tools/walletextension/api/ten-gateway/frontend/src/routes/router.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/src/routes/router.ts rename to tools/walletextension/api/ten-gateway/frontend/src/routes/router.ts diff --git a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts b/tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts similarity index 88% rename from tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts rename to tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts index a3df77c1d0..60c40b0e07 100644 --- a/tools/walletextension/api/ten-gateway/src/services/useGatewayService.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts @@ -2,12 +2,12 @@ import { addNetworkToMetaMask, joinTestnet, switchToTenNetwork, -} from "@/api/gateway"; -import { useWalletConnection } from "@/components/providers/wallet-provider"; -import { useToast } from "@/components/ui/use-toast"; -import { SWITCHED_CODE, tenGatewayVersion } from "@/lib/constants"; -import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "@/lib/utils"; -import { requestMethods } from "@/routes"; +} from "../api/gateway"; +import { useWalletConnection } from "../components/providers/wallet-provider"; +import { useToast } from "../components/ui/use-toast"; +import { SWITCHED_CODE, tenGatewayVersion } from "../lib/constants"; +import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "../lib/utils"; +import { requestMethods } from "../routes"; const useGatewayService = () => { const { toast } = useToast(); diff --git a/tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Bold_700.otf b/tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Bold_700.otf similarity index 100% rename from tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Bold_700.otf rename to tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Bold_700.otf diff --git a/tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Light_300.otf b/tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Light_300.otf similarity index 100% rename from tools/walletextension/api/ten-gateway/src/styles/fonts/CloudSoft-Light_300.otf rename to tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Light_300.otf diff --git a/tools/walletextension/api/ten-gateway/src/styles/fonts/README.txt b/tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/README.txt similarity index 100% rename from tools/walletextension/api/ten-gateway/src/styles/fonts/README.txt rename to tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/README.txt diff --git a/tools/walletextension/api/ten-gateway/src/styles/globals.css b/tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css similarity index 100% rename from tools/walletextension/api/ten-gateway/src/styles/globals.css rename to tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css diff --git a/tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/src/types/interfaces/WalletInterfaces.ts rename to tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts diff --git a/tools/walletextension/api/ten-gateway/src/types/interfaces/index.ts b/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/src/types/interfaces/index.ts rename to tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts diff --git a/tools/walletextension/api/ten-gateway/tailwind.config.js b/tools/walletextension/api/ten-gateway/frontend/tailwind.config.js similarity index 100% rename from tools/walletextension/api/ten-gateway/tailwind.config.js rename to tools/walletextension/api/ten-gateway/frontend/tailwind.config.js diff --git a/tools/walletextension/api/ten-gateway/tailwind.config.ts b/tools/walletextension/api/ten-gateway/frontend/tailwind.config.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/tailwind.config.ts rename to tools/walletextension/api/ten-gateway/frontend/tailwind.config.ts diff --git a/tools/walletextension/api/ten-gateway/tsconfig.json b/tools/walletextension/api/ten-gateway/frontend/tsconfig.json similarity index 100% rename from tools/walletextension/api/ten-gateway/tsconfig.json rename to tools/walletextension/api/ten-gateway/frontend/tsconfig.json From 66b045c3d53ad8973a8a34c4e8f86f22cf3148dd Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Tue, 28 Nov 2023 14:27:24 +0400 Subject: [PATCH 13/36] feat: authenticate account with EIP712 - refactor error handling - add loading state --- .../ten-gateway/frontend/src/api/gateway.ts | 65 +++++--- .../src/components/modules/home/index.tsx | 14 +- .../components/providers/wallet-provider.tsx | 145 ++++++++++-------- .../frontend/src/components/ui/use-toast.ts | 9 +- .../ten-gateway/frontend/src/lib/constants.ts | 21 +++ .../api/ten-gateway/frontend/src/lib/utils.ts | 9 +- .../ten-gateway/frontend/src/routes/index.ts | 1 + .../src/services/useGatewayService.ts | 26 +--- .../src/types/interfaces/WalletInterfaces.ts | 3 +- .../frontend/src/types/interfaces/index.ts | 10 +- 10 files changed, 191 insertions(+), 112 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts b/tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts index 638ed8e8b8..47b087d214 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts @@ -7,6 +7,7 @@ import { tenChainIDHex, tenscanLink, nativeCurrency, + typedData, } from "../lib/constants"; export async function switchToTenNetwork() { @@ -43,34 +44,61 @@ export async function accountIsAuthenticated( }); } -export async function authenticateAccountWithTenGateway( +const getSignature = async (account: string, data: any) => { + const { ethereum } = window as any; + const signature = await ethereum.request({ + method: metamaskPersonalSign, + params: [account, JSON.stringify(data)], + }); + + return signature; +}; + +export async function authenticateAccountWithTenGatewayEIP712( userID: string, account: string -): Promise { - const textToSign = `Register ${userID} for ${account.toLowerCase()}`; - const signature = await (window as any).ethereum - .request({ - method: metamaskPersonalSign, - params: [textToSign, account], - }) - .catch((error: any) => -1); +): Promise { + try { + const isAuthenticated = await accountIsAuthenticated(userID, account); + if (isAuthenticated) { + return "Account is already authenticated"; + } + const data = { + ...typedData, + message: { + ...typedData.message, + "Encryption Token": "0x" + userID, + }, + }; + const signature = await getSignature(account, data); - if (signature === -1) { - return "Signing failed"; + const auth = await authenticateUser(userID, { + signature, + address: account, + }); + return auth; + } catch (error) { + throw error; } +} - return await httpRequest({ +const authenticateUser = async ( + userID: string, + authenticateFields: { + signature: string; + address: string; + } +) => { + const authenticateResp = await httpRequest({ method: "post", url: pathToUrl(apiRoutes.authenticate), - data: { - signature, - message: textToSign, - }, + data: authenticateFields, searchParams: { u: userID, }, }); -} + return authenticateResp; +}; export async function revokeAccountsApi(userID: string): Promise { return await httpRequest({ @@ -103,10 +131,9 @@ export async function addNetworkToMetaMask(rpcUrls: string[]) { }, ], }); + return true; } catch (error) { console.error(error); return error; } - - return true; } diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx index 511f81d2c1..e2022eb0bc 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx @@ -1,17 +1,21 @@ import React from "react"; -import { Button } from "../../ui/button"; -import { Card, CardHeader, CardTitle, CardContent } from "../../ui/card"; -import { Terminal } from "lucide-react"; import { useWalletConnection } from "../../providers/wallet-provider"; import Connected from "./connected"; import Disconnected from "./disconnected"; +import { Skeleton } from "@/components/ui/skeleton"; const Home = () => { - const { walletConnected } = useWalletConnection(); + const { walletConnected, loading } = useWalletConnection(); return (
- {walletConnected ? : } + {loading ? ( + + ) : walletConnected ? ( + + ) : ( + + )}
); }; diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx index 12d1a11d65..ceac9fbaf6 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx @@ -5,7 +5,7 @@ import { WalletConnectionProviderProps, Account, } from "../../types/interfaces/WalletInterfaces"; -import { useToast } from "../ui/use-toast"; +import { showToast } from "../ui/use-toast"; import { getRandomIntAsString, isTenChain, @@ -13,11 +13,13 @@ import { } from "../../lib/utils"; import { accountIsAuthenticated, - authenticateAccountWithTenGateway, + authenticateAccountWithTenGatewayEIP712, fetchVersion, revokeAccountsApi, } from "../../api/gateway"; import { METAMASK_CONNECTION_TIMEOUT } from "../../lib/constants"; +import { requestMethods } from "@/routes"; +import { ToastType } from "@/types/interfaces"; const WalletConnectionContext = createContext(null); @@ -35,11 +37,10 @@ export const useWalletConnection = (): WalletConnectionContextType => { export const WalletConnectionProvider = ({ children, }: WalletConnectionProviderProps) => { - const { toast } = useToast(); - const [walletConnected, setWalletConnected] = useState(false); - const [userID, setUserID] = useState(null); + const [userID, setUserID] = useState(""); const [version, setVersion] = useState(null); + const [loading, setLoading] = useState(true); const [accounts, setAccounts] = useState(null); const [provider, setProvider] = useState(null); @@ -68,10 +69,11 @@ export const WalletConnectionProvider = ({ }, []); const checkIfMetamaskIsLoaded = async () => { - if (window && (window as any).ethereum) { + const { ethereum } = window as any; + if (ethereum) { await handleEthereum(); } else { - toast({ description: "Connecting to MetaMask..." }); + showToast(ToastType.INFO, "Connecting to MetaMask..."); window.addEventListener("ethereum#initialized", handleEthereum, { once: true, }); @@ -82,121 +84,133 @@ export const WalletConnectionProvider = ({ const handleEthereum = async () => { const { ethereum } = window as any; if (ethereum && ethereum.isMetaMask) { - const provider = new ethers.providers.Web3Provider( - (window as any).ethereum - ); + const provider = new ethers.providers.Web3Provider(ethereum); setProvider(provider); - await displayCorrectScreenBasedOnMetamaskAndUserID(); + const fetchedUserID = await getUserID(provider); + await displayCorrectScreenBasedOnMetamaskAndUserID( + fetchedUserID, + provider + ); } else { - toast({ description: "Please install MetaMask to use Ten Gateway." }); + showToast( + ToastType.WARNING, + "Please install MetaMask to use Ten Gateway." + ); } }; - const getUserID = async () => { + const getUserID = async (provider: ethers.providers.Web3Provider) => { if (!provider) { return null; } try { if (await isTenChain()) { - const id = await provider.send("eth_getStorageAt", [ + const id = await provider.send(requestMethods.getStorageAt, [ "getUserID", getRandomIntAsString(0, 1000), null, ]); + setUserID(id); return id; } else { return null; } } catch (e: any) { - toast({ - description: - `${e.message} ${e.data?.message}` || - "Error: Could not fetch your userID. Please try again later.", - }); + showToast( + ToastType.DESTRUCTIVE, + `${e.message} ${e.data?.message}` || + "Error: Could not fetch your user ID. Please try again later." + ); console.error(e); return null; } }; - const displayCorrectScreenBasedOnMetamaskAndUserID = async () => { + const displayCorrectScreenBasedOnMetamaskAndUserID = async ( + userID?: any, + provider?: any + ) => { setVersion(await fetchVersion()); - if (await isTenChain()) { - const userID = await getUserID(); - setUserID(userID); - if (provider && userID && isValidUserIDFormat(userID)) { - await getAccounts(); + if (userID) { + await getAccounts(provider); } else { setWalletConnected(false); } } else { setWalletConnected(false); } + + setLoading(false); }; const connectAccount = async (account: string) => { + if (loading) { + return; + } + if (!userID) { return; } - await authenticateAccountWithTenGateway(userID, account); + await authenticateAccountWithTenGatewayEIP712(userID, account); }; const revokeAccounts = async () => { if (!userID) { return; } - const revokeResponse = await revokeAccountsApi(userID); - if (revokeResponse === "success") { - toast({ - variant: "success", - description: "Successfully revoked all accounts.", - }); + if (revokeResponse === ToastType.SUCCESS) { + showToast(ToastType.DESTRUCTIVE, "Accounts revoked!"); setAccounts(null); setWalletConnected(false); } }; - const getAccounts = async () => { - if (!provider) { - toast({ - variant: "destructive", - description: "No provider found. Please try again later.", - }); - return; - } + const getAccounts = async (provider: ethers.providers.Web3Provider) => { + try { + if (!provider) { + showToast( + ToastType.DESTRUCTIVE, + "No provider found. Please try again later." + ); + return; + } - toast({ variant: "info", description: "Getting accounts..." }); - if (!(await isTenChain())) { - toast({ - variant: "warning", - description: "Please connect to the Ten chain.", - }); - return; - } - const accounts = await provider.listAccounts(); + showToast(ToastType.INFO, "Getting accounts..."); - if (accounts.length === 0) { - toast({ - variant: "destructive", - description: "No MetaMask accounts found.", - }); - return; - } + if (!(await isTenChain())) { + showToast(ToastType.DESTRUCTIVE, "Please connect to the Ten chain."); + return; + } - const user = await getUserID(); - setUserID(user); + const accounts = await provider.listAccounts(); - setAccounts( - await Promise.all( - accounts.map(async (account) => ({ + if (accounts.length === 0) { + showToast(ToastType.DESTRUCTIVE, "No MetaMask accounts found."); + return; + } + + for (const account of accounts) { + await authenticateAccountWithTenGatewayEIP712(userID, account); + } + + const updatedAccounts = await Promise.all( + accounts.map(async (account: string) => ({ name: account, - connected: await accountIsAuthenticated(user, account), + connected: await accountIsAuthenticated(userID, account), })) - ) - ); - setWalletConnected(true); + ); + + setAccounts(updatedAccounts); + setWalletConnected(true); + + showToast(ToastType.SUCCESS, "Accounts authenticated successfully!"); + } catch (error) { + console.error(error); + showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); + } }; const walletConnectionContextValue: WalletConnectionContextType = { @@ -209,6 +223,7 @@ export const WalletConnectionProvider = ({ version, revokeAccounts, getAccounts, + loading, }; return ( diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts index e380dbbf1c..a693743579 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts @@ -186,4 +186,11 @@ function useToast() { }; } -export { useToast, toast }; +const showToast = (variant: ToastProps["variant"], description: string) => { + toast({ + variant, + description, + }); +}; + +export { useToast, showToast, toast }; diff --git a/tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts b/tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts index 07a64205f8..8da38091e6 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts @@ -30,6 +30,7 @@ export const testnetUrls = { }; export const SWITCHED_CODE = 4902; +export const userIDHexLength = 40; export const tenGatewayVersion = "v1"; export const tenChainIDDecimal = 443; @@ -44,3 +45,23 @@ export const nativeCurrency = { symbol: "ETH", decimals: 18, }; + +export const typedData = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + ], + Authentication: [{ name: "Encryption Token", type: "address" }], + }, + primaryType: "Authentication", + domain: { + name: "Ten", + version: "1.0", + chainId: tenChainIDDecimal, + }, + message: { + "Encryption Token": "0x", + }, +}; diff --git a/tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts b/tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts index 07e6981afa..da06818815 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts @@ -1,7 +1,12 @@ import { type ClassValue, clsx } from "clsx"; import { formatDistanceToNow } from "date-fns"; import { twMerge } from "tailwind-merge"; -import { tenChainIDHex, tenGatewayAddress, testnetUrls } from "./constants"; +import { + tenChainIDHex, + tenGatewayAddress, + testnetUrls, + userIDHexLength, +} from "./constants"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -13,7 +18,7 @@ export function formatTimeAgo(unixTimestampSeconds: string) { } export function isValidUserIDFormat(value: string) { - return typeof value === "string" && value.length === 64; + return typeof value === "string" && value.length === userIDHexLength; } export function getRandomIntAsString(min: number, max: number) { diff --git a/tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts b/tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts index 6ca4c1695d..0fa649e4d5 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts @@ -15,4 +15,5 @@ export const requestMethods = { connectAccounts: "eth_requestAccounts", switchNetwork: "wallet_switchEthereumChain", addNetwork: "wallet_addEthereumChain", + getStorageAt: "eth_getStorageAt", }; diff --git a/tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts b/tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts index 60c40b0e07..44d16889e8 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts @@ -1,16 +1,16 @@ +import { ToastType } from "@/types/interfaces"; import { addNetworkToMetaMask, joinTestnet, switchToTenNetwork, } from "../api/gateway"; import { useWalletConnection } from "../components/providers/wallet-provider"; -import { useToast } from "../components/ui/use-toast"; +import { showToast } from "../components/ui/use-toast"; import { SWITCHED_CODE, tenGatewayVersion } from "../lib/constants"; import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "../lib/utils"; import { requestMethods } from "../routes"; const useGatewayService = () => { - const { toast } = useToast(); const { provider } = useWalletConnection(); const { userID, setUserID, getAccounts } = useWalletConnection(); @@ -19,12 +19,9 @@ const useGatewayService = () => { await (window as any).ethereum.request({ method: requestMethods.connectAccounts, }); - toast({ variant: "success", description: "Connected to Ten Network" }); + showToast(ToastType.SUCCESS, "Connected to Ten Network"); } catch (error) { - toast({ - variant: "destructive", - description: "Unable to connect to Ten Network", - }); + showToast(ToastType.DESTRUCTIVE, "Unable to connect to Ten Network"); return null; } }; @@ -37,7 +34,7 @@ const useGatewayService = () => { const accounts = await provider.listAccounts(); return accounts.length > 0; } catch (error) { - toast({ variant: "destructive", description: "Unable to get accounts" }); + showToast(ToastType.DESTRUCTIVE, "Unable to get accounts"); } return false; }; @@ -65,23 +62,16 @@ const useGatewayService = () => { } if (!(await isMetamaskConnected())) { - toast({ - variant: "info", - description: "No accounts found, connecting...", - }); + showToast(ToastType.INFO, "No accounts found, connecting..."); await connectAccounts(); } if (!provider) { return; } - await getAccounts(); + await getAccounts(provider); } catch (error: any) { - console.error("Error:", error.message); - toast({ - variant: "destructive", - description: `${error.message}`, - }); + showToast(ToastType.DESTRUCTIVE, `${error.message}`); } }; diff --git a/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts index f5c803c250..bcd9450990 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts @@ -9,7 +9,8 @@ export interface WalletConnectionContextType { provider: ethers.providers.Web3Provider | null; version: string | null; revokeAccounts: () => void; - getAccounts: () => Promise; + getAccounts: (provider: ethers.providers.Web3Provider) => Promise; + loading: boolean; } export interface Props { diff --git a/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts b/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts index 7558d165ed..fefe210d9b 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts +++ b/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts @@ -50,7 +50,7 @@ export interface ResponseDataInterface { item: T; message: string; pagination?: PaginationInterface; - success: string; + success: boolean; } export type NavLink = { @@ -60,3 +60,11 @@ export type NavLink = { isExternal?: boolean; subNavLinks?: NavLink[]; }; + +export enum ToastType { + INFO = "info", + SUCCESS = "success", + WARNING = "warning", + DESTRUCTIVE = "destructive", + DEFAULT = "default", +} From e22a921e92d0425840d09b1ed3848d5884c09a9a Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Tue, 28 Nov 2023 17:13:19 +0400 Subject: [PATCH 14/36] add `README` file --- .../api/ten-gateway/frontend/README.md | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/frontend/README.md b/tools/walletextension/api/ten-gateway/frontend/README.md index a75ac52488..52993e98be 100644 --- a/tools/walletextension/api/ten-gateway/frontend/README.md +++ b/tools/walletextension/api/ten-gateway/frontend/README.md @@ -1,40 +1,62 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Ten Gateway -## Getting Started +Ten Gateway is a Next.js and Tailwind CSS-powered application that serves as a Blockchain Explorer for The Encryption Network (TEN). This explorer allows users to interact with and explore the blocks, transactions, batches, resources, and personal data on the TEN blockchain. Tenscan is built using the Shadcn-UI Component Library for a consistent and visually appealing user interface. -First, run the development server: +## Folder Structure -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev ``` +📁 Ten Gateway +├── 📁 api - Contains server-side code, such as API routes or server logic +├── 📁 public - This directory is used to serve static assets. Files inside this directory can be referenced in your code with a URL path +├── 📁 src - Main source code directory for this project +│ ├── 📁 components - Contains reusable React components used throughout the application +│ ├── 📁 pages - Typically used for Next.js pages. Each .tsx or .js file in this directory becomes a route in your application +│ ├── 📁 hooks - Custom React hooks that can be shared and reused across components +│ ├── 📁 lib - Utility functions or modules that provide common functionalities across the application +│ ├── 📁 routes - Route-related logic or configuration can be placed in this directory +│ ├── 📁 services - Used for services that interact with external APIs or handle other data-related tasks +│ └── 📁 types - Type definitions (.d.ts files or TypeScript files) for TypeScript, describing the shape of data and objects used in the application +└── 📁 styles - Global styles, stylesheets, or styling-related configurations for this project +``` + +## Getting Started + +1. **Clone the Repository:** + ```bash + git clone https://github.com/ten-protocol/go-ten.git + cd go-ten/tools/walletextension/api/ten-gateway/frontend + ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +2. **Install Dependencies:** + ```bash + npm install + ``` -You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. +3. **Run the Development Server:** + ```bash + npm run dev + ``` -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. + The application will be accessible at [http://localhost:3000](http://localhost:3000). -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. +## Usage -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. +- Connect to Ten Testnet using the button in the top right corner of the application or on the homepage +- You can request tokens from the Discord bot by typing `!faucet ` in the #faucet channel +- You can also revoke accounts by clicking the "Revoke Accounts" button on the homepage -## Learn More +## Built With -To learn more about Next.js, take a look at the following resources: +- [Next.js](https://nextjs.org/) +- [Tailwind CSS](https://tailwindcss.com/) +- [Shadcn-UI](https://shadcn.com/) +- [TypeScript](https://www.typescriptlang.org/) -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +## Contributing -## Deploy on Vercel +Contributions are welcome! Please follow our [contribution guidelines](/docs/_docs/community/contributions.md). -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +## License -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +This project is licensed under the [GNU Affero General Public License v3.0](/LICENSE). \ No newline at end of file From 11bfd5397cb042af483a8ce61edd56f815bfeaa7 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Tue, 28 Nov 2023 22:40:43 +0400 Subject: [PATCH 15/36] - update css variables and constant urls - fix: remove conditional params --- .../src/components/modules/home/disconnected.tsx | 9 +++++++-- .../src/components/providers/wallet-provider.tsx | 9 ++++----- .../api/ten-gateway/frontend/src/styles/globals.css | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx index 9b1d2b4038..6cb9758fd4 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx @@ -77,9 +77,14 @@ const Disconnected = () => {

RPC URL:{" "} - + {testnetUrls.default.url} - +

diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx index ceac9fbaf6..3e44036477 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx @@ -42,14 +42,13 @@ export const WalletConnectionProvider = ({ const [version, setVersion] = useState(null); const [loading, setLoading] = useState(true); const [accounts, setAccounts] = useState(null); - const [provider, setProvider] = - useState(null); + const [provider, setProvider] = useState({} as ethers.providers.Web3Provider); useEffect(() => { const { ethereum } = window as any; const handleAccountsChanged = async () => { if (userID && isValidUserIDFormat(userID)) { - await displayCorrectScreenBasedOnMetamaskAndUserID(); + await displayCorrectScreenBasedOnMetamaskAndUserID(userID, provider); } }; ethereum.on("accountsChanged", handleAccountsChanged); @@ -128,8 +127,8 @@ export const WalletConnectionProvider = ({ }; const displayCorrectScreenBasedOnMetamaskAndUserID = async ( - userID?: any, - provider?: any + userID: string, + provider: ethers.providers.Web3Provider ) => { setVersion(await fetchVersion()); if (await isTenChain()) { diff --git a/tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css b/tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css index 9fb737a472..7557b32883 100644 --- a/tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css +++ b/tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css @@ -65,7 +65,7 @@ --warning: 45 80% 50%; --warning-foreground: 0 0% 20%; - --info: 224, 40%, 54%; + --info: 224 40% 54%; --info-foreground: 222.2 47.4% 11.2%; --destructive: 0 100% 50%; @@ -107,7 +107,7 @@ --warning: 45 80% 40%; --warning-foreground: 0 0% 80%; - --info: 224, 40%, 54%; + --info: 224 40% 54%; --info-foreground: 222.2 47.4% 11.2%; --destructive: 0 63% 31%; @@ -190,5 +190,5 @@ --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; - --ring: 212.7 26.8% 83.9; + --ring: 212.7 26.8% 83.9%; } From 284592e813d64f66e2d0038095a44b3bcd74e5b7 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Wed, 29 Nov 2023 00:08:18 +0400 Subject: [PATCH 16/36] move gateway base to `walletextension/frontend` --- .../{api/ten-gateway => }/frontend/.eslintrc.json | 0 .../{api/ten-gateway => }/frontend/.gitignore | 0 .../{api/ten-gateway => }/frontend/README.md | 0 .../{api/ten-gateway => }/frontend/next.config.js | 0 .../ten-gateway => }/frontend/package-lock.json | 0 .../{api/ten-gateway => }/frontend/package.json | 0 .../ten-gateway => }/frontend/postcss.config.js | 0 .../frontend/public/assets/images/ten-temp-logo.svg | 0 .../ten-gateway => }/frontend/public/favicon.ico | Bin .../ten-gateway => }/frontend/src/api/gateway.ts | 0 .../{api/ten-gateway => }/frontend/src/api/index.ts | 0 .../src/components/layouts/default-layout.tsx | 0 .../frontend/src/components/layouts/footer.tsx | 0 .../frontend/src/components/layouts/header.tsx | 0 .../frontend/src/components/main-nav.tsx | 0 .../frontend/src/components/mode-toggle.tsx | 0 .../components/modules/common/connect-wallet.tsx | 0 .../frontend/src/components/modules/common/copy.tsx | 0 .../components/modules/common/network-status.tsx | 0 .../components/modules/common/truncated-address.tsx | 0 .../src/components/modules/home/connected.tsx | 0 .../src/components/modules/home/disconnected.tsx | 0 .../frontend/src/components/modules/home/index.tsx | 0 .../src/components/providers/theme-provider.tsx | 0 .../src/components/providers/wallet-provider.tsx | 0 .../frontend/src/components/ui/alert.tsx | 0 .../frontend/src/components/ui/badge.tsx | 0 .../frontend/src/components/ui/button.tsx | 0 .../frontend/src/components/ui/card.tsx | 0 .../frontend/src/components/ui/dialog.tsx | 0 .../frontend/src/components/ui/dropdown-menu.tsx | 0 .../frontend/src/components/ui/select.tsx | 0 .../frontend/src/components/ui/separator.tsx | 0 .../frontend/src/components/ui/skeleton.tsx | 0 .../frontend/src/components/ui/table.tsx | 0 .../frontend/src/components/ui/toast.tsx | 0 .../frontend/src/components/ui/toaster.tsx | 0 .../frontend/src/components/ui/tooltip.tsx | 0 .../frontend/src/components/ui/use-toast.ts | 0 .../ten-gateway => }/frontend/src/hooks/useCopy.ts | 0 .../ten-gateway => }/frontend/src/lib/constants.ts | 0 .../{api/ten-gateway => }/frontend/src/lib/utils.ts | 0 .../ten-gateway => }/frontend/src/pages/_app.tsx | 0 .../frontend/src/pages/_document.tsx | 0 .../ten-gateway => }/frontend/src/pages/index.tsx | 0 .../ten-gateway => }/frontend/src/routes/index.ts | 0 .../ten-gateway => }/frontend/src/routes/router.ts | 0 .../frontend/src/services/useGatewayService.ts | 0 .../src/styles/fonts/CloudSoft-Bold_700.otf | Bin .../src/styles/fonts/CloudSoft-Light_300.otf | Bin .../frontend/src/styles/fonts/README.txt | 0 .../frontend/src/styles/globals.css | 0 .../src/types/interfaces/WalletInterfaces.ts | 0 .../frontend/src/types/interfaces/index.ts | 0 .../ten-gateway => }/frontend/tailwind.config.js | 0 .../ten-gateway => }/frontend/tailwind.config.ts | 0 .../{api/ten-gateway => }/frontend/tsconfig.json | 0 57 files changed, 0 insertions(+), 0 deletions(-) rename tools/walletextension/{api/ten-gateway => }/frontend/.eslintrc.json (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/.gitignore (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/README.md (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/next.config.js (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/package-lock.json (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/package.json (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/postcss.config.js (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/public/assets/images/ten-temp-logo.svg (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/public/favicon.ico (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/api/gateway.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/api/index.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/layouts/default-layout.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/layouts/footer.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/layouts/header.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/main-nav.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/mode-toggle.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/modules/common/connect-wallet.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/modules/common/copy.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/modules/common/network-status.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/modules/common/truncated-address.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/modules/home/connected.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/modules/home/disconnected.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/modules/home/index.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/providers/theme-provider.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/providers/wallet-provider.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/alert.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/badge.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/button.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/card.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/dialog.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/dropdown-menu.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/select.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/separator.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/skeleton.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/table.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/toast.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/toaster.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/tooltip.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/components/ui/use-toast.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/hooks/useCopy.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/lib/constants.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/lib/utils.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/pages/_app.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/pages/_document.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/pages/index.tsx (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/routes/index.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/routes/router.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/services/useGatewayService.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/styles/fonts/CloudSoft-Bold_700.otf (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/styles/fonts/CloudSoft-Light_300.otf (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/styles/fonts/README.txt (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/styles/globals.css (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/types/interfaces/WalletInterfaces.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/src/types/interfaces/index.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/tailwind.config.js (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/tailwind.config.ts (100%) rename tools/walletextension/{api/ten-gateway => }/frontend/tsconfig.json (100%) diff --git a/tools/walletextension/api/ten-gateway/frontend/.eslintrc.json b/tools/walletextension/frontend/.eslintrc.json similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/.eslintrc.json rename to tools/walletextension/frontend/.eslintrc.json diff --git a/tools/walletextension/api/ten-gateway/frontend/.gitignore b/tools/walletextension/frontend/.gitignore similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/.gitignore rename to tools/walletextension/frontend/.gitignore diff --git a/tools/walletextension/api/ten-gateway/frontend/README.md b/tools/walletextension/frontend/README.md similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/README.md rename to tools/walletextension/frontend/README.md diff --git a/tools/walletextension/api/ten-gateway/frontend/next.config.js b/tools/walletextension/frontend/next.config.js similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/next.config.js rename to tools/walletextension/frontend/next.config.js diff --git a/tools/walletextension/api/ten-gateway/frontend/package-lock.json b/tools/walletextension/frontend/package-lock.json similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/package-lock.json rename to tools/walletextension/frontend/package-lock.json diff --git a/tools/walletextension/api/ten-gateway/frontend/package.json b/tools/walletextension/frontend/package.json similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/package.json rename to tools/walletextension/frontend/package.json diff --git a/tools/walletextension/api/ten-gateway/frontend/postcss.config.js b/tools/walletextension/frontend/postcss.config.js similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/postcss.config.js rename to tools/walletextension/frontend/postcss.config.js diff --git a/tools/walletextension/api/ten-gateway/frontend/public/assets/images/ten-temp-logo.svg b/tools/walletextension/frontend/public/assets/images/ten-temp-logo.svg similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/public/assets/images/ten-temp-logo.svg rename to tools/walletextension/frontend/public/assets/images/ten-temp-logo.svg diff --git a/tools/walletextension/api/ten-gateway/frontend/public/favicon.ico b/tools/walletextension/frontend/public/favicon.ico similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/public/favicon.ico rename to tools/walletextension/frontend/public/favicon.ico diff --git a/tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts b/tools/walletextension/frontend/src/api/gateway.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/api/gateway.ts rename to tools/walletextension/frontend/src/api/gateway.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/api/index.ts b/tools/walletextension/frontend/src/api/index.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/api/index.ts rename to tools/walletextension/frontend/src/api/index.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/layouts/default-layout.tsx b/tools/walletextension/frontend/src/components/layouts/default-layout.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/layouts/default-layout.tsx rename to tools/walletextension/frontend/src/components/layouts/default-layout.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/layouts/footer.tsx b/tools/walletextension/frontend/src/components/layouts/footer.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/layouts/footer.tsx rename to tools/walletextension/frontend/src/components/layouts/footer.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/layouts/header.tsx b/tools/walletextension/frontend/src/components/layouts/header.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/layouts/header.tsx rename to tools/walletextension/frontend/src/components/layouts/header.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/main-nav.tsx b/tools/walletextension/frontend/src/components/main-nav.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/main-nav.tsx rename to tools/walletextension/frontend/src/components/main-nav.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/mode-toggle.tsx b/tools/walletextension/frontend/src/components/mode-toggle.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/mode-toggle.tsx rename to tools/walletextension/frontend/src/components/mode-toggle.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/connect-wallet.tsx b/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/connect-wallet.tsx rename to tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/copy.tsx b/tools/walletextension/frontend/src/components/modules/common/copy.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/copy.tsx rename to tools/walletextension/frontend/src/components/modules/common/copy.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/network-status.tsx b/tools/walletextension/frontend/src/components/modules/common/network-status.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/network-status.tsx rename to tools/walletextension/frontend/src/components/modules/common/network-status.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/truncated-address.tsx b/tools/walletextension/frontend/src/components/modules/common/truncated-address.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/modules/common/truncated-address.tsx rename to tools/walletextension/frontend/src/components/modules/common/truncated-address.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/connected.tsx b/tools/walletextension/frontend/src/components/modules/home/connected.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/connected.tsx rename to tools/walletextension/frontend/src/components/modules/home/connected.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx b/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/disconnected.tsx rename to tools/walletextension/frontend/src/components/modules/home/disconnected.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx b/tools/walletextension/frontend/src/components/modules/home/index.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/modules/home/index.tsx rename to tools/walletextension/frontend/src/components/modules/home/index.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/providers/theme-provider.tsx b/tools/walletextension/frontend/src/components/providers/theme-provider.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/providers/theme-provider.tsx rename to tools/walletextension/frontend/src/components/providers/theme-provider.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/providers/wallet-provider.tsx rename to tools/walletextension/frontend/src/components/providers/wallet-provider.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/alert.tsx b/tools/walletextension/frontend/src/components/ui/alert.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/alert.tsx rename to tools/walletextension/frontend/src/components/ui/alert.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/badge.tsx b/tools/walletextension/frontend/src/components/ui/badge.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/badge.tsx rename to tools/walletextension/frontend/src/components/ui/badge.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/button.tsx b/tools/walletextension/frontend/src/components/ui/button.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/button.tsx rename to tools/walletextension/frontend/src/components/ui/button.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/card.tsx b/tools/walletextension/frontend/src/components/ui/card.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/card.tsx rename to tools/walletextension/frontend/src/components/ui/card.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/dialog.tsx b/tools/walletextension/frontend/src/components/ui/dialog.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/dialog.tsx rename to tools/walletextension/frontend/src/components/ui/dialog.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/dropdown-menu.tsx b/tools/walletextension/frontend/src/components/ui/dropdown-menu.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/dropdown-menu.tsx rename to tools/walletextension/frontend/src/components/ui/dropdown-menu.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/select.tsx b/tools/walletextension/frontend/src/components/ui/select.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/select.tsx rename to tools/walletextension/frontend/src/components/ui/select.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/separator.tsx b/tools/walletextension/frontend/src/components/ui/separator.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/separator.tsx rename to tools/walletextension/frontend/src/components/ui/separator.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/skeleton.tsx b/tools/walletextension/frontend/src/components/ui/skeleton.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/skeleton.tsx rename to tools/walletextension/frontend/src/components/ui/skeleton.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/table.tsx b/tools/walletextension/frontend/src/components/ui/table.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/table.tsx rename to tools/walletextension/frontend/src/components/ui/table.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/toast.tsx b/tools/walletextension/frontend/src/components/ui/toast.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/toast.tsx rename to tools/walletextension/frontend/src/components/ui/toast.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/toaster.tsx b/tools/walletextension/frontend/src/components/ui/toaster.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/toaster.tsx rename to tools/walletextension/frontend/src/components/ui/toaster.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/tooltip.tsx b/tools/walletextension/frontend/src/components/ui/tooltip.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/tooltip.tsx rename to tools/walletextension/frontend/src/components/ui/tooltip.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts b/tools/walletextension/frontend/src/components/ui/use-toast.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/components/ui/use-toast.ts rename to tools/walletextension/frontend/src/components/ui/use-toast.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/hooks/useCopy.ts b/tools/walletextension/frontend/src/hooks/useCopy.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/hooks/useCopy.ts rename to tools/walletextension/frontend/src/hooks/useCopy.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/lib/constants.ts rename to tools/walletextension/frontend/src/lib/constants.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts b/tools/walletextension/frontend/src/lib/utils.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/lib/utils.ts rename to tools/walletextension/frontend/src/lib/utils.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/pages/_app.tsx b/tools/walletextension/frontend/src/pages/_app.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/pages/_app.tsx rename to tools/walletextension/frontend/src/pages/_app.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/pages/_document.tsx b/tools/walletextension/frontend/src/pages/_document.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/pages/_document.tsx rename to tools/walletextension/frontend/src/pages/_document.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/pages/index.tsx b/tools/walletextension/frontend/src/pages/index.tsx similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/pages/index.tsx rename to tools/walletextension/frontend/src/pages/index.tsx diff --git a/tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts b/tools/walletextension/frontend/src/routes/index.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/routes/index.ts rename to tools/walletextension/frontend/src/routes/index.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/routes/router.ts b/tools/walletextension/frontend/src/routes/router.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/routes/router.ts rename to tools/walletextension/frontend/src/routes/router.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/services/useGatewayService.ts rename to tools/walletextension/frontend/src/services/useGatewayService.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Bold_700.otf b/tools/walletextension/frontend/src/styles/fonts/CloudSoft-Bold_700.otf similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Bold_700.otf rename to tools/walletextension/frontend/src/styles/fonts/CloudSoft-Bold_700.otf diff --git a/tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Light_300.otf b/tools/walletextension/frontend/src/styles/fonts/CloudSoft-Light_300.otf similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/CloudSoft-Light_300.otf rename to tools/walletextension/frontend/src/styles/fonts/CloudSoft-Light_300.otf diff --git a/tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/README.txt b/tools/walletextension/frontend/src/styles/fonts/README.txt similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/styles/fonts/README.txt rename to tools/walletextension/frontend/src/styles/fonts/README.txt diff --git a/tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css b/tools/walletextension/frontend/src/styles/globals.css similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/styles/globals.css rename to tools/walletextension/frontend/src/styles/globals.css diff --git a/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/WalletInterfaces.ts rename to tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts b/tools/walletextension/frontend/src/types/interfaces/index.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/src/types/interfaces/index.ts rename to tools/walletextension/frontend/src/types/interfaces/index.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/tailwind.config.js b/tools/walletextension/frontend/tailwind.config.js similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/tailwind.config.js rename to tools/walletextension/frontend/tailwind.config.js diff --git a/tools/walletextension/api/ten-gateway/frontend/tailwind.config.ts b/tools/walletextension/frontend/tailwind.config.ts similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/tailwind.config.ts rename to tools/walletextension/frontend/tailwind.config.ts diff --git a/tools/walletextension/api/ten-gateway/frontend/tsconfig.json b/tools/walletextension/frontend/tsconfig.json similarity index 100% rename from tools/walletextension/api/ten-gateway/frontend/tsconfig.json rename to tools/walletextension/frontend/tsconfig.json From 15ba2478aaa03ff1e39d8d3c9a3ed8f918a60c4f Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Wed, 29 Nov 2023 19:37:38 +0400 Subject: [PATCH 17/36] update readme --- tools/walletextension/frontend/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/walletextension/frontend/README.md b/tools/walletextension/frontend/README.md index 52993e98be..dfa5e41705 100644 --- a/tools/walletextension/frontend/README.md +++ b/tools/walletextension/frontend/README.md @@ -1,6 +1,6 @@ # Ten Gateway -Ten Gateway is a Next.js and Tailwind CSS-powered application that serves as a Blockchain Explorer for The Encryption Network (TEN). This explorer allows users to interact with and explore the blocks, transactions, batches, resources, and personal data on the TEN blockchain. Tenscan is built using the Shadcn-UI Component Library for a consistent and visually appealing user interface. +Ten Gateway is a Next.js and Tailwind CSS-powered application. ## Folder Structure @@ -24,7 +24,7 @@ Ten Gateway is a Next.js and Tailwind CSS-powered application that serves as a B 1. **Clone the Repository:** ```bash git clone https://github.com/ten-protocol/go-ten.git - cd go-ten/tools/walletextension/api/ten-gateway/frontend + cd go-ten/tools/walletextension/frontend ``` 2. **Install Dependencies:** From e30d285c68447ffe112faf7e019f7f433e4607be Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 30 Nov 2023 02:12:47 +0400 Subject: [PATCH 18/36] refactor: account signing and authentication --- .../frontend/src/api/gateway.ts | 62 ++++++++++++++----- .../src/components/modules/home/connected.tsx | 2 +- .../components/providers/wallet-provider.tsx | 58 +++++++++++------ .../frontend/src/lib/constants.ts | 24 +------ .../frontend/src/routes/index.ts | 1 + .../src/services/useGatewayService.ts | 15 +++-- .../src/types/interfaces/GatewayInterfaces.ts | 3 + .../src/types/interfaces/WalletInterfaces.ts | 5 +- 8 files changed, 107 insertions(+), 63 deletions(-) create mode 100644 tools/walletextension/frontend/src/types/interfaces/GatewayInterfaces.ts diff --git a/tools/walletextension/frontend/src/api/gateway.ts b/tools/walletextension/frontend/src/api/gateway.ts index 47b087d214..35a0610329 100644 --- a/tools/walletextension/frontend/src/api/gateway.ts +++ b/tools/walletextension/frontend/src/api/gateway.ts @@ -3,14 +3,39 @@ import { httpRequest } from "."; import { pathToUrl } from "../routes/router"; import { getNetworkName } from "../lib/utils"; import { - metamaskPersonalSign, tenChainIDHex, tenscanLink, nativeCurrency, - typedData, + tenChainIDDecimal, } from "../lib/constants"; +import { AuthenticationResponse } from "@/types/interfaces/GatewayInterfaces"; + +const typedData = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + ], + Authentication: [{ name: "Encryption Token", type: "address" }], + }, + primaryType: "Authentication", + domain: { + name: "Ten", + version: "1.0", + chainId: tenChainIDDecimal, + }, + message: { + "Encryption Token": "0x", + }, +}; + +const { ethereum } = typeof window !== "undefined" ? window : ({} as any); export async function switchToTenNetwork() { + if (!ethereum) { + return; + } try { await (window as any).ethereum.request({ method: requestMethods.switchNetwork, @@ -33,34 +58,37 @@ export async function fetchVersion(): Promise { export async function accountIsAuthenticated( userID: string, account: string -): Promise { - return await httpRequest({ +): Promise { + return await httpRequest({ method: "get", url: pathToUrl(apiRoutes.queryAccountUserID), searchParams: { - u: userID, + token: userID, a: account, }, }); } const getSignature = async (account: string, data: any) => { - const { ethereum } = window as any; - const signature = await ethereum.request({ - method: metamaskPersonalSign, + if (!ethereum) { + return; + } + return await ethereum.request({ + method: requestMethods.signTypedData, params: [account, JSON.stringify(data)], }); - - return signature; }; export async function authenticateAccountWithTenGatewayEIP712( userID: string, account: string ): Promise { + if (!userID) { + return; + } try { const isAuthenticated = await accountIsAuthenticated(userID, account); - if (isAuthenticated) { + if (isAuthenticated.status) { return "Account is already authenticated"; } const data = { @@ -89,15 +117,14 @@ const authenticateUser = async ( address: string; } ) => { - const authenticateResp = await httpRequest({ + return await httpRequest({ method: "post", url: pathToUrl(apiRoutes.authenticate), data: authenticateFields, searchParams: { - u: userID, + token: userID, }, }); - return authenticateResp; }; export async function revokeAccountsApi(userID: string): Promise { @@ -105,7 +132,7 @@ export async function revokeAccountsApi(userID: string): Promise { method: "get", url: pathToUrl(apiRoutes.revoke), searchParams: { - u: userID, + token: userID, }, }); } @@ -118,8 +145,11 @@ export async function joinTestnet(): Promise { } export async function addNetworkToMetaMask(rpcUrls: string[]) { + if (!ethereum) { + return; + } try { - await (window as any).ethereum.request({ + await ethereum.request({ method: requestMethods.addNetwork, params: [ { diff --git a/tools/walletextension/frontend/src/components/modules/home/connected.tsx b/tools/walletextension/frontend/src/components/modules/home/connected.tsx index 65514a6e82..08c6636267 100644 --- a/tools/walletextension/frontend/src/components/modules/home/connected.tsx +++ b/tools/walletextension/frontend/src/components/modules/home/connected.tsx @@ -41,7 +41,7 @@ const Connected = () => { Account - Connected + Authenticated diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index 3e44036477..f85edca95b 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -21,6 +21,8 @@ import { METAMASK_CONNECTION_TIMEOUT } from "../../lib/constants"; import { requestMethods } from "@/routes"; import { ToastType } from "@/types/interfaces"; +const { ethereum } = typeof window !== "undefined" ? window : ({} as any); + const WalletConnectionContext = createContext(null); @@ -45,8 +47,10 @@ export const WalletConnectionProvider = ({ const [provider, setProvider] = useState({} as ethers.providers.Web3Provider); useEffect(() => { - const { ethereum } = window as any; const handleAccountsChanged = async () => { + if (!ethereum) { + return; + } if (userID && isValidUserIDFormat(userID)) { await displayCorrectScreenBasedOnMetamaskAndUserID(userID, provider); } @@ -68,7 +72,6 @@ export const WalletConnectionProvider = ({ }, []); const checkIfMetamaskIsLoaded = async () => { - const { ethereum } = window as any; if (ethereum) { await handleEthereum(); } else { @@ -81,7 +84,6 @@ export const WalletConnectionProvider = ({ }; const handleEthereum = async () => { - const { ethereum } = window as any; if (ethereum && ethereum.isMetaMask) { const provider = new ethers.providers.Web3Provider(ethereum); setProvider(provider); @@ -133,7 +135,7 @@ export const WalletConnectionProvider = ({ setVersion(await fetchVersion()); if (await isTenChain()) { if (userID) { - await getAccounts(provider); + await getAccounts(provider, userID); } else { setWalletConnected(false); } @@ -145,14 +147,30 @@ export const WalletConnectionProvider = ({ }; const connectAccount = async (account: string) => { - if (loading) { - return; - } - if (!userID) { return; } await authenticateAccountWithTenGatewayEIP712(userID, account); + const { status } = await accountIsAuthenticated(userID, account); + if (status) { + showToast(ToastType.SUCCESS, "Account authenticated!"); + setAccounts((accounts) => { + if (!accounts) { + return null; + } + return accounts.map((acc) => { + if (acc.name === account) { + return { + ...acc, + connected: status, + }; + } + return acc; + }); + }); + } else { + showToast(ToastType.DESTRUCTIVE, "Account authentication failed."); + } }; const revokeAccounts = async () => { @@ -167,7 +185,10 @@ export const WalletConnectionProvider = ({ } }; - const getAccounts = async (provider: ethers.providers.Web3Provider) => { + const getAccounts = async ( + provider: ethers.providers.Web3Provider, + id: string + ) => { try { if (!provider) { showToast( @@ -191,21 +212,22 @@ export const WalletConnectionProvider = ({ return; } - for (const account of accounts) { - await authenticateAccountWithTenGatewayEIP712(userID, account); - } + let updatedAccounts: Account[] = []; - const updatedAccounts = await Promise.all( - accounts.map(async (account: string) => ({ + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + authenticateAccountWithTenGatewayEIP712(id, account); + const { status } = await accountIsAuthenticated(id, account); + updatedAccounts.push({ name: account, - connected: await accountIsAuthenticated(userID, account), - })) - ); + connected: status, + }); + } setAccounts(updatedAccounts); setWalletConnected(true); - showToast(ToastType.SUCCESS, "Accounts authenticated successfully!"); + showToast(ToastType.SUCCESS, "Accounts authenticated"); } catch (error) { console.error(error); showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index 8da38091e6..3f9e6fcb70 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -1,4 +1,4 @@ -export const tenGatewayAddress = "https://testnet.obscu.ro"; +export const tenGatewayAddress = "https://uat-testnet.obscu.ro"; export const tenscanLink = "https://testnet.tenscan.com"; export const socialLinks = { @@ -35,8 +35,6 @@ export const userIDHexLength = 40; export const tenGatewayVersion = "v1"; export const tenChainIDDecimal = 443; -export const metamaskPersonalSign = "personal_sign"; - export const tenChainIDHex = "0x" + tenChainIDDecimal.toString(16); // Convert to hexadecimal and prefix with '0x' export const METAMASK_CONNECTION_TIMEOUT = 3000; @@ -45,23 +43,3 @@ export const nativeCurrency = { symbol: "ETH", decimals: 18, }; - -export const typedData = { - types: { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - ], - Authentication: [{ name: "Encryption Token", type: "address" }], - }, - primaryType: "Authentication", - domain: { - name: "Ten", - version: "1.0", - chainId: tenChainIDDecimal, - }, - message: { - "Encryption Token": "0x", - }, -}; diff --git a/tools/walletextension/frontend/src/routes/index.ts b/tools/walletextension/frontend/src/routes/index.ts index 0fa649e4d5..1b27864791 100644 --- a/tools/walletextension/frontend/src/routes/index.ts +++ b/tools/walletextension/frontend/src/routes/index.ts @@ -16,4 +16,5 @@ export const requestMethods = { switchNetwork: "wallet_switchEthereumChain", addNetwork: "wallet_addEthereumChain", getStorageAt: "eth_getStorageAt", + signTypedData: "eth_signTypedData_v4", }; diff --git a/tools/walletextension/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts index 44d16889e8..a28428d4e4 100644 --- a/tools/walletextension/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/frontend/src/services/useGatewayService.ts @@ -10,13 +10,18 @@ import { SWITCHED_CODE, tenGatewayVersion } from "../lib/constants"; import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "../lib/utils"; import { requestMethods } from "../routes"; +const { ethereum } = typeof window !== "undefined" ? window : ({} as any); + const useGatewayService = () => { const { provider } = useWalletConnection(); const { userID, setUserID, getAccounts } = useWalletConnection(); const connectAccounts = async () => { + if (!ethereum) { + return null; + } try { - await (window as any).ethereum.request({ + await ethereum.request({ method: requestMethods.connectAccounts, }); showToast(ToastType.SUCCESS, "Connected to Ten Network"); @@ -57,7 +62,9 @@ const useGatewayService = () => { ) { const user = await joinTestnet(); setUserID(user); - const rpcUrls = [`${getRPCFromUrl()}/${tenGatewayVersion}/?u=${user}`]; + const rpcUrls = [ + `${getRPCFromUrl()}/${tenGatewayVersion}/?token=${user}`, + ]; await addNetworkToMetaMask(rpcUrls); } @@ -66,10 +73,10 @@ const useGatewayService = () => { await connectAccounts(); } - if (!provider) { + if (!provider || !userID) { return; } - await getAccounts(provider); + await getAccounts(provider, userID); } catch (error: any) { showToast(ToastType.DESTRUCTIVE, `${error.message}`); } diff --git a/tools/walletextension/frontend/src/types/interfaces/GatewayInterfaces.ts b/tools/walletextension/frontend/src/types/interfaces/GatewayInterfaces.ts new file mode 100644 index 0000000000..8e1465a485 --- /dev/null +++ b/tools/walletextension/frontend/src/types/interfaces/GatewayInterfaces.ts @@ -0,0 +1,3 @@ +export type AuthenticationResponse = { + status: boolean; +}; diff --git a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts index bcd9450990..b01bc25f08 100644 --- a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts @@ -9,7 +9,10 @@ export interface WalletConnectionContextType { provider: ethers.providers.Web3Provider | null; version: string | null; revokeAccounts: () => void; - getAccounts: (provider: ethers.providers.Web3Provider) => Promise; + getAccounts: ( + provider: ethers.providers.Web3Provider, + userID: string + ) => Promise; loading: boolean; } From e8af463e59e4545bfef53d1f20eafa00ef3c13d4 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 30 Nov 2023 02:27:09 +0400 Subject: [PATCH 19/36] add docs and error pages --- .../frontend/public/docs/privacy.json | 105 ++++++++++++++++ .../frontend/public/docs/terms.json | 114 ++++++++++++++++++ .../src/components/layouts/footer.tsx | 16 ++- .../frontend/src/components/ui/spinner.tsx | 11 ++ .../frontend/src/lib/siteMetadata.ts | 16 +++ .../frontend/src/pages/404.tsx | 38 ++++++ .../frontend/src/pages/500.tsx | 32 +++++ .../frontend/src/pages/_error.tsx | 90 ++++++++++++++ .../frontend/src/pages/docs/[id].tsx | 105 ++++++++++++++++ .../frontend/src/styles/globals.css | 38 ++++++ 10 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 tools/walletextension/frontend/public/docs/privacy.json create mode 100644 tools/walletextension/frontend/public/docs/terms.json create mode 100644 tools/walletextension/frontend/src/components/ui/spinner.tsx create mode 100644 tools/walletextension/frontend/src/lib/siteMetadata.ts create mode 100644 tools/walletextension/frontend/src/pages/404.tsx create mode 100644 tools/walletextension/frontend/src/pages/500.tsx create mode 100644 tools/walletextension/frontend/src/pages/_error.tsx create mode 100644 tools/walletextension/frontend/src/pages/docs/[id].tsx diff --git a/tools/walletextension/frontend/public/docs/privacy.json b/tools/walletextension/frontend/public/docs/privacy.json new file mode 100644 index 0000000000..03dfcf9203 --- /dev/null +++ b/tools/walletextension/frontend/public/docs/privacy.json @@ -0,0 +1,105 @@ +{ + "title": "Privacy Policy", + "subHeading": "Last Updated: November 28, 2023", + "content": [ + { + "heading": "1. LEGAL INFORMATION", + "content": [ + "This Privacy Policy informs how Obscuro Limited (hereinafter also – ”Controller”, “Owner”,” we”, “us” or “our”) processes information and personal data on the website https://www.obscu.ro/ as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (hereinafter – Platform).", + "We strive to protect all personal information that we receive or generate. This Privacy Policy (“Privacy Policy” or “Policy”) explains our data protection practices for our visitors. This Privacy Policy also explains the nature of the personal information we collect, the means by which we collect it, the purposes for which we collect it, and how we use, process, protect, and share it.", + "Please read this entire Privacy Policy before submitting information to this Platform. By accessing or using this Platform for any purpose and by submitting any of your personal information to us, you are consenting to the terms and conditions of this Policy and to our Terms of Service posted on this Platform. If you disagree with any part of this Privacy Policy or the Terms of Service, please do not use this Platform or any of our other services and do not share any personal information with us.", + "Data Controller: Obscuro Limited, company incorporated and registered in England and Wales under company number 13873741 with a registered office at Ground Floor, Cromwell House, 15 Andover Road, Winchester, SO23 7BT, UK.", + "Contact information: e-mail address: terms@obscu.ro" + ] + }, + { + "heading": "2. DEFINITIONS AND LEGAL REFERENCES", + "content": [ + "Personal Data (or Data) - Any information that directly, indirectly, or in connection with other information — including a personal identification number — allows for the identification or identifiability of a natural person.", + "Usage Data - Information collected automatically through this Platform (or third-party services employed in this Platform), which can include: the IP addresses or domain names of the computers utilised by the Users who use this Platform, the URI addresses (Uniform Resource Identifier), the time of the request, the method utilized to submit the request to the server, the size of the file received in response, the numerical code indicating the status of the server's answer (successful outcome, error, etc.), the country of origin, the features of the browser and the operating system utilized by the User, the various time details per visit (e.g., the time spent on each page within the Platform) and the details about the path followed within the Platform with special reference to the sequence of pages visited, and other parameters about the device operating system and/or the User's IT environment.", + "User - The individual using this Platform who, unless otherwise specified, coincides with the Data Subject.", + "Data Subject - The natural person to whom the Personal Data refers.", + "Data Processor (or Data Supervisor) - The natural or legal person, public authority, agency or other body which processes Personal Data on behalf of the Controller, as described in this privacy policy.", + "Data Controller (or Owner) - The natural or legal person, public authority, agency or other body which, alone or jointly with others, determines the purposes and means of the processing of Personal Data, including the security measures concerning the operation and use of this Platform. The Data Controller, unless otherwise specified, is the Owner of this Platform.", + "This Platform - The means by which the Personal Data of the User is collected and processed.", + "Service - The service provided by this Platform as described in the relative terms and on this Platform." + ] + }, + { + "heading": "3. COLLECTING OF DATA", + "content": [ + "This section explains generally the sources from which, and the means by which, we collect and process personal information.", + "
  • Communicating with us. If you contact us in relation to any of the Services (via email, telephone, post or otherwise), We may collect and retain your contact details and your communication for the purpose of handling your query and keeping records of communications.
  • Submit personal information. When you submit personal information to us voluntarily, including when you communicate with us, pay for our services, or use any of our Services.
  • Visit our Platform. When you visit our Platform, we may collect location and other information from the internet browser you are using;
  • Technology technical information. When your communications with us provide us with certain technical information, such as internet protocol (IP) address, browser type, time zone setting and location, device operating system, and other technologies you may use to access our Platform or otherwise communicate with us.
  • Social media platforms (such as Discord, Twitter (X)). We receive information when you use your social media account while interacting with us on our Discord or Twitter and use our Services.
" + ] + }, + { + "heading": "4. COLLECTED DATA", + "content": [ + "We collect the following data:", + "
  • Identify and contact information, including your name, postal address, email address and phone number, and any other information you provide to prove you are eligible to use our Services;
  • Communications information, including records of your communications with us and our customer service team (such as records of emails, chat and in-app communications and voice recordings)
  • Social network information, including your interactions with us (such as messages) or our content (such as your likes) on social media (such as Discord or Twitter);
  • Technical information, including device identifiers, IP address, and where you have enabled location services, your GPS location, as well as information on how you use our Platform. This information is mainly collected through cookies and includes: your visits to our platform, the links you click on, through and from our site (including date and time), the services you view or search for, page response times, download errors, length of visits to certain pages, page interaction information (such as scrolling and clicks), and methods used to browse away from the page; technical information, including the internet protocol (IP) address used to connect your computer to the internet, your log-in information, the browser type and version, the time-zone setting, the operating system and platform, the type of device you use, a unique device identifier (for example, your device identifier, number, or the mobile phone number used by the device), mobile network information, your mobile operating system, the type of mobile browser you use; information stored on your device, including if you give us access to contact information from your address book, photos, videos or other digital content, check-ins. We collect this information from you, when you provide it to us directly or when we collect it through our Platform using technical means, such as cookies.
", + "We collect the following categories of Personal data for the following activities:", + "
ActivityCategories of Personal data
Visiting the Platform
  • Browsing Data
  • Technical Information
Contacting Obscuro Limited support teams
  • Identification Data
  • Contact Data
  • Content of your request
Allowing the visitors and Users to exercise their data protection rights
  • Identification Data
  • Contact Data
  • Content of the request
  • Data necessary to reply to the request addressed to Obscuro Limited
Complying with legal requests or manage litigation
  • Data necessary to prove Obscuro Limited compliance to its obligations and/or manage legal proceedings
Provide our Services on Platform
  • Identification Data
  • Contact Data
Sending you marketing communications or newsletter
  • Contact Data
" + ] + }, + { + "heading": "5. PURPOSE OF DATA COLLECTION", + "content": [ + "We use the personal information that we collect or receive from our Users for the purposes described in this Policy and for other business purposes allowed by law, including the development, delivery, and performance of our services, sharing with our affiliates for related business purposes, and as follows:", + "
  • To provide and maintain our Service, including to monitor the usage of our Service.
  • For the performance of a contract: the development, compliance and undertaking of the contract for the Services or of any other contract with us through the Service.
  • To contact you: To contact you by email, telephone calls, SMS, or other equivalent forms of electronic communication.
  • To manage your requests: To attend and manage your requests to us.
  • To respond to your requests and questions, resolve disputes, investigate and address your concerns, and monitor and improve our responses;
  • For testing, research, analysis, and a product and service development, including to improve our Platform and services;
  • To respond to law enforcement requests and as required by applicable laws, court orders, or governmental regulations;
  • For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, marketing and your experience.
", + "We process personal data on the following legal basis for the following purposes:", + "
PurposeLegal Basis
If you communicate with us (for example, if you email or call us), we will use your information for dealing with your queries, training and customer service purposes.Contractual necessity: where you provide information that is necessary for us to fulfil our contract with you and provide our Services. Our legitimate interests are to handle your queries and provide you with the requested information, ensure high customer service quality and to train staff in responding to such requests.
To send you marketing communications about our Services (and the products or services of third parties that we make available through our Services), or to send you our newsletter when you sign up to receive this and to monitor whether you open our emails and/or click on URLs in our emails.We rely on your consent, where this is required by law. Otherwise, we rely on our legitimate interest to keep you informed of products and services on our platforms, when we are allowed by law to do so.
To carry out market research and create marketing profiles about our users and understand their preferences in relation to our Services (including the products and services available through these).Our legitimate interest to carry out marketing activities.
To display our advertisements to you on other platforms, such as social media platforms or to display on our website and app advertisements which we think you might like.Your consent, where we obtain this information by using cookies or where otherwise required by law. Otherwise, we rely on our and third parties’ legitimate interests to carry out marketing activities and inform you of products and services we think you might like.
To compile statistics and analysis about the use of our Services and use such statistics to enable us to provide a better service, features, and functionality to you and other users. Your consent, where we obtain this information by using cookies.Our legitimate interests (where consent is not required by law) so as to ensure the smooth and effective functioning of our Services, to make sound business decisions about our products and services and to design, inform and deploy our business strategies.
To respond to legitimate requests for the disclosure of information, made by public authorities, law enforcement or government bodies or under a court order.Legal requirement, to the extent we are obliged under law to process such requests. Our legitimate interests to assist legitimate investigations carried out by official authorities.
To respond to complaints, to protect our legal rights and to establish, exercise or defend legal claims relating to our platforms and/or our products and services.Our legitimate interests to protect our legal rights.
For tax, accounting, record keeping and audit purposes.Legal requirements, to the extent the law requires that we use your information (for example, to comply with our tax obligations).
", + "Wherever we rely on consent, you will always be able to withdraw that consent at any time, although we may have other legal grounds for processing your data for other purposes, such as those set out above. You have an absolute right to opt-out of direct marketing, or profiling we carry out for direct marketing purposes, at any time. You can do this by clicking on the unsubscribe link in the relevant marketing communication or emailing us at - terms@obscu.ro.", + "We do not use your personal information to take automated decisions about you which have a legal or similarly significant effect on you." + ] + }, + { + "heading": "6. METHODS OF DATA PROCESSING", + "content": [ + "Obscuro Limited takes appropriate security measures to prevent unauthorized access, disclosure, modification, or unauthorized destruction of the Data.", + "The Data processing is carried out using computers and/or IT enabled tools, following organizational procedures and modes strictly related to the purposes indicated. In addition to Obscuro Limited, in some cases, the Data may be accessible to certain types of persons in charge, involved with the operation of this Platform (administration, sales, marketing, legal, system administration) or external parties (such as third-party technical service providers, mail carriers, hosting providers, IT companies, communications agencies) appointed, if necessary, as Data Processors by Obscuro Limited. The updated list of these parties may be requested from the Owner at any time." + ] + }, + { + "heading": "7. TRANSFERS AND SHARING OF DATA", + "content": [ + "Depending on the User's location, data transfers may involve transferring the User's Data to a country other than their own. If any such transfer takes place, Users can find out more by checking the relevant sections of this document or inquire with Obscuro Limited using the information provided in the contact section.", + "Where we need to transfer your personal data outside the European Economic Area (“EEA”), the United Kingdom or Switzerland, and where this is to a stakeholder or vendor in a country that is not subject to an adequacy decision by the EU Commission, data is adequately protected by EU Commission approved standard contractual clauses or a vendor’s Processor Binding Corporate Rules.", + "Obscuro Limited may store, process, and/or transfer personal data to countries outside of the European Economic Area (EEA) (including countries where the European Commission has not made a decision of an adequate level of protection of personal data), but in these cases Obscuro Limited will ask for specific consent regarding these data transfers.", + "In this case, Obscuro Limited processes your data in United Kingdom.", + "We may share your personal data with following recipients:", + "
  • Internal recipients – your Personal data will only be disclosed to authorised employees that require access to fulfil their obligations (e.g. support teams, developers, etc.). Our employees are specifically trained and made aware of the sensitivity of your Personal data and the requirements necessary to ensure the protection of your right to privacy.
  • Judicial, administrative and other public authorities – Obscuro Limited may have to share or disclose some of your Personal data if it is required to do so by the law, by a request meaning from a competent authority., to comply with a court order, to obtain legal remedies or defend Obscuro Limited’s rights, to contribute with investigations (e.g. fraud, identity theft, etc.).
  • Service providers - We share personal data with third-party service providers, who will process it on our behalf for the purposes identified above. These parties will use your information on our instructions, only in order to provide us with their services. In particular, we use third-party providers of website hosting, maintenance, IT services, mail carriers, customer support, communications and marketing services, identity checking.
  • Social media networks and advertisers - subject to your marketing preferences, we share information with social media networks, such as Twitter, Discord, and advertisers to present our ads to you on other platforms.
  • Business advisers - We share information with our legal advisers, accountants, business consultants, insurers and other business advisers, to the extent it is necessary for them to provide us with their services.
" + ] + }, + { + "heading": "8. RETENTION TIME", + "content": [ + "We will store your personal information for as long as it is required for us to fulfil the purposes for which we have collected it, as described in this Policy, and for such further period that is necessary to comply with our legal and regulatory obligations, to exercise our legal rights and to protect our business from legal claims. Therefore:", + "
  • Personal Data collected for purposes related to the performance of a contract between the Owner and the User shall be retained until such contract has been fully performed.
  • Personal Data collected for the purposes of the Owner’s legitimate interests shall be retained as long as needed to fulfil such purposes. Users may find specific information regarding the legitimate interests pursued by the Owner within the relevant sections of this document or by contacting the Owner.
  • Obscuro Limited may be allowed to retain Personal Data for a longer period whenever the User has given consent to such processing, as long as such consent is not withdrawn. Furthermore, the Owner may be obliged to retain Personal Data for a longer period whenever required to do so for the performance of a legal obligation or upon order of an authority.
  • Once the retention period expires, Personal Data shall be deleted. Therefore, the right of access, the right to erasure, the right to rectification and the right to data portability cannot be enforced after expiration of the retention period.
", + "Obscuro Limited may be allowed to retain Personal Data for a longer period whenever the User has given consent to such processing, as long as such consent is not withdrawn. Furthermore, the Owner may be obliged to retain Personal Data for a longer period whenever required to do so for the performance of a legal obligation or upon order of an authority.", + "Once the retention period expires, Personal Data shall be deleted. Therefore, the right of access, the right to erasure, the right to rectification and the right to data portability cannot be enforced after expiration of the retention period." + ] + }, + { + "heading": "9. RIGHTS UNDER GDPR", + "content": [ + "Users may exercise certain rights regarding their Data processed by Obscuro Limited. In particular, Users have the right to do the following:", + "
  • Withdraw consent at any time. Users have the right to withdraw consent where they have previously given their consent to the processing of their Personal Data.
  • Object to processing of Data. Users have the right to object to the processing of their Data if the processing is carried out on a legal basis other than consent. Further details are provided in the dedicated section below.
  • Access Data. Users have the right to learn if Data is being processed by the Owner, obtain disclosure regarding certain aspects of the processing and obtain a copy of the Data undergoing processing.
  • Verify and seek rectification. Users have the right to verify the accuracy of their Data and ask for it to be updated or corrected.
  • Restrict the processing of their Data. Users have the right, under certain circumstances, to restrict the processing of their Data. In this case, the Owner will not process their Data for any purpose other than storing it.
  • Have their Personal Data deleted or otherwise removed. Users have the right, under certain circumstances, to obtain the erasure of their Data from the Owner.
  • Receive their Data and have it transferred to another controller. Users have the right to receive their Data in a structured, commonly used and machine readable format and, if technically feasible, to have it transmitted to another controller without any hindrance. This provision is applicable provided that the Data is processed by automated means and that the processing is based on the User's consent, on a contract which the User is part of or on pre-contractual obligations thereof.
  • Lodge a complaint. If you have unresolved concerns, you have the right to complain to the data protection authority, which in the UK is the Information Commissioner’s Office.
", + "Any requests to exercise User rights can be directed to Obscuro Limited through the contact details provided in this document. These requests can be exercised free of charge and will be addressed by the Owner as early as possible and always within one month.", + "If user has any issues regarding data processing done by Obscuro Limited, user can send request to the Information Commissioner’s Office." + ] + }, + { + "heading": "ADDITIONAL INFORMATION ABOUT DATA COLLECTION AND PROCESSING", + "content": [ + "
  • Legal action. The User's Personal Data may be used for legal purposes by Obscuro Limited in Court or in the stages leading to possible legal action arising from improper use of this Platform or the related Services.
  • System logs and maintenance.For operation and maintenance purposes, this Platform and any third-party services may collect files that record interaction with this Platform (System logs) use other Personal Data (such as the IP Address) for this purpose.
  • Information not contained in this policy.More details concerning the collection or processing of Personal Data may be requested from Obscuro Limited at any time. Please see the contact information at the beginning of this document.
  • Visiting Third-Party Platforms.Our Platform may contain links or references to third party websites. These websites are outside of our control, and the privacy policies of these sites may differ from our own. Please be aware that we have no control over these third-party websites and our Privacy Policy does not apply to such websites. We encourage you to check the terms of use and privacy policies of such sites before disclosing any personal information via such sites. The privacy policy of the third party site will govern how information collected from you is used by the owner of the website. You can always know what Platform you are on by checking the Uniform Resource Locator (URL) in the location bar within your browser.
" + ] + }, + { + "heading": "CHANGES TO THIS PRIVACY POLICY", + "content": [ + "Obscuro Limited reserves the right to make changes to this privacy policy at any time by notifying its Users on this page and possibly within this Platform and/or - as far as technically and legally feasible - sending a notice to Users via any contact information available to Obscuro Limited. It is strongly recommended to check this page often, referring to the date of the last modification listed at the bottom.", + "Should the changes affect processing activities performed on the basis of the User’s consent, Obscuro Limited shall collect new consent from the User, where required." + ] + } + ] +} diff --git a/tools/walletextension/frontend/public/docs/terms.json b/tools/walletextension/frontend/public/docs/terms.json new file mode 100644 index 0000000000..ff0769351e --- /dev/null +++ b/tools/walletextension/frontend/public/docs/terms.json @@ -0,0 +1,114 @@ +{ + "title": "Terms of Service", + "subHeading": "Last Updated, November 22, 2023", + "content": [ + { + "heading": "1. GENERAL INFORMATION", + "content": [ + "Obscuro Limited (“our,” “us,” “we” or “Obscuro Labs“), a private limited company incorporated and registered in England and Wales under company number 13873741 with a registered office at Ground Floor, Cromwell House, 15 Andover Road, Winchester, SO23 7BT, UK, welcomes you. These Terms of Service (\"Terms\") govern your access to and use of the Website, Ten Testnet services and software (collectively, the \"Services\"). By using our Services, you agree to these Terms.", + "These Terms set forth the legal terms and conditions governing your Testnet use. These Terms, along with any of our other policies and rules referenced herein, comprise the entire understanding between you and Obscuro Labs regarding the Services and supersede all other agreements, understandings, or representations with respect to such subject matter, either written or oral. If you do not agree to these Terms, do not use our Services.", + "By using our Services, you confirm that you accept these Terms and that you agree to be bound by and comply with these Terms, and you represent and warrant that you have the willingness, right, authority, and capacity to enter into these Terms (on behalf of yourself or the entity that you represent). If you do not agree to all of these Terms in their entirety, you may not use the Services or any other related site in any manner.", + "You must be 18 years of age or older to use the Services. By using the Services, you confirm, represent and warrant that you meet these requirements." + ] + }, + { + "heading": "2. DISCLAIMER", + "content": [ + "You expressly acknowledge that your use of the Services is provided to you on an “as is” and “as available” basis without any warranty under these Terms and to the extent allowed by applicable law, all express or implied conditions, representations and warranties including without limitation, any implied warranties or conditions of merchantability, fitness for a particular purpose, satisfactory quality, or arising from a course of dealing usage or trade practice, or warranty of non-infringement are disclaimed.", + "In instances where we discuss future ideas or potential developments, we are expressing our vision and aspirations. However, this should not be interpreted as a binding commitment or a guarantee that these concepts will materialise, that we will implement any of them, or that they will prove effective.", + "You must obtain professional or specialist advice before taking, or refraining from, any action on the basis of the content on our Services or Terms." + ] + }, + { + "heading": "3. PRIVACY", + "content": [ + "Your privacy is important to us. In using the Services, you may be required to provide certain personal information. We will use this information solely for the purpose of facilitating your use of the Services, including but not limited to identity verification, compliance with legal obligations, and improving the Services. We will not share your personal data with third parties, except as required by law or for the purpose of providing the Services. Your data will be stored securely and will be deleted when no longer necessary for the provision of the Services or as required by applicable law." + ] + }, + { + "heading": "4. INTELLECTUAL PROPERTY RIGHTS; FEEDBACK", + "content": [ + "All the copyright and other intellectual property rights in our Services are reserved.", + "Neither these Terms nor your access to the Services transfers to you or any third party any rights, title, or interest to such intellectual property rights. You agree not to take any action(s) inconsistent with such ownership interests. We reserve all rights in connection with the Services and its content, including, without limitation, the exclusive right to create derivative works.", + "Obscuro Labs welcomes feedback, comments and suggestions for improvements to the Testnet and related technologies of the Services (“Feedback”). You grant to Obscuro Labs a non-exclusive, transferable, worldwide, perpetual, irrevocable, fully-paid, royalty-free license, with the right to sublicense, under any and all intellectual property rights that you own or control to use, copy, modify, create derivative works based upon and otherwise exploit the Feedback for any purpose, in any form, format, media or media channels now known or later developed or discovered." + ] + }, + { + "heading": "5. THIRD-PARTY MATERIALS", + "content": [ + "A third-party product site link is not an indication that we endorse such third-party products or are in a manner affiliated with them. Any time we link to, quote, or otherwise reference any third-party products or reproduce or incorporate their information, content, or material, it is solely for informational purposes. These third-party products are owned, operated, and controlled by third parties. We strongly advise you to read the terms and conditions and privacy policies of any third-party products you visit and/or use. When you use or rely on any third-party products, you do so at your own risk. You understand that you are solely responsible for any fees or costs associated with using third-party products and that, unless stated herein, the Terms do not otherwise apply to your dealings or relationships with any third parties or third-party products, and we assume no obligations or liability and make no representations or warranties regarding such third-party products." + ] + }, + { + "heading": "6. RESTRICTIONS ON THE USE OF THE SERVICES", + "content": [ + "You may only use the Services for lawful purposes and in compliance with these Terms. You agree not to use the Website and Services to do any of the following:", + "
  • violate any applicable law or regulation, including, without limitation, any applicable sanctions laws, export control laws, securities laws, anti-money laundering laws, privacy laws;
  • use any device, software or routine that interferes with or compromises the integrity, security, or proper functioning of our Services;
  • damage or disrupt any parts of the Services, the server(s) on which the Services run or any server, computer, or database connected to the Services;
  • further or promote any criminal activity or enterprise or provide instructional information about illegal activities; or
  • encourage or enable any other individual to do any of the foregoing.
" + ] + }, + { + "heading": "7. INDEMNITY", + "content": [ + "You acknowledge and agree to, at your own expense, defend, indemnify and hold harmless Obscuro Labs and its affiliates and their respective equity holders, directors, officers, employees, managers, partners, service providers, licensors, licensees, representatives, agents and successors (“Indemnified Parties“) from any claim, actions, liabilities, losses, damages, suits and expenses, costs of whatever kind, including attorneys’ and expert fees and legal expenses, that we incur in connection with or arising out of your use of the Services, including but not limited to:
  • any breach or violation of these Terms by you;
  • material entered into or transmitted through the Services by you or a third party acting at your request;
  • your use of any third-party products;
  • a claim that any use of the Services by you infringes any intellectual property right of any third party, or any right of privacy or publicity, is libellous or defamatory, or otherwise results in injury or damage to any third party; or
  • any deletions, additions, insertions, or alterations to, or any unauthorised use of, the Services by you (collectively, “Claims“). You agree to promptly notify us of any third-party Claims and cooperate with the Indemnified Parties in defending such Claims. We reserve the right to assume the exclusive defence and control of any Claim and matter otherwise subject to indemnification by you at your expense, and you shall not in any event settle or otherwise dispose of any matter without our prior written consent.
" + ] + }, + { + "heading": "8. LIMITATIONS OF LIABILITY", + "content": [ + "TO THE FULLEST EXTENT ALLOWED BY APPLICABLE LAW, UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, TORT, CONTRACT, STRICT LIABILITY, OR OTHERWISE) SHALL THE INDEMNIFIED PARTIES OR ANY OF THEM BE LIABLE TO YOU OR TO ANY OTHER PERSON FOR:
  • ANY INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING DAMAGES FOR LOST PROFITS, BUSINESS, OR REVENUE, BUSINESS INTERRUPTION, LOSS OF DATA, LOSS OF BUSINESS OPPORTUNITY, GOODWILL OR REPUTATION, WORK STOPPAGE, ACCURACY OF RESULTS, OR COMPUTER FAILURE OR MALFUNCTION;
  • ANY SUBSTITUTE GOODS, SERVICES OR TECHNOLOGY;
  • ANY AMOUNT, IN THE AGGREGATE, IN EXCESS OF ONE-HUNDRED POUNDS (£100); OR
  • ANY MATTER BEYOND THE REASONABLE CONTROL OF THE INDEMNIFIED PARTIES OR ANY OF THEM.
", + "SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL OR CERTAIN OTHER DAMAGES, SO THE FOREGOING LIMITATIONS AND EXCLUSIONS MAY NOT APPLY TO YOU.", + "Nothing in these Terms is intended to exclude or limit our liability for death or personal injury caused by our negligence, or for fraud or fraudulent misrepresentation, or to affect your statutory rights." + ] + }, + { + "heading": "9. TERMINATION", + "content": [ + "We may terminate or suspend your access to our Services at any time for any reason." + ] + }, + { + "heading": "10. FORCE MAJEURE", + "content": [ + "You acknowledge and agree that we will not be liable for failures or delays in providing Services or other non-performance caused by events including but not limited to strikes, insurrection, riot, civil unrest, war, fires, utility, or power failures, equipment failures, changes in law, cyberattacks, denial of service attacks, non-performance of our vendors or suppliers, acts of god, pandemic or epidemic events, or other causes over which we have no reasonable control. We will make reasonable efforts to limit the effect of any of those events and start or restart the Website and Services as soon as those events have been fixed." + ] + }, + { + "heading": "11. COMPLAINTS, DISPUTES AND GOVERNING LAW", + "content": [ + "Any dispute, claim or request for relief arising out of or in connection with these Terms and/or the Services, including any question regarding its existence, validity or termination, shall be referred to and finally resolved by arbitration under the LCIA Rules, which Rules are deemed to be incorporated by reference into this clause. The number of arbitrators shall be one. The seat, or legal place, of arbitration, shall be London, United Kingdom. The language to be used in the arbitration shall be English. The governing law shall be the substantive law of England.", + "To the extent there is a dispute regarding any Claim (including questions about the scope, applicability, interpretation, validity, and enforceability of this arbitration agreement), you and Obscuro Labs agree that this threshold dispute shall be delegated to the arbitrator (not a court) and that the arbitrator shall have initial authority to resolve such threshold disputes, except as expressly provided below. The arbitration will be final and binding, and the judgement on the award rendered by the arbitrator(s) may be entered in any court having jurisdiction thereof. Please be advised that all arbitration proceedings are confidential unless the parties agree otherwise.", + "If you are a UK or EU resident and use the Services mainly for non-business purposes, you can bring proceedings in any competent courts in the country of your main residence that has jurisdiction over your claim or dispute.", + "Governing law. These Terms and any issue, claim or dispute between you and us that arises out of them (or otherwise relating to the Services) will be governed by the laws of England. However, any additional, mandatory consumer rights and protections that you are entitled to under the laws of the country in which you reside will also apply." + ] + }, + { + "heading": "12. CHANGES TO THESE TERMS", + "content": [ + "We reserve the right, in our sole discretion, to modify, suspend or discontinue the Terms and /or Services (or any features or parts thereof) from time to time to reflect changes to our Services, our users’ needs, our business priorities or changes in laws applicable to us, without liability to you. We will give you reasonable notice when we change our Terms. Your continued use of the Services following the posting of revised Terms means that you accept and agree to the changes. Please check these Terms regularly to ensure you agree with the most recent version." + ] + }, + { + "heading": "13. UPDATES; MONITORING", + "content": [ + "We may make any improvement, modifications or updates to the Services, including but not limited to changes and updates to the underlying software, infrastructure, security protocols, technical configurations or service features (the “Updates”) from time to time. Your continued access and use of our Services are subject to such Updates and you shall accept any patches, system upgrades, bug fixes, feature modifications, or other maintenance work that arise out of such Updates. We are not liable for any failure by you to accept and use such Updates in the manner specified or required by us. Although the Company is not obligated to monitor access to or participation in the Services, it has the right to do so for the purpose of operating the Services, to ensure compliance with the Terms and to comply with applicable law or other legal requirements." + ] + }, + { + "heading": "14. GENERAL TERMS", + "content": [ + "Information Only. You agree that the Services (or any information provided by or obtained from the Services) are for informational purposes only, are not intended to be relied upon for professional advice of any sort, and is not a substitute for information from experts or professionals in the applicable area. You should not take, or refrain from taking, any action or decision based on any information contained in the Services. If, and before you make any financial, legal, or other decisions involving the Services, you should seek independent professional advice from an individual who is licensed and qualified in the area for which such advice would be appropriate.", + "Open-Source: Ten is being developed on an open-source basis under the GNU Affero General Public License, Version 3.0, the terms of which are found here: https://www.gnu.org/licenses/agpl-3.0.html (the “AGPLv3 Licence“). By accessing, using, copying (or similar) the Services, you agree that the Services shall be governed by the AGPLv3 Licence (and the Terms herein). The Services are licensed under the AGPLv3 License and you undertake that you will not use the Services except in compliance with the AGPLv3 License and these Terms. You agree that any software developed using the Services, shall be made available for the public to use on an open source basis, on the AGPLv3 License terms.", + "Compliance with Law. You represent and warrant that you will comply with all laws that apply to you, your use of the Services, and your actions and omissions that relate to the Services. If your use of the Services is prohibited by applicable laws, then you aren’t authorised to use the Services. We will not be responsible for your using the Services (and developing and/or deploying any software) in a way that is a violation of any law. Without limiting the foregoing, you represent and warrant that you are not, and for the duration of the time you use the Services (to develop any software or similar) will not be (a) the subject of economic or trade sanctions administered or enforced by any governmental authority or otherwise designated on any list of prohibited or restricted parties (including but not limited to the United Nations Security Council, the European Union, His Majesty’s Treasury, and U.S. Department of Treasury), or (b) a citizen, resident, or organised in a jurisdiction or territory that is the subject of comprehensive country-wide, territory-wide, or regional economic sanctions by the United Nations, European Union, any EU country, UK Treasury, or the United States, including without limitation Cuba, the Crimea, Donetsk, and Luhansk regions of Ukraine, Iran, North Korea, Russia, Syria, Yemen and any other regions and/or countries sanctioned from time to time. If at any point the above is no longer true, you must immediately cease using the Services.", + "Assumption of Risk. By using the Services, you (a) represent that you are sophisticated enough to understand the various inherent risks of using cryptographic and public blockchain-based systems, including but not limited to the Services and digital assets, and (b) acknowledge and accept all such risks, and agree that we make no representations or warranties (expressly or implicitly) regarding, and that you will not hold us liable for those risks, including but not limited to the risks described below, any or all of which could lead to losses and damages, including the total and irrevocable loss of your assets. These risks include, but are not limited to:", + "
  • Wallet security and safekeeping. You are solely responsible for the safeguarding and security of your Web3 wallets. If you lose your wallet seed phrase, private keys, or password, you may be forever unable to access your digital assets. Any unauthorised access to your wallet by third parties could result in the loss or theft of your digital assets. We have no involvement in, or responsibility for, storing, retaining, securing or recovering your Web3 wallet seed phrases, private keys, or passwords, or for any unauthorised access to your Web3 wallet.
  • Blockchain technology. Public blockchains, and the technology underlying and interacting with cryptographic and public blockchain-based systems, are experimental, inherently risky, and subject to change. Among other risks, bugs, malfunctions, cyberattacks, or changes to a particular public blockchain (e.g., via forks) could disrupt these technologies irreparably. There is no guarantee that any of these technologies will not become unavailable, degraded, or subject to hardware or software errors, operational or technical difficulties, denial-of-service attacks, other cyberattacks, or other problems requiring maintenance, interruptions, delays, or errors.
  • Network cost and performance. The cost, speed, and availability of transacting on public blockchain systems are subject to significant variability. There is no guarantee that any transfer will be confirmed or transferred successfully.
  • Blockchain transactions and smart contract execution. Public blockchain-based transactions (including but not limited to transactions automatically executed by smart contracts) are generally considered irreversible when confirmed. Any transaction that will interact with smart contracts or be recorded on a public blockchain must be recorded with extreme caution.
  • Digital assets. The markets for digital assets are nascent and highly volatile due to various risk factors including (but not limited to) adoption, speculation, technology, security, and regulation. Digital assets and their underlying blockchain networks are complex emerging technologies that may be subject to delays, halts or go offline as a result of errors, forks, attacks or other unforeseeable reasons. Anyone can create a digital asset, including fake versions of existing digital assets and digital assets that falsely claim to represent projects. So-called stablecoins may not be as stable as they purport to be, may not be fully or adequately collateralised, and may be subject to panics and runs. You are solely responsible for understanding the risks specific to each digital asset that is relevant to you.
  • Bridging. In addition to being an especially novel and untested implementation of blockchain technology in general, cross-blockchain bridging technology has historically been, and may in the future be, the subject of numerous cyberattacks and exploits, including without limitation, hacks that exploit a vulnerability in the associated software, hardware, systems or other equipment or social engineering to gain control of any bridge components, wallets, smart contracts or other related systems.
  • Control of the Services. The Services may be subject to periodic upgrades, which may introduce other risks, bugs, malfunctions, cyberattack vectors, or other changes to the Services that could disrupt the operation of the Services, the functionality of bridging, your ability to access bridged digital assets, or otherwise cause you damage or loss.
  • Third Party Risks. Third-party products carry their own individual, oftentimes highly significant risks. When you use the Services to interact with any third-party products, you are subject to all of those risks.
  • Legislative and regulatory risks. Digital assets, blockchain technology, and any related software and services are subject to legal and regulatory uncertainty in many jurisdictions. Legislative and regulatory changes or actions may adversely affect the usage, transferability, transactability and accessibility of digital assets, bridging or the Services.
", + "Release of claims. You expressly agree that you assume all risks and liabilities in connection with your use of the Services (including, without limitation, the development and/or deployment of any software under or in connection with the Services), as such are detailed above and as permitted under applicable laws. You further expressly waive and release Obscuro Labs, as well as its affiliates and service providers, and each of their respective past, present and future officers, directors, members, employees, consultants, representatives and agents, and each of their respective successors and assigns from any and all liability, claims, causes of action, or damages arising from or in any way relating to your use of the Services." + ] + }, + { + "heading": "15. CONTACT INFORMATION", + "content": [ + "If you have any questions about the Website or these Terms, please contact us at siteMetadata.email" + ] + } + ] +} diff --git a/tools/walletextension/frontend/src/components/layouts/footer.tsx b/tools/walletextension/frontend/src/components/layouts/footer.tsx index 3aaefbffff..8cc2f0de75 100644 --- a/tools/walletextension/frontend/src/components/layouts/footer.tsx +++ b/tools/walletextension/frontend/src/components/layouts/footer.tsx @@ -1,3 +1,4 @@ +import Link from "next/link"; import { socialLinks } from "../../lib/constants"; import { GitHubLogoIcon, @@ -12,36 +13,39 @@ export default function Footer() {
diff --git a/tools/walletextension/frontend/src/components/ui/spinner.tsx b/tools/walletextension/frontend/src/components/ui/spinner.tsx new file mode 100644 index 0000000000..2948ef451b --- /dev/null +++ b/tools/walletextension/frontend/src/components/ui/spinner.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +const Spinner = () => { + return ( +
+
+
+ ); +}; + +export default Spinner; diff --git a/tools/walletextension/frontend/src/lib/siteMetadata.ts b/tools/walletextension/frontend/src/lib/siteMetadata.ts new file mode 100644 index 0000000000..764fb271c4 --- /dev/null +++ b/tools/walletextension/frontend/src/lib/siteMetadata.ts @@ -0,0 +1,16 @@ +import { socialLinks } from "./constants"; + +export const siteMetadata = { + companyName: "Ten Scan", + metaTitle: + "Ten, a decentralized Layer 2 Rollup protocol designed to hyper-scale and encrypt the Ethereum blockchain.", + description: + "Ten, a decentralized Ethereum Layer 2 Rollup protocol designed to hyper-scale, encrypt and prevent negative MEV on the Ethereum blockchain using Secure Enclaves and ZKPs.", + siteUrl: "https://obscu.ro", + siteLogo: ``, + siteLogoSquare: ``, + email: "team@obscu.ro", + twitter: socialLinks.twitter, + twitterHandle: "@obscuronet", + github: socialLinks.github, +}; diff --git a/tools/walletextension/frontend/src/pages/404.tsx b/tools/walletextension/frontend/src/pages/404.tsx new file mode 100644 index 0000000000..13124aadfd --- /dev/null +++ b/tools/walletextension/frontend/src/pages/404.tsx @@ -0,0 +1,38 @@ +import { ErrorType } from "@/types/interfaces"; +import Error from "./_error"; + +export function Custom404Error({ + customPageTitle, + showRedirectText, + redirectText, + isFullWidth, + message, + showMessage = true, + redirectLink, + children, +}: ErrorType) { + return ( + + {children} + + ); +} + +export default Custom404Error; diff --git a/tools/walletextension/frontend/src/pages/500.tsx b/tools/walletextension/frontend/src/pages/500.tsx new file mode 100644 index 0000000000..8115382c5e --- /dev/null +++ b/tools/walletextension/frontend/src/pages/500.tsx @@ -0,0 +1,32 @@ +import { ErrorType } from "@/types/interfaces"; +import Error from "./_error"; + +function Custom500Error({ + customPageTitle, + message, + showRedirectText, + redirectText, + err, + redirectLink, + children, +}: ErrorType) { + return ( + + {children} + + ); +} + +export default Custom500Error; diff --git a/tools/walletextension/frontend/src/pages/_error.tsx b/tools/walletextension/frontend/src/pages/_error.tsx new file mode 100644 index 0000000000..9504bf8b03 --- /dev/null +++ b/tools/walletextension/frontend/src/pages/_error.tsx @@ -0,0 +1,90 @@ +import React from "react"; +import NextErrorComponent from "next/error"; +import Link from "next/link"; +import { ErrorType } from "@/types/interfaces"; + +function ErrorMessage({ + statusText, + message, + showMessage, + showStatusText, +}: any) { + return ( +
+ {showStatusText &&

{statusText}

} + {message && showMessage && ( +

{message}

+ )} +
+ ); +} + +export function CustomError({ + showRedirectText = true, + heading = "Oops! Something went wrong.", + statusText = "500", + message = "We're experiencing technical difficulties. Please try again later.", + redirectText = "Home Page", + isFullWidth, + err, + showMessage = true, + showStatusText, + statusCode, + isModal, + redirectLink = "/", + children, + ...props +}: ErrorType) { + return ( +
+
+
+

{heading}

+
+ +
+ {showRedirectText && ( +
+ Go to{" "} + + {redirectText} + {" "} + {/*
+ Looks like you're on the wrong side of town, buddy. + Let's get you back on the right side. +
*/} +
+ )} + {children} +
+
+
+ ); +} + +CustomError.getInitialProps = async ({ res, err }: any) => { + const statusCode = res ? res.statusCode : err?.statusCode || 404; + const errorInitialProps = await NextErrorComponent.getInitialProps({ + res, + err, + } as any); + errorInitialProps.statusCode = statusCode; + + return statusCode < 500 + ? errorInitialProps + : { ...errorInitialProps, statusCode }; +}; + +export default CustomError; diff --git a/tools/walletextension/frontend/src/pages/docs/[id].tsx b/tools/walletextension/frontend/src/pages/docs/[id].tsx new file mode 100644 index 0000000000..a4c8408259 --- /dev/null +++ b/tools/walletextension/frontend/src/pages/docs/[id].tsx @@ -0,0 +1,105 @@ +import Layout from "@/components/layouts/default-layout"; +import Spinner from "@/components/ui/spinner"; +import { useToast } from "@/components/ui/use-toast"; +import { siteMetadata } from "@/lib/siteMetadata"; +import { useRouter } from "next/router"; +import React from "react"; +import Custom404Error from "../404"; + +type Document = { + title: string; + subHeading: string; + content: { + heading: string; + content: string[]; + }[]; +}; + +const Document = () => { + const { toast } = useToast(); + const { query } = useRouter(); + const { id } = query; + + const [document, setDocument] = React.useState({} as Document); + const [loading, setLoading] = React.useState(false); + + const getDocument = async () => { + setLoading(true); + try { + const response = await fetch(`/docs/${id}.json`); + const data = await response.json(); + const processedData = { + title: data.title, + subHeading: data.subHeading, + content: data.content.map((item: any) => { + return { + heading: item.heading, + content: item.content.map((paragraph: any) => { + return paragraph.replace( + /siteMetadata.email/g, + siteMetadata.email + ); + }), + }; + }), + }; + setDocument(processedData); + } catch (error) { + toast({ + variant: "destructive", + description: "Error fetching document", + }); + } finally { + setLoading(false); + } + }; + + React.useEffect(() => { + if (id) { + getDocument(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id]); + + return ( + +
+ {!loading ? ( + !document.title ? ( + + ) : ( + <> +
+

+ {document.title} +

+

+ {document.subHeading} +

+
+
+ {document.content && + document.content.map((section, index) => ( +
+

{section.heading}

+ {section.content && + section.content.map((paragraph, index) => ( +
+ ))} +
+ ))} +
+ + ) + ) : ( + + )} +
+
+ ); +}; + +export default Document; diff --git a/tools/walletextension/frontend/src/styles/globals.css b/tools/walletextension/frontend/src/styles/globals.css index 7557b32883..78e53ef9e8 100644 --- a/tools/walletextension/frontend/src/styles/globals.css +++ b/tools/walletextension/frontend/src/styles/globals.css @@ -127,6 +127,44 @@ @apply bg-background text-foreground; font-feature-settings: "rlig" 1, "calt" 1; } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-weight: 500; + } + + /* styles for docs */ + .prose ul { + list-style: disc; + margin-left: 2rem; + } + + .prose ul li { + margin-bottom: 0.5rem; + } + + .prose ul li:last-child { + margin-bottom: 0; + } + + .prose ul ul { + list-style: circle; + margin-left: 2rem; + } + + .prose a { + @apply text-accent-foreground; + } + + .prose table td, + .prose table th { + border: 1px solid hsl(var(--border)); + padding: 0.5rem; + } } .theme-slate { From d2e5e03946f5db940293dabadd66019ba6b20bb5 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 30 Nov 2023 02:35:33 +0400 Subject: [PATCH 20/36] feat: add SEO tags and GTAG --- .../frontend/src/components/head-seo.tsx | 50 +++++++++++++++++ .../frontend/src/lib/constants.ts | 3 + .../frontend/src/lib/siteMetadata.ts | 4 +- .../frontend/src/pages/_app.tsx | 56 +++++++++++++++---- .../frontend/src/types/interfaces/index.ts | 10 ++++ 5 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 tools/walletextension/frontend/src/components/head-seo.tsx diff --git a/tools/walletextension/frontend/src/components/head-seo.tsx b/tools/walletextension/frontend/src/components/head-seo.tsx new file mode 100644 index 0000000000..beffaf3496 --- /dev/null +++ b/tools/walletextension/frontend/src/components/head-seo.tsx @@ -0,0 +1,50 @@ +import Head from "next/head"; +import { siteMetadata } from "../lib/siteMetadata"; +import { SeoProps } from "@/types/interfaces"; + +const HeadSeo = ({ + title, + description, + canonicalUrl, + ogTwitterImage, + ogImageUrl, + ogType, + children, +}: SeoProps) => { + return ( + + {/* Basic metadata */} + {title} + + + + {/* twitter metadata */} + + + + + + {/* canonical link */} + + {/* open graph metadata */} + + + + + + + + {children} + + ); +}; + +export default HeadSeo; diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index 3f9e6fcb70..4f7eacdc0c 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -5,8 +5,11 @@ export const socialLinks = { github: "https://github.com/obscuronet", discord: "https://discord.gg/2JQ2Z3r", twitter: "https://twitter.com/obscuronet", + twitterHandle: "@obscuronet", }; +export const GOOGLE_ANALYTICS_ID = "G-RPFRRG1S7F"; + export const testnetUrls = { sepolia: { name: "Ten Dev-Testnet", diff --git a/tools/walletextension/frontend/src/lib/siteMetadata.ts b/tools/walletextension/frontend/src/lib/siteMetadata.ts index 764fb271c4..30f2f4d872 100644 --- a/tools/walletextension/frontend/src/lib/siteMetadata.ts +++ b/tools/walletextension/frontend/src/lib/siteMetadata.ts @@ -1,7 +1,7 @@ import { socialLinks } from "./constants"; export const siteMetadata = { - companyName: "Ten Scan", + companyName: "Ten Gateway", metaTitle: "Ten, a decentralized Layer 2 Rollup protocol designed to hyper-scale and encrypt the Ethereum blockchain.", description: @@ -11,6 +11,6 @@ export const siteMetadata = { siteLogoSquare: ``, email: "team@obscu.ro", twitter: socialLinks.twitter, - twitterHandle: "@obscuronet", + twitterHandle: socialLinks.twitterHandle, github: socialLinks.github, }; diff --git a/tools/walletextension/frontend/src/pages/_app.tsx b/tools/walletextension/frontend/src/pages/_app.tsx index e7dae5ec08..d8bbbf3804 100644 --- a/tools/walletextension/frontend/src/pages/_app.tsx +++ b/tools/walletextension/frontend/src/pages/_app.tsx @@ -4,20 +4,52 @@ import type { AppProps } from "next/app"; import { Toaster } from "../components/ui/toaster"; import { WalletConnectionProvider } from "../components/providers/wallet-provider"; import { NetworkStatus } from "../components/modules/common/network-status"; +import HeadSeo from "@/components/head-seo"; +import Script from "next/script"; +import { GOOGLE_ANALYTICS_ID } from "@/lib/constants"; +import { siteMetadata } from "@/lib/siteMetadata"; export default function App({ Component, pageProps }: AppProps) { return ( - - - - - - - + <> + + + + + + + + + + + + + + + ); } diff --git a/tools/walletextension/frontend/src/types/interfaces/index.ts b/tools/walletextension/frontend/src/types/interfaces/index.ts index fefe210d9b..a6fe4d8388 100644 --- a/tools/walletextension/frontend/src/types/interfaces/index.ts +++ b/tools/walletextension/frontend/src/types/interfaces/index.ts @@ -1,5 +1,15 @@ import React from "react"; +export interface SeoProps { + title: string; + description: string; + canonicalUrl: string; + ogTwitterImage: string; + ogImageUrl: string; + ogType: string; + children: React.ReactNode; +} + export interface ErrorType { statusCode?: number; showRedirectText?: boolean; From 8ace8ccfb7273f5c858fb1c6383d00391a86346d Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Fri, 1 Dec 2023 08:40:01 +0400 Subject: [PATCH 21/36] refactor: separate eth and gateway requests --- .../frontend/src/api/ethRequests.ts | 152 +++++++++++++++ .../frontend/src/api/gateway.ts | 114 +----------- .../src/components/modules/home/connected.tsx | 2 +- .../components/modules/home/disconnected.tsx | 1 - .../components/providers/wallet-provider.tsx | 173 ++++-------------- .../frontend/src/services/ethService.ts | 129 +++++++++++++ .../src/services/useGatewayService.ts | 48 ++--- .../src/types/interfaces/WalletInterfaces.ts | 9 +- 8 files changed, 339 insertions(+), 289 deletions(-) create mode 100644 tools/walletextension/frontend/src/api/ethRequests.ts create mode 100644 tools/walletextension/frontend/src/services/ethService.ts diff --git a/tools/walletextension/frontend/src/api/ethRequests.ts b/tools/walletextension/frontend/src/api/ethRequests.ts new file mode 100644 index 0000000000..b4ba8ed933 --- /dev/null +++ b/tools/walletextension/frontend/src/api/ethRequests.ts @@ -0,0 +1,152 @@ +import { + nativeCurrency, + tenChainIDDecimal, + tenChainIDHex, + tenscanLink, +} from "@/lib/constants"; +import { getNetworkName, getRandomIntAsString, isTenChain } from "@/lib/utils"; +import { requestMethods } from "@/routes"; +import { ethers } from "ethers"; +import { accountIsAuthenticated, authenticateUser } from "./gateway"; + +const { ethereum } = typeof window !== "undefined" ? window : ({} as any); + +const typedData = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + ], + Authentication: [{ name: "Encryption Token", type: "address" }], + }, + primaryType: "Authentication", + domain: { + name: "Ten", + version: "1.0", + chainId: tenChainIDDecimal, + }, + message: { + "Encryption Token": "0x", + }, +}; + +export const switchToTenNetwork = async () => { + if (!ethereum) { + throw new Error("No ethereum object found"); + } + try { + await ethereum.request({ + method: requestMethods.switchNetwork, + params: [{ chainId: tenChainIDHex }], + }); + + return 0; + } catch (error: any) { + return error.code; + } +}; + +export const connectAccounts = async () => { + if (!ethereum) { + throw new Error("No ethereum object found"); + } + try { + return await ethereum.request({ + method: requestMethods.connectAccounts, + }); + } catch (error) { + console.error(error); + throw error; + } +}; + +export const getSignature = async (account: string, data: any) => { + if (!ethereum) { + throw new Error("No ethereum object found"); + } + return await ethereum.request({ + method: requestMethods.signTypedData, + params: [account, JSON.stringify(data)], + }); +}; + +export const getUserID = async (provider: ethers.providers.Web3Provider) => { + if (!provider) { + return null; + } + + try { + if (await isTenChain()) { + const id = await provider.send(requestMethods.getStorageAt, [ + "getUserID", + getRandomIntAsString(0, 1000), + null, + ]); + return id; + } else { + return null; + } + } catch (e: any) { + console.error(e); + throw e; + } +}; + +export async function addNetworkToMetaMask(rpcUrls: string[]) { + if (!ethereum) { + throw new Error("No ethereum object found"); + } + try { + await ethereum.request({ + method: requestMethods.addNetwork, + params: [ + { + chainId: tenChainIDHex, + chainName: getNetworkName(), + nativeCurrency, + rpcUrls, + blockExplorerUrls: [tenscanLink], + }, + ], + }); + return true; + } catch (error) { + console.error(error); + throw error; + } +} + +export async function authenticateAccountWithTenGatewayEIP712( + userID: string, + account: string +): Promise { + if (!userID) { + return; + } + try { + const isAuthenticated = await accountIsAuthenticated(userID, account); + if (isAuthenticated.status) { + return { + status: true, + message: "Account already authenticated", + }; + } + const data = { + ...typedData, + message: { + ...typedData.message, + "Encryption Token": "0x" + userID, + }, + }; + const signature = await getSignature(account, data); + + const auth = await authenticateUser(userID, { + signature, + address: account, + }); + return auth; + } catch (error) { + throw error; + } +} diff --git a/tools/walletextension/frontend/src/api/gateway.ts b/tools/walletextension/frontend/src/api/gateway.ts index 35a0610329..244f246ffb 100644 --- a/tools/walletextension/frontend/src/api/gateway.ts +++ b/tools/walletextension/frontend/src/api/gateway.ts @@ -1,53 +1,8 @@ -import { apiRoutes, requestMethods } from "../routes"; +import { apiRoutes } from "../routes"; import { httpRequest } from "."; import { pathToUrl } from "../routes/router"; -import { getNetworkName } from "../lib/utils"; -import { - tenChainIDHex, - tenscanLink, - nativeCurrency, - tenChainIDDecimal, -} from "../lib/constants"; import { AuthenticationResponse } from "@/types/interfaces/GatewayInterfaces"; -const typedData = { - types: { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - ], - Authentication: [{ name: "Encryption Token", type: "address" }], - }, - primaryType: "Authentication", - domain: { - name: "Ten", - version: "1.0", - chainId: tenChainIDDecimal, - }, - message: { - "Encryption Token": "0x", - }, -}; - -const { ethereum } = typeof window !== "undefined" ? window : ({} as any); - -export async function switchToTenNetwork() { - if (!ethereum) { - return; - } - try { - await (window as any).ethereum.request({ - method: requestMethods.switchNetwork, - params: [{ chainId: tenChainIDHex }], - }); - - return 0; - } catch (switchError: any) { - return switchError.code; - } -} - export async function fetchVersion(): Promise { return await httpRequest({ method: "get", @@ -69,48 +24,7 @@ export async function accountIsAuthenticated( }); } -const getSignature = async (account: string, data: any) => { - if (!ethereum) { - return; - } - return await ethereum.request({ - method: requestMethods.signTypedData, - params: [account, JSON.stringify(data)], - }); -}; - -export async function authenticateAccountWithTenGatewayEIP712( - userID: string, - account: string -): Promise { - if (!userID) { - return; - } - try { - const isAuthenticated = await accountIsAuthenticated(userID, account); - if (isAuthenticated.status) { - return "Account is already authenticated"; - } - const data = { - ...typedData, - message: { - ...typedData.message, - "Encryption Token": "0x" + userID, - }, - }; - const signature = await getSignature(account, data); - - const auth = await authenticateUser(userID, { - signature, - address: account, - }); - return auth; - } catch (error) { - throw error; - } -} - -const authenticateUser = async ( +export const authenticateUser = async ( userID: string, authenticateFields: { signature: string; @@ -143,27 +57,3 @@ export async function joinTestnet(): Promise { url: pathToUrl(apiRoutes.join), }); } - -export async function addNetworkToMetaMask(rpcUrls: string[]) { - if (!ethereum) { - return; - } - try { - await ethereum.request({ - method: requestMethods.addNetwork, - params: [ - { - chainId: tenChainIDHex, - chainName: getNetworkName(), - nativeCurrency, - rpcUrls, - blockExplorerUrls: [tenscanLink], - }, - ], - }); - return true; - } catch (error) { - console.error(error); - return error; - } -} diff --git a/tools/walletextension/frontend/src/components/modules/home/connected.tsx b/tools/walletextension/frontend/src/components/modules/home/connected.tsx index 08c6636267..39bb850b64 100644 --- a/tools/walletextension/frontend/src/components/modules/home/connected.tsx +++ b/tools/walletextension/frontend/src/components/modules/home/connected.tsx @@ -75,7 +75,7 @@ const Connected = () => { size={"sm"} onClick={() => connectAccount(account.name)} > - {account.connected ? "Disconnect" : "Connect"} + Connect )} diff --git a/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx b/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx index 6cb9758fd4..2ec744042d 100644 --- a/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx +++ b/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx @@ -16,7 +16,6 @@ import { } from "../../ui/dialog"; import Copy from "../common/copy"; import { testnetUrls, tenChainIDDecimal } from "../../../lib/constants"; -import Link from "next/link"; const CONNECTION_STEPS = [ "Hit Connect to Ten and start your journey", diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index f85edca95b..27110522ac 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -1,25 +1,23 @@ import { createContext, useContext, useEffect, useState } from "react"; -import { ethers } from "ethers"; import { WalletConnectionContextType, WalletConnectionProviderProps, Account, } from "../../types/interfaces/WalletInterfaces"; import { showToast } from "../ui/use-toast"; -import { - getRandomIntAsString, - isTenChain, - isValidUserIDFormat, -} from "../../lib/utils"; +import { isValidUserIDFormat } from "../../lib/utils"; import { accountIsAuthenticated, - authenticateAccountWithTenGatewayEIP712, fetchVersion, revokeAccountsApi, } from "../../api/gateway"; -import { METAMASK_CONNECTION_TIMEOUT } from "../../lib/constants"; -import { requestMethods } from "@/routes"; import { ToastType } from "@/types/interfaces"; +import { + authenticateAccountWithTenGatewayEIP712, + getUserID, +} from "@/api/ethRequests"; +import { ethers } from "ethers"; +import ethService from "@/services/ethService"; const { ethereum } = typeof window !== "undefined" ? window : ({} as any); @@ -28,6 +26,7 @@ const WalletConnectionContext = export const useWalletConnection = (): WalletConnectionContextType => { const context = useContext(WalletConnectionContext); + if (!context) { throw new Error( "useWalletConnection must be used within a WalletConnectionProvider" @@ -52,7 +51,9 @@ export const WalletConnectionProvider = ({ return; } if (userID && isValidUserIDFormat(userID)) { - await displayCorrectScreenBasedOnMetamaskAndUserID(userID, provider); + const status = + await ethService.getCorrectScreenBasedOnMetamaskAndUserID(userID); + setWalletConnected(status); } }; ethereum.on("accountsChanged", handleAccountsChanged); @@ -63,89 +64,27 @@ export const WalletConnectionProvider = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const initialize = async () => { + const providerInstance = new ethers.providers.Web3Provider(ethereum); + setProvider(providerInstance); + await ethService.checkIfMetamaskIsLoaded(providerInstance); + const id = await getUserID(providerInstance); + setUserID(id); + const status = await ethService.getCorrectScreenBasedOnMetamaskAndUserID( + id + ); + setWalletConnected(status); + const accounts = await ethService.getAccounts(providerInstance); + setAccounts(accounts || null); + setVersion(await fetchVersion()); + setLoading(false); + }; + useEffect(() => { - const initialize = async () => { - await checkIfMetamaskIsLoaded(); - }; initialize(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const checkIfMetamaskIsLoaded = async () => { - if (ethereum) { - await handleEthereum(); - } else { - showToast(ToastType.INFO, "Connecting to MetaMask..."); - window.addEventListener("ethereum#initialized", handleEthereum, { - once: true, - }); - setTimeout(handleEthereum, METAMASK_CONNECTION_TIMEOUT); - } - }; - - const handleEthereum = async () => { - if (ethereum && ethereum.isMetaMask) { - const provider = new ethers.providers.Web3Provider(ethereum); - setProvider(provider); - const fetchedUserID = await getUserID(provider); - await displayCorrectScreenBasedOnMetamaskAndUserID( - fetchedUserID, - provider - ); - } else { - showToast( - ToastType.WARNING, - "Please install MetaMask to use Ten Gateway." - ); - } - }; - - const getUserID = async (provider: ethers.providers.Web3Provider) => { - if (!provider) { - return null; - } - - try { - if (await isTenChain()) { - const id = await provider.send(requestMethods.getStorageAt, [ - "getUserID", - getRandomIntAsString(0, 1000), - null, - ]); - setUserID(id); - return id; - } else { - return null; - } - } catch (e: any) { - showToast( - ToastType.DESTRUCTIVE, - `${e.message} ${e.data?.message}` || - "Error: Could not fetch your user ID. Please try again later." - ); - console.error(e); - return null; - } - }; - - const displayCorrectScreenBasedOnMetamaskAndUserID = async ( - userID: string, - provider: ethers.providers.Web3Provider - ) => { - setVersion(await fetchVersion()); - if (await isTenChain()) { - if (userID) { - await getAccounts(provider, userID); - } else { - setWalletConnected(false); - } - } else { - setWalletConnected(false); - } - - setLoading(false); - }; - const connectAccount = async (account: string) => { if (!userID) { return; @@ -182,69 +121,27 @@ export const WalletConnectionProvider = ({ showToast(ToastType.DESTRUCTIVE, "Accounts revoked!"); setAccounts(null); setWalletConnected(false); + setUserID(""); } }; - const getAccounts = async ( - provider: ethers.providers.Web3Provider, - id: string - ) => { - try { - if (!provider) { - showToast( - ToastType.DESTRUCTIVE, - "No provider found. Please try again later." - ); - return; - } - - showToast(ToastType.INFO, "Getting accounts..."); - - if (!(await isTenChain())) { - showToast(ToastType.DESTRUCTIVE, "Please connect to the Ten chain."); - return; - } - - const accounts = await provider.listAccounts(); - - if (accounts.length === 0) { - showToast(ToastType.DESTRUCTIVE, "No MetaMask accounts found."); - return; - } - - let updatedAccounts: Account[] = []; - - for (let i = 0; i < accounts.length; i++) { - const account = accounts[i]; - authenticateAccountWithTenGatewayEIP712(id, account); - const { status } = await accountIsAuthenticated(id, account); - updatedAccounts.push({ - name: account, - connected: status, - }); - } - - setAccounts(updatedAccounts); - setWalletConnected(true); - - showToast(ToastType.SUCCESS, "Accounts authenticated"); - } catch (error) { - console.error(error); - showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); - } + const fetchUserAccounts = async () => { + const accounts = await ethService.getAccounts(provider); + setAccounts(accounts || null); + setWalletConnected(true); }; const walletConnectionContextValue: WalletConnectionContextType = { walletConnected, accounts, userID, - setUserID, connectAccount, - provider, version, revokeAccounts, - getAccounts, loading, + provider, + fetchUserAccounts, + setLoading, }; return ( diff --git a/tools/walletextension/frontend/src/services/ethService.ts b/tools/walletextension/frontend/src/services/ethService.ts new file mode 100644 index 0000000000..a27865e1e2 --- /dev/null +++ b/tools/walletextension/frontend/src/services/ethService.ts @@ -0,0 +1,129 @@ +import { + authenticateAccountWithTenGatewayEIP712, + getUserID, +} from "@/api/ethRequests"; +import { accountIsAuthenticated } from "@/api/gateway"; +import { showToast } from "@/components/ui/use-toast"; +import { METAMASK_CONNECTION_TIMEOUT } from "@/lib/constants"; +import { isTenChain, isValidUserIDFormat } from "@/lib/utils"; +import { ToastType } from "@/types/interfaces"; +import { Account } from "@/types/interfaces/WalletInterfaces"; +import { ethers } from "ethers"; + +const { ethereum } = typeof window !== "undefined" ? window : ({} as any); + +const ethService = { + checkIfMetamaskIsLoaded: async (provider: ethers.providers.Web3Provider) => { + if (ethereum) { + await ethService.handleEthereum(provider); + } else { + showToast(ToastType.INFO, "Connecting to MetaMask..."); + + const handleEthereumOnce = () => { + ethService.handleEthereum(provider); + }; + + window.addEventListener("ethereum#initialized", handleEthereumOnce, { + once: true, + }); + + setTimeout(() => { + handleEthereumOnce(); // Call the handler function after the timeout + }, METAMASK_CONNECTION_TIMEOUT); + } + }, + + handleEthereum: async (provider: ethers.providers.Web3Provider) => { + if (ethereum && ethereum.isMetaMask) { + const fetchedUserID = await getUserID(provider); + if (fetchedUserID && isValidUserIDFormat(fetchedUserID)) { + showToast(ToastType.SUCCESS, "MetaMask connected!"); + } else { + showToast( + ToastType.WARNING, + "Please connect to the Ten chain to use Ten Gateway." + ); + } + } else { + showToast( + ToastType.WARNING, + "Please install MetaMask to use Ten Gateway." + ); + } + }, + + fetchUserID: async (provider: ethers.providers.Web3Provider) => { + try { + return await getUserID(provider); + } catch (e: any) { + showToast( + ToastType.DESTRUCTIVE, + `${e.message} ${e.data?.message}` || + "Error: Could not fetch your user ID. Please try again later." + ); + return null; + } + }, + + getCorrectScreenBasedOnMetamaskAndUserID: async (userID: string) => { + if (await isTenChain()) { + if (userID && isValidUserIDFormat(userID)) { + return true; + } else { + return false; + } + } else { + return false; + } + }, + + getAccounts: async (provider: ethers.providers.Web3Provider) => { + const id = await getUserID(provider); + if (!id || !isValidUserIDFormat(id)) { + return; + } + + try { + if (!provider) { + showToast( + ToastType.DESTRUCTIVE, + "No provider found. Please try again later." + ); + return; + } + + showToast(ToastType.INFO, "Getting accounts..."); + + if (!(await isTenChain())) { + showToast(ToastType.DESTRUCTIVE, "Please connect to the Ten chain."); + return; + } + + const accounts = await provider.listAccounts(); + + if (accounts.length === 0) { + showToast(ToastType.DESTRUCTIVE, "No MetaMask accounts found."); + return []; + } + + let updatedAccounts: Account[] = []; + + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + await authenticateAccountWithTenGatewayEIP712(id, account); + const { status } = await accountIsAuthenticated(id, account); + updatedAccounts.push({ + name: account, + connected: status, + }); + } + showToast(ToastType.SUCCESS, "Accounts fetched successfully."); + return updatedAccounts; + } catch (error) { + console.error(error); + showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); + } + }, +}; + +export default ethService; diff --git a/tools/walletextension/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts index a28428d4e4..388ba1c7b4 100644 --- a/tools/walletextension/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/frontend/src/services/useGatewayService.ts @@ -1,35 +1,18 @@ import { ToastType } from "@/types/interfaces"; -import { - addNetworkToMetaMask, - joinTestnet, - switchToTenNetwork, -} from "../api/gateway"; +import { joinTestnet } from "../api/gateway"; import { useWalletConnection } from "../components/providers/wallet-provider"; import { showToast } from "../components/ui/use-toast"; import { SWITCHED_CODE, tenGatewayVersion } from "../lib/constants"; import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "../lib/utils"; -import { requestMethods } from "../routes"; - -const { ethereum } = typeof window !== "undefined" ? window : ({} as any); +import { + addNetworkToMetaMask, + connectAccounts, + switchToTenNetwork, +} from "@/api/ethRequests"; const useGatewayService = () => { - const { provider } = useWalletConnection(); - const { userID, setUserID, getAccounts } = useWalletConnection(); - - const connectAccounts = async () => { - if (!ethereum) { - return null; - } - try { - await ethereum.request({ - method: requestMethods.connectAccounts, - }); - showToast(ToastType.SUCCESS, "Connected to Ten Network"); - } catch (error) { - showToast(ToastType.DESTRUCTIVE, "Unable to connect to Ten Network"); - return null; - } - }; + const { userID, provider, fetchUserAccounts, setLoading } = + useWalletConnection(); const isMetamaskConnected = async () => { if (!provider) { @@ -45,12 +28,15 @@ const useGatewayService = () => { }; const connectToTenTestnet = async () => { + setLoading(true); try { if (await isTenChain()) { if (!userID || !isValidUserIDFormat(userID)) { - throw new Error( + showToast( + ToastType.WARNING, "Existing Ten network detected in MetaMask. Please remove before hitting begin" ); + return; } } @@ -61,7 +47,6 @@ const useGatewayService = () => { (userID && !isValidUserIDFormat(userID)) ) { const user = await joinTestnet(); - setUserID(user); const rpcUrls = [ `${getRPCFromUrl()}/${tenGatewayVersion}/?token=${user}`, ]; @@ -71,14 +56,15 @@ const useGatewayService = () => { if (!(await isMetamaskConnected())) { showToast(ToastType.INFO, "No accounts found, connecting..."); await connectAccounts(); + showToast(ToastType.SUCCESS, "Connected to Ten Network"); } - if (!provider || !userID) { - return; - } - await getAccounts(provider, userID); + await fetchUserAccounts(); } catch (error: any) { showToast(ToastType.DESTRUCTIVE, `${error.message}`); + throw error; + } finally { + setLoading(false); } }; diff --git a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts index b01bc25f08..3ca123984a 100644 --- a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts @@ -5,15 +5,12 @@ export interface WalletConnectionContextType { walletConnected: boolean; connectAccount: (account: string) => Promise; userID: string | null; - setUserID: (userID: string) => void; - provider: ethers.providers.Web3Provider | null; version: string | null; revokeAccounts: () => void; - getAccounts: ( - provider: ethers.providers.Web3Provider, - userID: string - ) => Promise; loading: boolean; + provider: ethers.providers.Web3Provider; + fetchUserAccounts: () => Promise; + setLoading: (loading: boolean) => void; } export interface Props { From bd78077d27cda1e7597b82a45245eed974bd1953 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Tue, 5 Dec 2023 14:21:22 +0400 Subject: [PATCH 22/36] fix: change toast type --- .../walletextension/frontend/src/services/useGatewayService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/walletextension/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts index 388ba1c7b4..37d27af96c 100644 --- a/tools/walletextension/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/frontend/src/services/useGatewayService.ts @@ -33,7 +33,7 @@ const useGatewayService = () => { if (await isTenChain()) { if (!userID || !isValidUserIDFormat(userID)) { showToast( - ToastType.WARNING, + ToastType.DESTRUCTIVE, "Existing Ten network detected in MetaMask. Please remove before hitting begin" ); return; From 5c72f5ba208b1917ee442099221efaae3eb2abb9 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Tue, 5 Dec 2023 15:25:26 +0400 Subject: [PATCH 23/36] chore: cleanup functions and error msgs --- .../frontend/src/api/ethRequests.ts | 20 ++++-- .../components/providers/wallet-provider.tsx | 35 ++++------- .../frontend/src/lib/constants.ts | 2 + .../walletextension/frontend/src/lib/utils.ts | 5 +- .../frontend/src/services/ethService.ts | 61 +++++++++++-------- 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/tools/walletextension/frontend/src/api/ethRequests.ts b/tools/walletextension/frontend/src/api/ethRequests.ts index b4ba8ed933..a975518ea1 100644 --- a/tools/walletextension/frontend/src/api/ethRequests.ts +++ b/tools/walletextension/frontend/src/api/ethRequests.ts @@ -3,13 +3,19 @@ import { tenChainIDDecimal, tenChainIDHex, tenscanLink, + userStorageAddress, } from "@/lib/constants"; -import { getNetworkName, getRandomIntAsString, isTenChain } from "@/lib/utils"; +import { + getNetworkName, + getRandomIntAsString, + isTenChain, + ethereum, +} from "@/lib/utils"; import { requestMethods } from "@/routes"; import { ethers } from "ethers"; import { accountIsAuthenticated, authenticateUser } from "./gateway"; - -const { ethereum } = typeof window !== "undefined" ? window : ({} as any); +import { showToast } from "@/components/ui/use-toast"; +import { ToastType } from "@/types/interfaces"; const typedData = { types: { @@ -73,13 +79,17 @@ export const getSignature = async (account: string, data: any) => { export const getUserID = async (provider: ethers.providers.Web3Provider) => { if (!provider) { + showToast( + ToastType.DESTRUCTIVE, + "No provider found. Please try again later." + ); return null; } try { if (await isTenChain()) { const id = await provider.send(requestMethods.getStorageAt, [ - "getUserID", + userStorageAddress, getRandomIntAsString(0, 1000), null, ]); @@ -122,8 +132,10 @@ export async function authenticateAccountWithTenGatewayEIP712( account: string ): Promise { if (!userID) { + showToast(ToastType.INFO, "User ID is required to revoke accounts"); return; } + try { const isAuthenticated = await accountIsAuthenticated(userID, account); if (isAuthenticated.status) { diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index 27110522ac..3678d767c1 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -5,7 +5,7 @@ import { Account, } from "../../types/interfaces/WalletInterfaces"; import { showToast } from "../ui/use-toast"; -import { isValidUserIDFormat } from "../../lib/utils"; +import { ethereum } from "../../lib/utils"; import { accountIsAuthenticated, fetchVersion, @@ -19,8 +19,6 @@ import { import { ethers } from "ethers"; import ethService from "@/services/ethService"; -const { ethereum } = typeof window !== "undefined" ? window : ({} as any); - const WalletConnectionContext = createContext(null); @@ -46,16 +44,7 @@ export const WalletConnectionProvider = ({ const [provider, setProvider] = useState({} as ethers.providers.Web3Provider); useEffect(() => { - const handleAccountsChanged = async () => { - if (!ethereum) { - return; - } - if (userID && isValidUserIDFormat(userID)) { - const status = - await ethService.getCorrectScreenBasedOnMetamaskAndUserID(userID); - setWalletConnected(status); - } - }; + initialize(); ethereum.on("accountsChanged", handleAccountsChanged); return () => { @@ -64,29 +53,30 @@ export const WalletConnectionProvider = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const handleAccountsChanged = async () => { + if (!ethereum) { + return; + } + const status = await ethService.isUserConnectedToTenChain(userID); + setWalletConnected(status); + }; + const initialize = async () => { const providerInstance = new ethers.providers.Web3Provider(ethereum); setProvider(providerInstance); await ethService.checkIfMetamaskIsLoaded(providerInstance); const id = await getUserID(providerInstance); setUserID(id); - const status = await ethService.getCorrectScreenBasedOnMetamaskAndUserID( - id - ); - setWalletConnected(status); + handleAccountsChanged(); const accounts = await ethService.getAccounts(providerInstance); setAccounts(accounts || null); setVersion(await fetchVersion()); setLoading(false); }; - useEffect(() => { - initialize(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const connectAccount = async (account: string) => { if (!userID) { + showToast(ToastType.INFO, "User ID is required to connect an account."); return; } await authenticateAccountWithTenGatewayEIP712(userID, account); @@ -114,6 +104,7 @@ export const WalletConnectionProvider = ({ const revokeAccounts = async () => { if (!userID) { + showToast(ToastType.INFO, "User ID is required to revoke accounts"); return; } const revokeResponse = await revokeAccountsApi(userID); diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index 4f7eacdc0c..5b582a40dd 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -41,6 +41,8 @@ export const tenChainIDDecimal = 443; export const tenChainIDHex = "0x" + tenChainIDDecimal.toString(16); // Convert to hexadecimal and prefix with '0x' export const METAMASK_CONNECTION_TIMEOUT = 3000; +export const userStorageAddress = "getUserID"; + export const nativeCurrency = { name: "Sepolia Ether", symbol: "ETH", diff --git a/tools/walletextension/frontend/src/lib/utils.ts b/tools/walletextension/frontend/src/lib/utils.ts index da06818815..a13f5f2cea 100644 --- a/tools/walletextension/frontend/src/lib/utils.ts +++ b/tools/walletextension/frontend/src/lib/utils.ts @@ -55,8 +55,11 @@ export function getRPCFromUrl() { } export async function isTenChain() { - let currentChain = await (window as any).ethereum.request({ + let currentChain = await ethereum.request({ method: "eth_chainId", }); return currentChain === tenChainIDHex; } + +export const { ethereum } = + typeof window !== "undefined" ? window : ({} as any); diff --git a/tools/walletextension/frontend/src/services/ethService.ts b/tools/walletextension/frontend/src/services/ethService.ts index a27865e1e2..da1c3ce780 100644 --- a/tools/walletextension/frontend/src/services/ethService.ts +++ b/tools/walletextension/frontend/src/services/ethService.ts @@ -5,13 +5,11 @@ import { import { accountIsAuthenticated } from "@/api/gateway"; import { showToast } from "@/components/ui/use-toast"; import { METAMASK_CONNECTION_TIMEOUT } from "@/lib/constants"; -import { isTenChain, isValidUserIDFormat } from "@/lib/utils"; +import { isTenChain, isValidUserIDFormat, ethereum } from "@/lib/utils"; import { ToastType } from "@/types/interfaces"; import { Account } from "@/types/interfaces/WalletInterfaces"; import { ethers } from "ethers"; -const { ethereum } = typeof window !== "undefined" ? window : ({} as any); - const ethService = { checkIfMetamaskIsLoaded: async (provider: ethers.providers.Web3Provider) => { if (ethereum) { @@ -19,15 +17,23 @@ const ethService = { } else { showToast(ToastType.INFO, "Connecting to MetaMask..."); + let timeoutId: ReturnType; const handleEthereumOnce = () => { ethService.handleEthereum(provider); }; - window.addEventListener("ethereum#initialized", handleEthereumOnce, { - once: true, - }); + window.addEventListener( + "ethereum#initialized", + () => { + clearTimeout(timeoutId); + handleEthereumOnce(); + }, + { + once: true, + } + ); - setTimeout(() => { + timeoutId = setTimeout(() => { handleEthereumOnce(); // Call the handler function after the timeout }, METAMASK_CONNECTION_TIMEOUT); } @@ -65,7 +71,7 @@ const ethService = { } }, - getCorrectScreenBasedOnMetamaskAndUserID: async (userID: string) => { + isUserConnectedToTenChain: async (userID: string) => { if (await isTenChain()) { if (userID && isValidUserIDFormat(userID)) { return true; @@ -78,20 +84,25 @@ const ethService = { }, getAccounts: async (provider: ethers.providers.Web3Provider) => { + if (!provider) { + showToast( + ToastType.DESTRUCTIVE, + "No provider found. Please try again later." + ); + return; + } + const id = await getUserID(provider); + if (!id || !isValidUserIDFormat(id)) { + showToast( + ToastType.DESTRUCTIVE, + "No user ID found. Please try again later." + ); return; } try { - if (!provider) { - showToast( - ToastType.DESTRUCTIVE, - "No provider found. Please try again later." - ); - return; - } - showToast(ToastType.INFO, "Getting accounts..."); if (!(await isTenChain())) { @@ -108,15 +119,15 @@ const ethService = { let updatedAccounts: Account[] = []; - for (let i = 0; i < accounts.length; i++) { - const account = accounts[i]; - await authenticateAccountWithTenGatewayEIP712(id, account); - const { status } = await accountIsAuthenticated(id, account); - updatedAccounts.push({ - name: account, - connected: status, - }); - } + const authenticationPromises = accounts.map((account) => + authenticateAccountWithTenGatewayEIP712(id, account) + .then(() => accountIsAuthenticated(id, account)) + .then(({ status }) => ({ + name: account, + connected: status, + })) + ); + updatedAccounts = await Promise.all(authenticationPromises); showToast(ToastType.SUCCESS, "Accounts fetched successfully."); return updatedAccounts; } catch (error) { From f0a2ca8aa58efb1ebf72cb8e1c2b6db8f8bb1a57 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 7 Dec 2023 02:40:16 +0400 Subject: [PATCH 24/36] fix (dis)connection errors --- .../frontend/src/api/ethRequests.ts | 25 +-- .../frontend/src/api/gateway.ts | 14 +- .../modules/common/connect-wallet.tsx | 1 - .../components/modules/home/disconnected.tsx | 5 +- .../components/providers/wallet-provider.tsx | 141 ++++++++++++----- .../frontend/src/lib/constants.ts | 2 +- .../walletextension/frontend/src/lib/utils.ts | 7 +- .../frontend/src/routes/index.ts | 2 +- .../frontend/src/services/ethService.ts | 148 ++++++++++-------- .../src/services/useGatewayService.ts | 13 +- .../src/types/interfaces/WalletInterfaces.ts | 6 +- 11 files changed, 214 insertions(+), 150 deletions(-) diff --git a/tools/walletextension/frontend/src/api/ethRequests.ts b/tools/walletextension/frontend/src/api/ethRequests.ts index a975518ea1..ef2556d8e5 100644 --- a/tools/walletextension/frontend/src/api/ethRequests.ts +++ b/tools/walletextension/frontend/src/api/ethRequests.ts @@ -77,7 +77,7 @@ export const getSignature = async (account: string, data: any) => { }); }; -export const getUserID = async (provider: ethers.providers.Web3Provider) => { +export const getToken = async (provider: ethers.providers.Web3Provider) => { if (!provider) { showToast( ToastType.DESTRUCTIVE, @@ -88,12 +88,12 @@ export const getUserID = async (provider: ethers.providers.Web3Provider) => { try { if (await isTenChain()) { - const id = await provider.send(requestMethods.getStorageAt, [ + const token = await provider.send(requestMethods.getStorageAt, [ userStorageAddress, getRandomIntAsString(0, 1000), null, ]); - return id; + return token; } else { return null; } @@ -128,16 +128,18 @@ export async function addNetworkToMetaMask(rpcUrls: string[]) { } export async function authenticateAccountWithTenGatewayEIP712( - userID: string, + token: string, account: string ): Promise { - if (!userID) { - showToast(ToastType.INFO, "User ID is required to revoke accounts"); - return; + if (!token) { + return showToast( + ToastType.INFO, + "Encryption token not found. Please try again later." + ); } try { - const isAuthenticated = await accountIsAuthenticated(userID, account); + const isAuthenticated = await accountIsAuthenticated(token, account); if (isAuthenticated.status) { return { status: true, @@ -148,17 +150,18 @@ export async function authenticateAccountWithTenGatewayEIP712( ...typedData, message: { ...typedData.message, - "Encryption Token": "0x" + userID, + "Encryption Token": "0x" + token, }, }; const signature = await getSignature(account, data); - const auth = await authenticateUser(userID, { + const auth = await authenticateUser(token, { signature, address: account, }); + console.log("🚀 ~ file: ethRequests.ts:166 ~ auth:", auth); return auth; - } catch (error) { + } catch (error: any) { throw error; } } diff --git a/tools/walletextension/frontend/src/api/gateway.ts b/tools/walletextension/frontend/src/api/gateway.ts index 244f246ffb..2e057b3d9d 100644 --- a/tools/walletextension/frontend/src/api/gateway.ts +++ b/tools/walletextension/frontend/src/api/gateway.ts @@ -11,21 +11,21 @@ export async function fetchVersion(): Promise { } export async function accountIsAuthenticated( - userID: string, + token: string, account: string ): Promise { return await httpRequest({ method: "get", - url: pathToUrl(apiRoutes.queryAccountUserID), + url: pathToUrl(apiRoutes.queryAccountToken), searchParams: { - token: userID, + token, a: account, }, }); } export const authenticateUser = async ( - userID: string, + token: string, authenticateFields: { signature: string; address: string; @@ -36,17 +36,17 @@ export const authenticateUser = async ( url: pathToUrl(apiRoutes.authenticate), data: authenticateFields, searchParams: { - token: userID, + token, }, }); }; -export async function revokeAccountsApi(userID: string): Promise { +export async function revokeAccountsApi(token: string): Promise { return await httpRequest({ method: "get", url: pathToUrl(apiRoutes.revoke), searchParams: { - token: userID, + token, }, }); } diff --git a/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx b/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx index e1c661649b..4004c6e149 100644 --- a/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx +++ b/tools/walletextension/frontend/src/components/modules/common/connect-wallet.tsx @@ -22,7 +22,6 @@ const ConnectWalletButton = () => { <> Connect -  Account(s) )} diff --git a/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx b/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx index 2ec744042d..f7a1004228 100644 --- a/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx +++ b/tools/walletextension/frontend/src/components/modules/home/disconnected.tsx @@ -56,9 +56,8 @@ const Disconnected = () => {

By connecting your wallet to Ten and signing the signature request - you will get a unique user id, which is also your{" "} - viewing key. It is contained in the RPC link and unique for - each user. + you will get a unique token, which is also your viewing key + . It is contained in the RPC link and unique for each user.

diff --git a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx index 3678d767c1..c7a507c374 100644 --- a/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx +++ b/tools/walletextension/frontend/src/components/providers/wallet-provider.tsx @@ -14,7 +14,7 @@ import { import { ToastType } from "@/types/interfaces"; import { authenticateAccountWithTenGatewayEIP712, - getUserID, + getToken, } from "@/api/ethRequests"; import { ethers } from "ethers"; import ethService from "@/services/ethService"; @@ -37,7 +37,7 @@ export const WalletConnectionProvider = ({ children, }: WalletConnectionProviderProps) => { const [walletConnected, setWalletConnected] = useState(false); - const [userID, setUserID] = useState(""); + const [token, setToken] = useState(""); const [version, setVersion] = useState(null); const [loading, setLoading] = useState(true); const [accounts, setAccounts] = useState(null); @@ -57,75 +57,132 @@ export const WalletConnectionProvider = ({ if (!ethereum) { return; } - const status = await ethService.isUserConnectedToTenChain(userID); + const status = await ethService.isUserConnectedToTenChain(token); + await fetchUserAccounts(); setWalletConnected(status); }; const initialize = async () => { - const providerInstance = new ethers.providers.Web3Provider(ethereum); - setProvider(providerInstance); - await ethService.checkIfMetamaskIsLoaded(providerInstance); - const id = await getUserID(providerInstance); - setUserID(id); - handleAccountsChanged(); - const accounts = await ethService.getAccounts(providerInstance); - setAccounts(accounts || null); - setVersion(await fetchVersion()); - setLoading(false); + if (!ethereum) { + showToast( + ToastType.DESTRUCTIVE, + "Please install Metamask to connect your wallet." + ); + return; + } + try { + const providerInstance = new ethers.providers.Web3Provider(ethereum); + setProvider(providerInstance); + await ethService.checkIfMetamaskIsLoaded(providerInstance); + + const fetchedToken = await getToken(providerInstance); + setToken(fetchedToken); + const status = await ethService.isUserConnectedToTenChain(fetchedToken); + setWalletConnected(status); + + const accounts = await ethService.getAccounts(providerInstance); + setAccounts(accounts || null); + setVersion(await fetchVersion()); + } catch (error) { + showToast( + ToastType.DESTRUCTIVE, + "Error initializing wallet connection. Please refresh the page." + ); + } finally { + setLoading(false); + } }; const connectAccount = async (account: string) => { - if (!userID) { - showToast(ToastType.INFO, "User ID is required to connect an account."); - return; - } - await authenticateAccountWithTenGatewayEIP712(userID, account); - const { status } = await accountIsAuthenticated(userID, account); - if (status) { - showToast(ToastType.SUCCESS, "Account authenticated!"); - setAccounts((accounts) => { - if (!accounts) { - return null; - } - return accounts.map((acc) => { - if (acc.name === account) { - return { - ...acc, - connected: status, - }; + try { + if (!token) { + showToast( + ToastType.INFO, + "Encryption token is required to connect an account." + ); + return; + } + await authenticateAccountWithTenGatewayEIP712(token, account); + const { status } = await accountIsAuthenticated(token, account); + if (status) { + showToast(ToastType.SUCCESS, "Account authenticated!"); + setAccounts((accounts) => { + if (!accounts) { + return null; } - return acc; + return accounts.map((acc) => { + if (acc.name === account) { + return { + ...acc, + connected: status, + }; + } + return acc; + }); }); - }); - } else { + } else { + showToast(ToastType.DESTRUCTIVE, "Account authentication failed."); + } + } catch (error) { showToast(ToastType.DESTRUCTIVE, "Account authentication failed."); } }; const revokeAccounts = async () => { - if (!userID) { - showToast(ToastType.INFO, "User ID is required to revoke accounts"); + if (!token) { + showToast( + ToastType.INFO, + "Encryption token is required to revoke accounts" + ); return; } - const revokeResponse = await revokeAccountsApi(userID); + const revokeResponse = await revokeAccountsApi(token); if (revokeResponse === ToastType.SUCCESS) { showToast(ToastType.DESTRUCTIVE, "Accounts revoked!"); setAccounts(null); setWalletConnected(false); - setUserID(""); + setToken(""); } }; const fetchUserAccounts = async () => { - const accounts = await ethService.getAccounts(provider); - setAccounts(accounts || null); - setWalletConnected(true); + if (!provider) { + showToast( + ToastType.INFO, + "Provider is required to fetch user accounts. Please connect your wallet." + ); + return; + } + + try { + const accounts = await ethService.getAccounts(provider); + const token = await getToken(provider); + setToken(token); + let updatedAccounts: Account[] = []; + + updatedAccounts = await Promise.all( + accounts!.map(async (account) => { + await ethService.authenticateWithGateway(token, account.name); + const { status } = await accountIsAuthenticated(token, account.name); + return { + ...account, + connected: status, + }; + }) + ); + setAccounts(updatedAccounts || null); + } catch (error) { + showToast(ToastType.DESTRUCTIVE, "Error fetching user accounts."); + } finally { + setWalletConnected(true); + setLoading(false); + } }; const walletConnectionContextValue: WalletConnectionContextType = { walletConnected, accounts, - userID, + token, connectAccount, version, revokeAccounts, diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index 5b582a40dd..0c22bc6180 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -33,7 +33,7 @@ export const testnetUrls = { }; export const SWITCHED_CODE = 4902; -export const userIDHexLength = 40; +export const tokenHexLength = 40; export const tenGatewayVersion = "v1"; export const tenChainIDDecimal = 443; diff --git a/tools/walletextension/frontend/src/lib/utils.ts b/tools/walletextension/frontend/src/lib/utils.ts index a13f5f2cea..f4d0c7abfd 100644 --- a/tools/walletextension/frontend/src/lib/utils.ts +++ b/tools/walletextension/frontend/src/lib/utils.ts @@ -5,7 +5,7 @@ import { tenChainIDHex, tenGatewayAddress, testnetUrls, - userIDHexLength, + tokenHexLength, } from "./constants"; export function cn(...inputs: ClassValue[]) { @@ -17,8 +17,8 @@ export function formatTimeAgo(unixTimestampSeconds: string) { return formatDistanceToNow(date, { addSuffix: true }); } -export function isValidUserIDFormat(value: string) { - return typeof value === "string" && value.length === userIDHexLength; +export function isValidTokenFormat(value: string) { + return typeof value === "string" && value.length === tokenHexLength; } export function getRandomIntAsString(min: number, max: number) { @@ -27,6 +27,7 @@ export function getRandomIntAsString(min: number, max: number) { const randomInt = Math.floor(Math.random() * (max - min + 1)) + min; return randomInt.toString(); } + export function getNetworkName() { switch (tenGatewayAddress) { case testnetUrls.uat.url: diff --git a/tools/walletextension/frontend/src/routes/index.ts b/tools/walletextension/frontend/src/routes/index.ts index 1b27864791..2bba85e009 100644 --- a/tools/walletextension/frontend/src/routes/index.ts +++ b/tools/walletextension/frontend/src/routes/index.ts @@ -6,7 +6,7 @@ export const NavLinks: NavLink[] = []; export const apiRoutes = { join: `/${tenGatewayVersion}/join/`, authenticate: `/${tenGatewayVersion}/authenticate/`, - queryAccountUserID: `/${tenGatewayVersion}/query/`, + queryAccountToken: `/${tenGatewayVersion}/query/`, revoke: `/${tenGatewayVersion}/revoke/`, version: `/version/`, }; diff --git a/tools/walletextension/frontend/src/services/ethService.ts b/tools/walletextension/frontend/src/services/ethService.ts index da1c3ce780..b44de0ccd0 100644 --- a/tools/walletextension/frontend/src/services/ethService.ts +++ b/tools/walletextension/frontend/src/services/ethService.ts @@ -1,79 +1,69 @@ +import { ethers } from "ethers"; import { authenticateAccountWithTenGatewayEIP712, - getUserID, + getToken, } from "@/api/ethRequests"; import { accountIsAuthenticated } from "@/api/gateway"; import { showToast } from "@/components/ui/use-toast"; import { METAMASK_CONNECTION_TIMEOUT } from "@/lib/constants"; -import { isTenChain, isValidUserIDFormat, ethereum } from "@/lib/utils"; +import { isTenChain, isValidTokenFormat, ethereum } from "@/lib/utils"; import { ToastType } from "@/types/interfaces"; import { Account } from "@/types/interfaces/WalletInterfaces"; -import { ethers } from "ethers"; const ethService = { checkIfMetamaskIsLoaded: async (provider: ethers.providers.Web3Provider) => { - if (ethereum) { - await ethService.handleEthereum(provider); - } else { - showToast(ToastType.INFO, "Connecting to MetaMask..."); - - let timeoutId: ReturnType; - const handleEthereumOnce = () => { - ethService.handleEthereum(provider); - }; + try { + if (ethereum) { + return await ethService.handleEthereum(provider); + } else { + showToast(ToastType.INFO, "Connecting to MetaMask..."); + + let timeoutId: ReturnType; + + const handleEthereumOnce = async () => { + await ethService.handleEthereum(provider); + }; + + window.addEventListener( + "ethereum#initialized", + () => { + clearTimeout(timeoutId); + handleEthereumOnce(); + }, + { + once: true, + } + ); - window.addEventListener( - "ethereum#initialized", - () => { - clearTimeout(timeoutId); + timeoutId = setTimeout(() => { handleEthereumOnce(); - }, - { - once: true, - } - ); - - timeoutId = setTimeout(() => { - handleEthereumOnce(); // Call the handler function after the timeout - }, METAMASK_CONNECTION_TIMEOUT); + }, METAMASK_CONNECTION_TIMEOUT); + } + } catch (error) { + showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); + throw error; } }, handleEthereum: async (provider: ethers.providers.Web3Provider) => { - if (ethereum && ethereum.isMetaMask) { - const fetchedUserID = await getUserID(provider); - if (fetchedUserID && isValidUserIDFormat(fetchedUserID)) { - showToast(ToastType.SUCCESS, "MetaMask connected!"); + try { + if (ethereum && ethereum.isMetaMask) { + return; } else { showToast( ToastType.WARNING, - "Please connect to the Ten chain to use Ten Gateway." + "Please install MetaMask to use Ten Gateway." ); } - } else { - showToast( - ToastType.WARNING, - "Please install MetaMask to use Ten Gateway." - ); - } - }, - - fetchUserID: async (provider: ethers.providers.Web3Provider) => { - try { - return await getUserID(provider); - } catch (e: any) { - showToast( - ToastType.DESTRUCTIVE, - `${e.message} ${e.data?.message}` || - "Error: Could not fetch your user ID. Please try again later." - ); - return null; + } catch (error: any) { + showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); + throw error; } }, - isUserConnectedToTenChain: async (userID: string) => { + isUserConnectedToTenChain: async (token: string) => { if (await isTenChain()) { - if (userID && isValidUserIDFormat(userID)) { + if (token && isValidTokenFormat(token)) { return true; } else { return false; @@ -83,7 +73,11 @@ const ethService = { } }, - getAccounts: async (provider: ethers.providers.Web3Provider) => { + formatAccounts: async ( + accounts: string[], + provider: ethers.providers.Web3Provider, + token: string + ) => { if (!provider) { showToast( ToastType.DESTRUCTIVE, @@ -91,17 +85,35 @@ const ethService = { ); return; } + let updatedAccounts: Account[] = []; + showToast(ToastType.INFO, "Checking account authentication status..."); + const authenticationPromise = accounts.map((account) => + accountIsAuthenticated(token, account).then(({ status }) => { + return { + name: account, + connected: status, + }; + }) + ); + updatedAccounts = await Promise.all(authenticationPromise); + return updatedAccounts; + }, - const id = await getUserID(provider); - - if (!id || !isValidUserIDFormat(id)) { + getAccounts: async (provider: ethers.providers.Web3Provider) => { + if (!provider) { showToast( ToastType.DESTRUCTIVE, - "No user ID found. Please try again later." + "No provider found. Please try again later." ); return; } + const token = await getToken(provider); + + if (!token || !isValidTokenFormat(token)) { + return; + } + try { showToast(ToastType.INFO, "Getting accounts..."); @@ -116,25 +128,25 @@ const ethService = { showToast(ToastType.DESTRUCTIVE, "No MetaMask accounts found."); return []; } + showToast(ToastType.SUCCESS, "Accounts found!"); - let updatedAccounts: Account[] = []; - - const authenticationPromises = accounts.map((account) => - authenticateAccountWithTenGatewayEIP712(id, account) - .then(() => accountIsAuthenticated(id, account)) - .then(({ status }) => ({ - name: account, - connected: status, - })) - ); - updatedAccounts = await Promise.all(authenticationPromises); - showToast(ToastType.SUCCESS, "Accounts fetched successfully."); - return updatedAccounts; + return ethService.formatAccounts(accounts, provider, token); } catch (error) { console.error(error); showToast(ToastType.DESTRUCTIVE, "An error occurred. Please try again."); } }, + + authenticateWithGateway: async (token: string, account: string) => { + try { + return await authenticateAccountWithTenGatewayEIP712(token, account); + } catch (error) { + showToast( + ToastType.DESTRUCTIVE, + `Error authenticating account: ${account}` + ); + } + }, }; export default ethService; diff --git a/tools/walletextension/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts index 37d27af96c..c2920d9076 100644 --- a/tools/walletextension/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/frontend/src/services/useGatewayService.ts @@ -3,7 +3,7 @@ import { joinTestnet } from "../api/gateway"; import { useWalletConnection } from "../components/providers/wallet-provider"; import { showToast } from "../components/ui/use-toast"; import { SWITCHED_CODE, tenGatewayVersion } from "../lib/constants"; -import { getRPCFromUrl, isTenChain, isValidUserIDFormat } from "../lib/utils"; +import { getRPCFromUrl, isTenChain, isValidTokenFormat } from "../lib/utils"; import { addNetworkToMetaMask, connectAccounts, @@ -11,7 +11,7 @@ import { } from "@/api/ethRequests"; const useGatewayService = () => { - const { userID, provider, fetchUserAccounts, setLoading } = + const { token, provider, fetchUserAccounts, setLoading } = useWalletConnection(); const isMetamaskConnected = async () => { @@ -23,15 +23,15 @@ const useGatewayService = () => { return accounts.length > 0; } catch (error) { showToast(ToastType.DESTRUCTIVE, "Unable to get accounts"); + throw error; } - return false; }; const connectToTenTestnet = async () => { setLoading(true); try { if (await isTenChain()) { - if (!userID || !isValidUserIDFormat(userID)) { + if (!token || !isValidTokenFormat(token)) { showToast( ToastType.DESTRUCTIVE, "Existing Ten network detected in MetaMask. Please remove before hitting begin" @@ -42,10 +42,7 @@ const useGatewayService = () => { const switched = await switchToTenNetwork(); - if ( - switched === SWITCHED_CODE || - (userID && !isValidUserIDFormat(userID)) - ) { + if (switched === SWITCHED_CODE || (token && !isValidTokenFormat(token))) { const user = await joinTestnet(); const rpcUrls = [ `${getRPCFromUrl()}/${tenGatewayVersion}/?token=${user}`, diff --git a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts index 3ca123984a..7456a4ff39 100644 --- a/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts +++ b/tools/walletextension/frontend/src/types/interfaces/WalletInterfaces.ts @@ -4,7 +4,7 @@ export interface WalletConnectionContextType { accounts: Account[] | null; walletConnected: boolean; connectAccount: (account: string) => Promise; - userID: string | null; + token: string | null; version: string | null; revokeAccounts: () => void; loading: boolean; @@ -13,10 +13,6 @@ export interface WalletConnectionContextType { setLoading: (loading: boolean) => void; } -export interface Props { - children: React.ReactNode; -} - export interface State { hasError: boolean; } From 1169109a88b2d22413d5043ab1528c0932f3f9f4 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 7 Dec 2023 04:01:25 +0400 Subject: [PATCH 25/36] update toast methods --- tools/walletextension/frontend/src/hooks/useCopy.ts | 11 +++++++---- .../walletextension/frontend/src/pages/docs/[id].tsx | 9 +++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/walletextension/frontend/src/hooks/useCopy.ts b/tools/walletextension/frontend/src/hooks/useCopy.ts index 249e817dfd..e05e843c05 100644 --- a/tools/walletextension/frontend/src/hooks/useCopy.ts +++ b/tools/walletextension/frontend/src/hooks/useCopy.ts @@ -1,19 +1,22 @@ -import { useToast } from "../components/ui/use-toast"; +import { showToast } from "@/components/ui/use-toast"; +import { ToastType } from "@/types/interfaces"; import React from "react"; export const useCopy = () => { - const { toast } = useToast(); const [copied, setCopied] = React.useState(false); const copyToClipboard = (text: string, name?: string) => { copyText(text) .catch(() => fallbackCopyTextToClipboard(text)) .then(() => { - toast({ description: `${name ? name : ""} Copied!` }); + showToast(ToastType.DESTRUCTIVE, `${name ? name : "Copied!"}`); setCopied(true); }) .catch(() => { - toast({ description: `Couldn't copy ${name ? name : "Text"}!!!` }); + showToast( + ToastType.DESTRUCTIVE, + `Couldn't copy ${name ? name : "Text"}!!!` + ); }) .finally(() => { setTimeout(() => setCopied(false), 1000); diff --git a/tools/walletextension/frontend/src/pages/docs/[id].tsx b/tools/walletextension/frontend/src/pages/docs/[id].tsx index a4c8408259..a757d68e1c 100644 --- a/tools/walletextension/frontend/src/pages/docs/[id].tsx +++ b/tools/walletextension/frontend/src/pages/docs/[id].tsx @@ -1,10 +1,11 @@ import Layout from "@/components/layouts/default-layout"; import Spinner from "@/components/ui/spinner"; -import { useToast } from "@/components/ui/use-toast"; import { siteMetadata } from "@/lib/siteMetadata"; import { useRouter } from "next/router"; import React from "react"; import Custom404Error from "../404"; +import { showToast } from "@/components/ui/use-toast"; +import { ToastType } from "@/types/interfaces"; type Document = { title: string; @@ -16,7 +17,6 @@ type Document = { }; const Document = () => { - const { toast } = useToast(); const { query } = useRouter(); const { id } = query; @@ -45,10 +45,7 @@ const Document = () => { }; setDocument(processedData); } catch (error) { - toast({ - variant: "destructive", - description: "Error fetching document", - }); + showToast(ToastType.DESTRUCTIVE, "Error fetching document"); } finally { setLoading(false); } From 820ce4e4186fe68a11e7b8de37531a95f30b9292 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 7 Dec 2023 16:05:52 +0400 Subject: [PATCH 26/36] update meta description --- tools/walletextension/frontend/src/lib/siteMetadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/walletextension/frontend/src/lib/siteMetadata.ts b/tools/walletextension/frontend/src/lib/siteMetadata.ts index 30f2f4d872..70544f59b9 100644 --- a/tools/walletextension/frontend/src/lib/siteMetadata.ts +++ b/tools/walletextension/frontend/src/lib/siteMetadata.ts @@ -5,7 +5,7 @@ export const siteMetadata = { metaTitle: "Ten, a decentralized Layer 2 Rollup protocol designed to hyper-scale and encrypt the Ethereum blockchain.", description: - "Ten, a decentralized Ethereum Layer 2 Rollup protocol designed to hyper-scale, encrypt and prevent negative MEV on the Ethereum blockchain using Secure Enclaves and ZKPs.", + "Ten, a decentralized Ethereum Layer 2 Rollup protocol designed to hyper-scale, encrypt and prevent negative MEV on Ethereum.", siteUrl: "https://obscu.ro", siteLogo: ``, siteLogoSquare: ``, From d9c7dd0049279c6234df22d62ba6b14309c62b06 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Thu, 7 Dec 2023 18:26:28 +0400 Subject: [PATCH 27/36] add env example file --- tools/walletextension/frontend/src/api/index.ts | 1 - tools/walletextension/frontend/src/lib/constants.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/walletextension/frontend/src/api/index.ts b/tools/walletextension/frontend/src/api/index.ts index 610ff3c6d8..6b4cb9e72a 100644 --- a/tools/walletextension/frontend/src/api/index.ts +++ b/tools/walletextension/frontend/src/api/index.ts @@ -21,7 +21,6 @@ interface HttpOptions { } const baseConfig: AxiosRequestConfig = { - // baseURL: process.env.BASE_URL, baseURL: tenGatewayAddress, timeout: 10000, }; diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index 0c22bc6180..790b1c75aa 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -1,4 +1,4 @@ -export const tenGatewayAddress = "https://uat-testnet.obscu.ro"; +export const tenGatewayAddress = process.env.NEXT_PUBLIC_API_GATEWAY_URL; export const tenscanLink = "https://testnet.tenscan.com"; export const socialLinks = { From 2286cc106b8e6f4b7a3257d36296311a67fe038b Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Fri, 8 Dec 2023 14:32:02 +0400 Subject: [PATCH 28/36] remove log --- tools/walletextension/frontend/src/api/ethRequests.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/walletextension/frontend/src/api/ethRequests.ts b/tools/walletextension/frontend/src/api/ethRequests.ts index ef2556d8e5..12f1d74449 100644 --- a/tools/walletextension/frontend/src/api/ethRequests.ts +++ b/tools/walletextension/frontend/src/api/ethRequests.ts @@ -155,12 +155,10 @@ export async function authenticateAccountWithTenGatewayEIP712( }; const signature = await getSignature(account, data); - const auth = await authenticateUser(token, { + return await authenticateUser(token, { signature, address: account, }); - console.log("🚀 ~ file: ethRequests.ts:166 ~ auth:", auth); - return auth; } catch (error: any) { throw error; } From 75ddb7af85c10afb87c3d8341c01acc0c2e6aef6 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Fri, 8 Dec 2023 14:37:57 +0400 Subject: [PATCH 29/36] update README to configure env --- tools/walletextension/frontend/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/walletextension/frontend/README.md b/tools/walletextension/frontend/README.md index dfa5e41705..8c477a6554 100644 --- a/tools/walletextension/frontend/README.md +++ b/tools/walletextension/frontend/README.md @@ -32,7 +32,19 @@ Ten Gateway is a Next.js and Tailwind CSS-powered application. npm install ``` -3. **Run the Development Server:** +3. **Configure Environment Variables:** +Create a `.env.local` file in the root directory of the project and add the following environment variables: + + ```bash + NEXT_PUBLIC_API_GATEWAY_URL=******** + ``` + + Possible values for `NEXT_PUBLIC_API_GATEWAY_URL` are: + - `https://https://uat-testnet.obscu.ro` + - `https://https://sepolia-testnet.obscu.ro` + - `https://https://dev-testnet.obscu.ro` + +4. **Run the Development Server:** ```bash npm run dev ``` From ca85d2a6565521d72ae66091a30ae62e181fc821 Mon Sep 17 00:00:00 2001 From: Jennifer Echenim Date: Fri, 8 Dec 2023 14:43:48 +0400 Subject: [PATCH 30/36] fix build error --- .../frontend/src/components/modules/common/copy.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/walletextension/frontend/src/components/modules/common/copy.tsx b/tools/walletextension/frontend/src/components/modules/common/copy.tsx index 94d8e0123d..87cb51872f 100644 --- a/tools/walletextension/frontend/src/components/modules/common/copy.tsx +++ b/tools/walletextension/frontend/src/components/modules/common/copy.tsx @@ -3,15 +3,15 @@ import { Button } from "../../ui/button"; import { useCopy } from "../../../hooks/useCopy"; import { CopyIcon, CheckIcon } from "@radix-ui/react-icons"; -const Copy = ({ value }: { value: string | number }) => { +const Copy = ({ value }: { value: string | number | undefined }) => { const { copyToClipboard, copied } = useCopy(); return (