diff --git a/.gitignore b/.gitignore index 742f4df2f..48398df42 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,9 @@ project-words.txt .venv **/.obsidian/** test_result.txt +logs +node_modules +.vscode +**/static/frontend +tools/Blockchain/EtherView2/frontend/auto-imports.d.ts +tools/Blockchain/EtherView2/frontend/components.d.ts \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/.dockerignore b/tools/Blockchain/EtherView2/.dockerignore new file mode 100644 index 000000000..a95eca0bf --- /dev/null +++ b/tools/Blockchain/EtherView2/.dockerignore @@ -0,0 +1,5 @@ +**/node_modules +**/logs +**/auto-imports.d.ts +**/components.d.ts +**/__pycache__ \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/.gitignore b/tools/Blockchain/EtherView2/.gitignore new file mode 100644 index 000000000..0b6611862 --- /dev/null +++ b/tools/Blockchain/EtherView2/.gitignore @@ -0,0 +1,3 @@ +keystore/ +emulator_info/ +emulator_data/ \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/Dockerfile b/tools/Blockchain/EtherView2/Dockerfile new file mode 100644 index 000000000..5f66658d8 --- /dev/null +++ b/tools/Blockchain/EtherView2/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22 AS node-image +WORKDIR /frontend +RUN npm install -g pnpm +RUN pnpm i +RUN pnpm run build-test + +FROM handsonsecurity/seedemu-multiarch-base:buildx-latest +ARG DEBIAN_FRONTEND=noninteractive +WORKDIR / +RUN apt-get update && apt-get install -y python3 python3-pip +RUN pip3 install flask web3==5.31.1 docker Flask-Cors flask-apscheduler Flask-SQLAlchemy pymysql +COPY start.sh /start.sh +RUN chmod +x /start.sh +COPY . . +ENTRYPOINT ["sh", "/start.sh"] diff --git a/tools/Blockchain/EtherView2/README.md b/tools/Blockchain/EtherView2/README.md new file mode 100644 index 000000000..b939691fa --- /dev/null +++ b/tools/Blockchain/EtherView2/README.md @@ -0,0 +1,21 @@ +# EtherView + +EtherView is a collection of tools that can be used to +interact with the Ethereum emulator. It has the following +features: + +- Viewing the balances for all the accounts + +- Viewing blocks and transactions (like EtherScan) + +- Viewing (or visualizing) interesting blockchain properties, such as + - base fee + - transaction pools on each node + +- Viewing POA/POS related information, especially POS. + +- Sample dApps. We should host some open-source dApps. Each dApp + is built into a container. They can be included when the emulator + is built. When EtherView detects it, the corresponding menu + will appear, leading us to the dApp. More thinking is needed + for this feature. diff --git a/tools/Blockchain/EtherView2/docker-compose.yml b/tools/Blockchain/EtherView2/docker-compose.yml new file mode 100644 index 000000000..2b926c8c1 --- /dev/null +++ b/tools/Blockchain/EtherView2/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3" + +services: + seedemu-etherview: + build: . + container_name: seedemu-etherview + volumes: + - /var/run/docker.sock:/var/run/docker.sock + ports: + - 5000:5000 + image: handsonsecurity/seedemu-etherview diff --git a/tools/Blockchain/EtherView2/frontend/.env/.env.development b/tools/Blockchain/EtherView2/frontend/.env/.env.development new file mode 100644 index 000000000..028cd7d44 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/.env/.env.development @@ -0,0 +1,9 @@ +VITE_NODE_ENV=development +VITE_OPEN=true +VITE_APP_HOST=0.0.0.0 +VITE_APP_PORT=5173 +VITE_URL_PREFIX=/ +VITE_APP_BASE_URL=/api +VITE_APP_SERVER=http://127.0.0.1:3000 +VITE_APP_TIMEOUT=10000 +VITE_APP_TITLE=开发环境 \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/.env/.env.production b/tools/Blockchain/EtherView2/frontend/.env/.env.production new file mode 100644 index 000000000..6274a134a --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/.env/.env.production @@ -0,0 +1,7 @@ +VITE_NODE_ENV=production +VITE_URL_PREFIX=/frontend +VITE_STATIC_ASSET_PREFIX=/static +VITE_APP_BASE_URL=/api +VITE_APP_SERVER=http://127.0.0.1:3000 +VITE_APP_TIMEOUT=10000 +VITE_APP_TITLE=生产环境 \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/README.md b/tools/Blockchain/EtherView2/frontend/README.md new file mode 100644 index 000000000..33895ab20 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/tools/Blockchain/EtherView2/frontend/package.json b/tools/Blockchain/EtherView2/frontend/package.json new file mode 100644 index 000000000..92780dbd2 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/package.json @@ -0,0 +1,33 @@ +{ + "name": "vue3-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview", + "build-test": "vue-tsc --skipLibCheck && vite build" + }, + "dependencies": { + "pinia": "^3.0.3", + "vue": "^3.5.21" + }, + "devDependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@types/decimal.js": "^7.4.3", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.8.1", + "axios": "^1.12.2", + "decimal.js": "^10.6.0", + "element-plus": "^2.11.4", + "ethers": "~5.4.7", + "sass-embedded": "^1.93.2", + "typescript": "~5.8.3", + "unplugin-auto-import": "^20.2.0", + "unplugin-vue-components": "^29.1.0", + "vite": "^7.1.7", + "vue-router": "^4.5.1", + "vue-tsc": "^3.0.7" + } +} diff --git a/tools/Blockchain/EtherView2/frontend/pnpm-lock.yaml b/tools/Blockchain/EtherView2/frontend/pnpm-lock.yaml new file mode 100644 index 000000000..5635971dd --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/pnpm-lock.yaml @@ -0,0 +1,2734 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + pinia: + specifier: ^3.0.3 + version: 3.0.3(typescript@5.8.3)(vue@3.5.22(typescript@5.8.3)) + vue: + specifier: ^3.5.21 + version: 3.5.22(typescript@5.8.3) + devDependencies: + '@element-plus/icons-vue': + specifier: ^2.3.2 + version: 2.3.2(vue@3.5.22(typescript@5.8.3)) + '@types/decimal.js': + specifier: ^7.4.3 + version: 7.4.3 + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.1(vite@7.1.7(sass-embedded@1.93.2)(sass@1.93.2))(vue@3.5.22(typescript@5.8.3)) + '@vue/tsconfig': + specifier: ^0.8.1 + version: 0.8.1(typescript@5.8.3)(vue@3.5.22(typescript@5.8.3)) + axios: + specifier: ^1.12.2 + version: 1.12.2 + decimal.js: + specifier: ^10.6.0 + version: 10.6.0 + element-plus: + specifier: ^2.11.4 + version: 2.11.4(vue@3.5.22(typescript@5.8.3)) + ethers: + specifier: ~5.4.7 + version: 5.4.7 + sass-embedded: + specifier: ^1.93.2 + version: 1.93.2 + typescript: + specifier: ~5.8.3 + version: 5.8.3 + unplugin-auto-import: + specifier: ^20.2.0 + version: 20.2.0(@vueuse/core@9.13.0(vue@3.5.22(typescript@5.8.3))) + unplugin-vue-components: + specifier: ^29.1.0 + version: 29.1.0(@babel/parser@7.28.4)(vue@3.5.22(typescript@5.8.3)) + vite: + specifier: ^7.1.7 + version: 7.1.7(sass-embedded@1.93.2)(sass@1.93.2) + vue-router: + specifier: ^4.5.1 + version: 4.5.1(vue@3.5.22(typescript@5.8.3)) + vue-tsc: + specifier: ^3.0.7 + version: 3.0.8(typescript@5.8.3) + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + + '@bufbuild/protobuf@2.9.0': + resolution: {integrity: sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@esbuild/aix-ppc64@0.25.10': + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.10': + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.10': + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.10': + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.10': + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.10': + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.10': + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.10': + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.10': + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.10': + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.10': + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.10': + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.10': + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.10': + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.10': + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.10': + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.10': + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.10': + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.10': + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.10': + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.10': + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.10': + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.10': + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.10': + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@ethersproject/abi@5.4.1': + resolution: {integrity: sha512-9mhbjUk76BiSluiiW4BaYyI58KSbDMMQpCLdsAR+RsT2GyATiNYxVv+pGWRrekmsIdY3I+hOqsYQSTkc8L/mcg==} + + '@ethersproject/abstract-provider@5.4.1': + resolution: {integrity: sha512-3EedfKI3LVpjSKgAxoUaI+gB27frKsxzm+r21w9G60Ugk+3wVLQwhi1LsEJAKNV7WoZc8CIpNrATlL1QFABjtQ==} + + '@ethersproject/abstract-signer@5.4.1': + resolution: {integrity: sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA==} + + '@ethersproject/address@5.4.0': + resolution: {integrity: sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q==} + + '@ethersproject/base64@5.4.0': + resolution: {integrity: sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ==} + + '@ethersproject/basex@5.4.0': + resolution: {integrity: sha512-J07+QCVJ7np2bcpxydFVf/CuYo9mZ7T73Pe7KQY4c1lRlrixMeblauMxHXD0MPwFmUHZIILDNViVkykFBZylbg==} + + '@ethersproject/bignumber@5.4.2': + resolution: {integrity: sha512-oIBDhsKy5bs7j36JlaTzFgNPaZjiNDOXsdSgSpXRucUl+UA6L/1YLlFeI3cPAoodcenzF4nxNPV13pcy7XbWjA==} + + '@ethersproject/bytes@5.4.0': + resolution: {integrity: sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA==} + + '@ethersproject/constants@5.4.0': + resolution: {integrity: sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q==} + + '@ethersproject/contracts@5.4.1': + resolution: {integrity: sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w==} + + '@ethersproject/hash@5.4.0': + resolution: {integrity: sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA==} + + '@ethersproject/hdnode@5.4.0': + resolution: {integrity: sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q==} + + '@ethersproject/json-wallets@5.4.0': + resolution: {integrity: sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ==} + + '@ethersproject/keccak256@5.4.0': + resolution: {integrity: sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A==} + + '@ethersproject/logger@5.4.1': + resolution: {integrity: sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A==} + + '@ethersproject/networks@5.4.2': + resolution: {integrity: sha512-eekOhvJyBnuibfJnhtK46b8HimBc5+4gqpvd1/H9LEl7Q7/qhsIhM81dI9Fcnjpk3jB1aTy6bj0hz3cifhNeYw==} + + '@ethersproject/pbkdf2@5.4.0': + resolution: {integrity: sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g==} + + '@ethersproject/properties@5.4.1': + resolution: {integrity: sha512-cyCGlF8wWlIZyizsj2PpbJ9I7rIlUAfnHYwy/T90pdkSn/NFTa5YWZx2wTJBe9V7dD65dcrrEMisCRUJiq6n3w==} + + '@ethersproject/providers@5.4.5': + resolution: {integrity: sha512-1GkrvkiAw3Fj28cwi1Sqm8ED1RtERtpdXmRfwIBGmqBSN5MoeRUHuwHPppMtbPayPgpFcvD7/Gdc9doO5fGYgw==} + + '@ethersproject/random@5.4.0': + resolution: {integrity: sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw==} + + '@ethersproject/rlp@5.4.0': + resolution: {integrity: sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg==} + + '@ethersproject/sha2@5.4.0': + resolution: {integrity: sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg==} + + '@ethersproject/signing-key@5.4.0': + resolution: {integrity: sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A==} + + '@ethersproject/solidity@5.4.0': + resolution: {integrity: sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ==} + + '@ethersproject/strings@5.4.0': + resolution: {integrity: sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA==} + + '@ethersproject/transactions@5.4.0': + resolution: {integrity: sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ==} + + '@ethersproject/units@5.4.0': + resolution: {integrity: sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg==} + + '@ethersproject/wallet@5.4.0': + resolution: {integrity: sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ==} + + '@ethersproject/web@5.4.0': + resolution: {integrity: sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og==} + + '@ethersproject/wordlists@5.4.0': + resolution: {integrity: sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA==} + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + + '@rollup/rollup-android-arm-eabi@4.52.3': + resolution: {integrity: sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.3': + resolution: {integrity: sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.3': + resolution: {integrity: sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.3': + resolution: {integrity: sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.3': + resolution: {integrity: sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.3': + resolution: {integrity: sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': + resolution: {integrity: sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.52.3': + resolution: {integrity: sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.52.3': + resolution: {integrity: sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.52.3': + resolution: {integrity: sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.52.3': + resolution: {integrity: sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.52.3': + resolution: {integrity: sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.52.3': + resolution: {integrity: sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.52.3': + resolution: {integrity: sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.52.3': + resolution: {integrity: sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.52.3': + resolution: {integrity: sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.52.3': + resolution: {integrity: sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.52.3': + resolution: {integrity: sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.3': + resolution: {integrity: sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.3': + resolution: {integrity: sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.3': + resolution: {integrity: sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.3': + resolution: {integrity: sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==} + cpu: [x64] + os: [win32] + + '@sxzz/popperjs-es@2.11.7': + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} + + '@types/decimal.js@7.4.3': + resolution: {integrity: sha512-7MpxcJPHqQ637FCZwJLtJMaDZkcD/iyUxj0m8A+m06slFeqRiK9QtgEyuocWNRbEtCrOZOEbZPTSSR88hMZVsg==} + deprecated: This is a stub types definition. decimal.js provides its own type definitions, so you do not need this installed. + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + + '@types/web-bluetooth@0.0.16': + resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} + + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vue/compiler-core@3.5.22': + resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==} + + '@vue/compiler-dom@3.5.22': + resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==} + + '@vue/compiler-sfc@3.5.22': + resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==} + + '@vue/compiler-ssr@3.5.22': + resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/devtools-api@7.7.7': + resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} + + '@vue/devtools-kit@7.7.7': + resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} + + '@vue/devtools-shared@7.7.7': + resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} + + '@vue/language-core@3.0.8': + resolution: {integrity: sha512-eYs6PF7bxoPYvek9qxceo1BCwFbJZYqJll+WaYC8o8ec60exqj+n+QRGGiJHSeUfYp0hDxARbMdxMq/fbPgU5g==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.22': + resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==} + + '@vue/runtime-core@3.5.22': + resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==} + + '@vue/runtime-dom@3.5.22': + resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==} + + '@vue/server-renderer@3.5.22': + resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==} + peerDependencies: + vue: 3.5.22 + + '@vue/shared@3.5.22': + resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==} + + '@vue/tsconfig@0.8.1': + resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + '@vueuse/core@9.13.0': + resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} + + '@vueuse/metadata@9.13.0': + resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} + + '@vueuse/shared@9.13.0': + resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + aes-js@3.0.0: + resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} + + alien-signals@2.0.8: + resolution: {integrity: sha512-844G1VLkk0Pe2SJjY0J8vp8ADI73IM4KliNu2OGlYzWpO28NexEUvjHTcFjFX3VXoiUtwTbHxLNI9ImkcoBqzA==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.12.2: + resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} + + bech32@1.1.4: + resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + birpc@2.6.1: + resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==} + + bn.js@4.12.2: + resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + buffer-builder@0.2.0: + resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + colorjs.io@0.5.2: + resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + element-plus@2.11.4: + resolution: {integrity: sha512-sLq+Ypd0cIVilv8wGGMEGvzRVBBsRpJjnAS5PsI/1JU1COZXqzH3N1UYMUc/HCdvdjf6dfrBy80Sj7KcACsT7w==} + peerDependencies: + vue: ^3.2.0 + + elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + ethers@5.4.7: + resolution: {integrity: sha512-iZc5p2nqfWK1sj8RabwsPM28cr37Bpq7ehTQ5rWExBr2Y09Sn1lDKZOED26n+TsZMye7Y6mIgQ/1cwpSD8XZew==} + + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + immutable@5.1.3: + resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + js-sha3@0.5.7: + resolution: {integrity: sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pinia@3.0.3: + resolution: {integrity: sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.52.3: + resolution: {integrity: sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + sass-embedded-all-unknown@1.93.2: + resolution: {integrity: sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q==} + cpu: ['!arm', '!arm64', '!riscv64', '!x64'] + + sass-embedded-android-arm64@1.93.2: + resolution: {integrity: sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + + sass-embedded-android-arm@1.93.2: + resolution: {integrity: sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + + sass-embedded-android-riscv64@1.93.2: + resolution: {integrity: sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [android] + + sass-embedded-android-x64@1.93.2: + resolution: {integrity: sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + + sass-embedded-darwin-arm64@1.93.2: + resolution: {integrity: sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + + sass-embedded-darwin-x64@1.93.2: + resolution: {integrity: sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + + sass-embedded-linux-arm64@1.93.2: + resolution: {integrity: sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-arm@1.93.2: + resolution: {integrity: sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-musl-arm64@1.93.2: + resolution: {integrity: sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-musl-arm@1.93.2: + resolution: {integrity: sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-musl-riscv64@1.93.2: + resolution: {integrity: sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-musl-x64@1.93.2: + resolution: {integrity: sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-linux-riscv64@1.93.2: + resolution: {integrity: sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-x64@1.93.2: + resolution: {integrity: sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-unknown-all@1.93.2: + resolution: {integrity: sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==} + os: ['!android', '!darwin', '!linux', '!win32'] + + sass-embedded-win32-arm64@1.93.2: + resolution: {integrity: sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + + sass-embedded-win32-x64@1.93.2: + resolution: {integrity: sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + + sass-embedded@1.93.2: + resolution: {integrity: sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==} + engines: {node: '>=16.0.0'} + hasBin: true + + sass@1.93.2: + resolution: {integrity: sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==} + engines: {node: '>=14.0.0'} + hasBin: true + + scrypt-js@3.0.1: + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + superjson@2.2.3: + resolution: {integrity: sha512-ay3d+LW/S6yppKoTz3Bq4mG0xrS5bFwfWEBmQfbC7lt5wmtk+Obq0TxVuA9eYRirBTQb1K3eEpBRHMQEo0WyVw==} + engines: {node: '>=16'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + sync-child-process@1.0.2: + resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} + engines: {node: '>=16.0.0'} + + sync-message-port@1.1.3: + resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==} + engines: {node: '>=16.0.0'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + unimport@5.4.0: + resolution: {integrity: sha512-g/OLFZR2mEfqbC6NC9b2225eCJGvufxq34mj6kM3OmI5gdSL0qyqtnv+9qmsGpAmnzSl6x0IWZj4W+8j2hLkMA==} + engines: {node: '>=18.12.0'} + + unplugin-auto-import@20.2.0: + resolution: {integrity: sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': ^4.0.0 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + + unplugin-utils@0.3.0: + resolution: {integrity: sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==} + engines: {node: '>=20.19.0'} + + unplugin-vue-components@29.1.0: + resolution: {integrity: sha512-z/9ACPXth199s9aCTCdKZAhe5QGOpvzJYP+Hkd0GN1/PpAmsu+W3UlRY3BJAewPqQxh5xi56+Og6mfiCV1Jzpg==} + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 || ^4.0.0 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + + unplugin@2.3.10: + resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} + engines: {node: '>=18.12.0'} + + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + + vite@7.1.7: + resolution: {integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-router@4.5.1: + resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==} + peerDependencies: + vue: ^3.2.0 + + vue-tsc@3.0.8: + resolution: {integrity: sha512-H9yg/m6ywykmWS+pIAEs65v2FrVm5uOA0a0dHkX6Sx8dNg1a1m4iudt/6eGa9fAenmNHGlLFN9XpWQb8i5sU1w==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.22: + resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + ws@7.4.6: + resolution: {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 + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bufbuild/protobuf@2.9.0': {} + + '@ctrl/tinycolor@3.6.1': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.22(typescript@5.8.3))': + dependencies: + vue: 3.5.22(typescript@5.8.3) + + '@esbuild/aix-ppc64@0.25.10': + optional: true + + '@esbuild/android-arm64@0.25.10': + optional: true + + '@esbuild/android-arm@0.25.10': + optional: true + + '@esbuild/android-x64@0.25.10': + optional: true + + '@esbuild/darwin-arm64@0.25.10': + optional: true + + '@esbuild/darwin-x64@0.25.10': + optional: true + + '@esbuild/freebsd-arm64@0.25.10': + optional: true + + '@esbuild/freebsd-x64@0.25.10': + optional: true + + '@esbuild/linux-arm64@0.25.10': + optional: true + + '@esbuild/linux-arm@0.25.10': + optional: true + + '@esbuild/linux-ia32@0.25.10': + optional: true + + '@esbuild/linux-loong64@0.25.10': + optional: true + + '@esbuild/linux-mips64el@0.25.10': + optional: true + + '@esbuild/linux-ppc64@0.25.10': + optional: true + + '@esbuild/linux-riscv64@0.25.10': + optional: true + + '@esbuild/linux-s390x@0.25.10': + optional: true + + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': + optional: true + + '@esbuild/netbsd-x64@0.25.10': + optional: true + + '@esbuild/openbsd-arm64@0.25.10': + optional: true + + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': + optional: true + + '@esbuild/sunos-x64@0.25.10': + optional: true + + '@esbuild/win32-arm64@0.25.10': + optional: true + + '@esbuild/win32-ia32@0.25.10': + optional: true + + '@esbuild/win32-x64@0.25.10': + optional: true + + '@ethersproject/abi@5.4.1': + dependencies: + '@ethersproject/address': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/constants': 5.4.0 + '@ethersproject/hash': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + '@ethersproject/strings': 5.4.0 + + '@ethersproject/abstract-provider@5.4.1': + dependencies: + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/networks': 5.4.2 + '@ethersproject/properties': 5.4.1 + '@ethersproject/transactions': 5.4.0 + '@ethersproject/web': 5.4.0 + + '@ethersproject/abstract-signer@5.4.1': + dependencies: + '@ethersproject/abstract-provider': 5.4.1 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + + '@ethersproject/address@5.4.0': + dependencies: + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/rlp': 5.4.0 + + '@ethersproject/base64@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + + '@ethersproject/basex@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/properties': 5.4.1 + + '@ethersproject/bignumber@5.4.2': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + bn.js: 4.12.2 + + '@ethersproject/bytes@5.4.0': + dependencies: + '@ethersproject/logger': 5.4.1 + + '@ethersproject/constants@5.4.0': + dependencies: + '@ethersproject/bignumber': 5.4.2 + + '@ethersproject/contracts@5.4.1': + dependencies: + '@ethersproject/abi': 5.4.1 + '@ethersproject/abstract-provider': 5.4.1 + '@ethersproject/abstract-signer': 5.4.1 + '@ethersproject/address': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/constants': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + '@ethersproject/transactions': 5.4.0 + + '@ethersproject/hash@5.4.0': + dependencies: + '@ethersproject/abstract-signer': 5.4.1 + '@ethersproject/address': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + '@ethersproject/strings': 5.4.0 + + '@ethersproject/hdnode@5.4.0': + dependencies: + '@ethersproject/abstract-signer': 5.4.1 + '@ethersproject/basex': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/pbkdf2': 5.4.0 + '@ethersproject/properties': 5.4.1 + '@ethersproject/sha2': 5.4.0 + '@ethersproject/signing-key': 5.4.0 + '@ethersproject/strings': 5.4.0 + '@ethersproject/transactions': 5.4.0 + '@ethersproject/wordlists': 5.4.0 + + '@ethersproject/json-wallets@5.4.0': + dependencies: + '@ethersproject/abstract-signer': 5.4.1 + '@ethersproject/address': 5.4.0 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/hdnode': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/pbkdf2': 5.4.0 + '@ethersproject/properties': 5.4.1 + '@ethersproject/random': 5.4.0 + '@ethersproject/strings': 5.4.0 + '@ethersproject/transactions': 5.4.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + + '@ethersproject/keccak256@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + js-sha3: 0.5.7 + + '@ethersproject/logger@5.4.1': {} + + '@ethersproject/networks@5.4.2': + dependencies: + '@ethersproject/logger': 5.4.1 + + '@ethersproject/pbkdf2@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/sha2': 5.4.0 + + '@ethersproject/properties@5.4.1': + dependencies: + '@ethersproject/logger': 5.4.1 + + '@ethersproject/providers@5.4.5': + dependencies: + '@ethersproject/abstract-provider': 5.4.1 + '@ethersproject/abstract-signer': 5.4.1 + '@ethersproject/address': 5.4.0 + '@ethersproject/basex': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/constants': 5.4.0 + '@ethersproject/hash': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/networks': 5.4.2 + '@ethersproject/properties': 5.4.1 + '@ethersproject/random': 5.4.0 + '@ethersproject/rlp': 5.4.0 + '@ethersproject/sha2': 5.4.0 + '@ethersproject/strings': 5.4.0 + '@ethersproject/transactions': 5.4.0 + '@ethersproject/web': 5.4.0 + bech32: 1.1.4 + ws: 7.4.6 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ethersproject/random@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + + '@ethersproject/rlp@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + + '@ethersproject/sha2@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + hash.js: 1.1.7 + + '@ethersproject/signing-key@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + bn.js: 4.12.2 + elliptic: 6.5.4 + hash.js: 1.1.7 + + '@ethersproject/solidity@5.4.0': + dependencies: + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/sha2': 5.4.0 + '@ethersproject/strings': 5.4.0 + + '@ethersproject/strings@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/constants': 5.4.0 + '@ethersproject/logger': 5.4.1 + + '@ethersproject/transactions@5.4.0': + dependencies: + '@ethersproject/address': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/constants': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + '@ethersproject/rlp': 5.4.0 + '@ethersproject/signing-key': 5.4.0 + + '@ethersproject/units@5.4.0': + dependencies: + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/constants': 5.4.0 + '@ethersproject/logger': 5.4.1 + + '@ethersproject/wallet@5.4.0': + dependencies: + '@ethersproject/abstract-provider': 5.4.1 + '@ethersproject/abstract-signer': 5.4.1 + '@ethersproject/address': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/hash': 5.4.0 + '@ethersproject/hdnode': 5.4.0 + '@ethersproject/json-wallets': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + '@ethersproject/random': 5.4.0 + '@ethersproject/signing-key': 5.4.0 + '@ethersproject/transactions': 5.4.0 + '@ethersproject/wordlists': 5.4.0 + + '@ethersproject/web@5.4.0': + dependencies: + '@ethersproject/base64': 5.4.0 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + '@ethersproject/strings': 5.4.0 + + '@ethersproject/wordlists@5.4.0': + dependencies: + '@ethersproject/bytes': 5.4.0 + '@ethersproject/hash': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/properties': 5.4.1 + '@ethersproject/strings': 5.4.0 + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rollup/rollup-android-arm-eabi@4.52.3': + optional: true + + '@rollup/rollup-android-arm64@4.52.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.3': + optional: true + + '@rollup/rollup-darwin-x64@4.52.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.3': + optional: true + + '@sxzz/popperjs-es@2.11.7': {} + + '@types/decimal.js@7.4.3': + dependencies: + decimal.js: 10.6.0 + + '@types/estree@1.0.8': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.20 + + '@types/lodash@4.17.20': {} + + '@types/web-bluetooth@0.0.16': {} + + '@vitejs/plugin-vue@6.0.1(vite@7.1.7(sass-embedded@1.93.2)(sass@1.93.2))(vue@3.5.22(typescript@5.8.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.1.7(sass-embedded@1.93.2)(sass@1.93.2) + vue: 3.5.22(typescript@5.8.3) + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/shared': 3.5.22 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.22': + dependencies: + '@vue/compiler-core': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/compiler-sfc@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/compiler-core': 3.5.22 + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + estree-walker: 2.0.2 + magic-string: 0.30.19 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.22': + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.4': {} + + '@vue/devtools-api@7.7.7': + dependencies: + '@vue/devtools-kit': 7.7.7 + + '@vue/devtools-kit@7.7.7': + dependencies: + '@vue/devtools-shared': 7.7.7 + birpc: 2.6.1 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.3 + + '@vue/devtools-shared@7.7.7': + dependencies: + rfdc: 1.4.1 + + '@vue/language-core@3.0.8(typescript@5.8.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.22 + alien-signals: 2.0.8 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + optionalDependencies: + typescript: 5.8.3 + + '@vue/reactivity@3.5.22': + dependencies: + '@vue/shared': 3.5.22 + + '@vue/runtime-core@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/runtime-dom@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/runtime-core': 3.5.22 + '@vue/shared': 3.5.22 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.22(vue@3.5.22(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + vue: 3.5.22(typescript@5.8.3) + + '@vue/shared@3.5.22': {} + + '@vue/tsconfig@0.8.1(typescript@5.8.3)(vue@3.5.22(typescript@5.8.3))': + optionalDependencies: + typescript: 5.8.3 + vue: 3.5.22(typescript@5.8.3) + + '@vueuse/core@9.13.0(vue@3.5.22(typescript@5.8.3))': + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.13.0 + '@vueuse/shared': 9.13.0(vue@3.5.22(typescript@5.8.3)) + vue-demi: 0.14.10(vue@3.5.22(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@9.13.0': {} + + '@vueuse/shared@9.13.0(vue@3.5.22(typescript@5.8.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.22(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + acorn@8.15.0: {} + + aes-js@3.0.0: {} + + alien-signals@2.0.8: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + axios@1.12.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + bech32@1.1.4: {} + + binary-extensions@2.3.0: {} + + birpc@2.6.1: {} + + bn.js@4.12.2: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brorand@1.1.0: {} + + buffer-builder@0.2.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + optional: true + + colorjs.io@0.5.2: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + csstype@3.1.3: {} + + dayjs@1.11.18: {} + + de-indent@1.0.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + delayed-stream@1.0.0: {} + + detect-libc@1.0.3: + optional: true + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + element-plus@2.11.4(vue@3.5.22(typescript@5.8.3)): + dependencies: + '@ctrl/tinycolor': 3.6.1 + '@element-plus/icons-vue': 2.3.2(vue@3.5.22(typescript@5.8.3)) + '@floating-ui/dom': 1.7.4 + '@popperjs/core': '@sxzz/popperjs-es@2.11.7' + '@types/lodash': 4.17.20 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 9.13.0(vue@3.5.22(typescript@5.8.3)) + async-validator: 4.2.5 + dayjs: 1.11.18 + escape-html: 1.0.3 + lodash: 4.17.21 + lodash-es: 4.17.21 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.22(typescript@5.8.3) + transitivePeerDependencies: + - '@vue/composition-api' + + elliptic@6.5.4: + dependencies: + bn.js: 4.12.2 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + entities@4.5.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + + escape-html@1.0.3: {} + + escape-string-regexp@5.0.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + ethers@5.4.7: + dependencies: + '@ethersproject/abi': 5.4.1 + '@ethersproject/abstract-provider': 5.4.1 + '@ethersproject/abstract-signer': 5.4.1 + '@ethersproject/address': 5.4.0 + '@ethersproject/base64': 5.4.0 + '@ethersproject/basex': 5.4.0 + '@ethersproject/bignumber': 5.4.2 + '@ethersproject/bytes': 5.4.0 + '@ethersproject/constants': 5.4.0 + '@ethersproject/contracts': 5.4.1 + '@ethersproject/hash': 5.4.0 + '@ethersproject/hdnode': 5.4.0 + '@ethersproject/json-wallets': 5.4.0 + '@ethersproject/keccak256': 5.4.0 + '@ethersproject/logger': 5.4.1 + '@ethersproject/networks': 5.4.2 + '@ethersproject/pbkdf2': 5.4.0 + '@ethersproject/properties': 5.4.1 + '@ethersproject/providers': 5.4.5 + '@ethersproject/random': 5.4.0 + '@ethersproject/rlp': 5.4.0 + '@ethersproject/sha2': 5.4.0 + '@ethersproject/signing-key': 5.4.0 + '@ethersproject/solidity': 5.4.0 + '@ethersproject/strings': 5.4.0 + '@ethersproject/transactions': 5.4.0 + '@ethersproject/units': 5.4.0 + '@ethersproject/wallet': 5.4.0 + '@ethersproject/web': 5.4.0 + '@ethersproject/wordlists': 5.4.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + exsolve@1.0.7: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + hookable@5.5.3: {} + + immutable@5.1.3: {} + + inherits@2.0.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-what@5.5.0: {} + + js-sha3@0.5.7: {} + + js-tokens@9.0.1: {} + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + lodash-es@4.17.21: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.17.21 + lodash-es: 4.17.21 + + lodash@4.17.21: {} + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + memoize-one@6.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + optional: true + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} + + mitt@3.0.1: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + node-addon-api@7.1.1: + optional: true + + normalize-path@3.0.0: {} + + normalize-wheel-es@1.2.0: {} + + path-browserify@1.0.1: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pinia@3.0.3(typescript@5.8.3)(vue@3.5.22(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 7.7.7 + vue: 3.5.22(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-from-env@1.1.0: {} + + quansync@0.2.11: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: + optional: true + + rfdc@1.4.1: {} + + rollup@4.52.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.3 + '@rollup/rollup-android-arm64': 4.52.3 + '@rollup/rollup-darwin-arm64': 4.52.3 + '@rollup/rollup-darwin-x64': 4.52.3 + '@rollup/rollup-freebsd-arm64': 4.52.3 + '@rollup/rollup-freebsd-x64': 4.52.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.3 + '@rollup/rollup-linux-arm-musleabihf': 4.52.3 + '@rollup/rollup-linux-arm64-gnu': 4.52.3 + '@rollup/rollup-linux-arm64-musl': 4.52.3 + '@rollup/rollup-linux-loong64-gnu': 4.52.3 + '@rollup/rollup-linux-ppc64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-musl': 4.52.3 + '@rollup/rollup-linux-s390x-gnu': 4.52.3 + '@rollup/rollup-linux-x64-gnu': 4.52.3 + '@rollup/rollup-linux-x64-musl': 4.52.3 + '@rollup/rollup-openharmony-arm64': 4.52.3 + '@rollup/rollup-win32-arm64-msvc': 4.52.3 + '@rollup/rollup-win32-ia32-msvc': 4.52.3 + '@rollup/rollup-win32-x64-gnu': 4.52.3 + '@rollup/rollup-win32-x64-msvc': 4.52.3 + fsevents: 2.3.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + sass-embedded-all-unknown@1.93.2: + dependencies: + sass: 1.93.2 + optional: true + + sass-embedded-android-arm64@1.93.2: + optional: true + + sass-embedded-android-arm@1.93.2: + optional: true + + sass-embedded-android-riscv64@1.93.2: + optional: true + + sass-embedded-android-x64@1.93.2: + optional: true + + sass-embedded-darwin-arm64@1.93.2: + optional: true + + sass-embedded-darwin-x64@1.93.2: + optional: true + + sass-embedded-linux-arm64@1.93.2: + optional: true + + sass-embedded-linux-arm@1.93.2: + optional: true + + sass-embedded-linux-musl-arm64@1.93.2: + optional: true + + sass-embedded-linux-musl-arm@1.93.2: + optional: true + + sass-embedded-linux-musl-riscv64@1.93.2: + optional: true + + sass-embedded-linux-musl-x64@1.93.2: + optional: true + + sass-embedded-linux-riscv64@1.93.2: + optional: true + + sass-embedded-linux-x64@1.93.2: + optional: true + + sass-embedded-unknown-all@1.93.2: + dependencies: + sass: 1.93.2 + optional: true + + sass-embedded-win32-arm64@1.93.2: + optional: true + + sass-embedded-win32-x64@1.93.2: + optional: true + + sass-embedded@1.93.2: + dependencies: + '@bufbuild/protobuf': 2.9.0 + buffer-builder: 0.2.0 + colorjs.io: 0.5.2 + immutable: 5.1.3 + rxjs: 7.8.2 + supports-color: 8.1.1 + sync-child-process: 1.0.2 + varint: 6.0.0 + optionalDependencies: + sass-embedded-all-unknown: 1.93.2 + sass-embedded-android-arm: 1.93.2 + sass-embedded-android-arm64: 1.93.2 + sass-embedded-android-riscv64: 1.93.2 + sass-embedded-android-x64: 1.93.2 + sass-embedded-darwin-arm64: 1.93.2 + sass-embedded-darwin-x64: 1.93.2 + sass-embedded-linux-arm: 1.93.2 + sass-embedded-linux-arm64: 1.93.2 + sass-embedded-linux-musl-arm: 1.93.2 + sass-embedded-linux-musl-arm64: 1.93.2 + sass-embedded-linux-musl-riscv64: 1.93.2 + sass-embedded-linux-musl-x64: 1.93.2 + sass-embedded-linux-riscv64: 1.93.2 + sass-embedded-linux-x64: 1.93.2 + sass-embedded-unknown-all: 1.93.2 + sass-embedded-win32-arm64: 1.93.2 + sass-embedded-win32-x64: 1.93.2 + + sass@1.93.2: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.3 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + optional: true + + scrypt-js@3.0.1: {} + + scule@1.3.0: {} + + source-map-js@1.2.1: {} + + speakingurl@14.0.1: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + superjson@2.2.3: + dependencies: + copy-anything: 4.0.5 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + sync-child-process@1.0.2: + dependencies: + sync-message-port: 1.1.3 + + sync-message-port@1.1.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tslib@2.8.1: {} + + typescript@5.8.3: {} + + ufo@1.6.1: {} + + unimport@5.4.0: + dependencies: + acorn: 8.15.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.1.2 + magic-string: 0.30.19 + mlly: 1.8.0 + pathe: 2.0.3 + picomatch: 4.0.3 + pkg-types: 2.3.0 + scule: 1.3.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.15 + unplugin: 2.3.10 + unplugin-utils: 0.3.0 + + unplugin-auto-import@20.2.0(@vueuse/core@9.13.0(vue@3.5.22(typescript@5.8.3))): + dependencies: + local-pkg: 1.1.2 + magic-string: 0.30.19 + picomatch: 4.0.3 + unimport: 5.4.0 + unplugin: 2.3.10 + unplugin-utils: 0.3.0 + optionalDependencies: + '@vueuse/core': 9.13.0(vue@3.5.22(typescript@5.8.3)) + + unplugin-utils@0.3.0: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + unplugin-vue-components@29.1.0(@babel/parser@7.28.4)(vue@3.5.22(typescript@5.8.3)): + dependencies: + chokidar: 3.6.0 + debug: 4.4.3 + local-pkg: 1.1.2 + magic-string: 0.30.19 + mlly: 1.8.0 + tinyglobby: 0.2.15 + unplugin: 2.3.10 + unplugin-utils: 0.3.0 + vue: 3.5.22(typescript@5.8.3) + optionalDependencies: + '@babel/parser': 7.28.4 + transitivePeerDependencies: + - supports-color + + unplugin@2.3.10: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + varint@6.0.0: {} + + vite@7.1.7(sass-embedded@1.93.2)(sass@1.93.2): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.3 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + sass: 1.93.2 + sass-embedded: 1.93.2 + + vscode-uri@3.1.0: {} + + vue-demi@0.14.10(vue@3.5.22(typescript@5.8.3)): + dependencies: + vue: 3.5.22(typescript@5.8.3) + + vue-router@4.5.1(vue@3.5.22(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.22(typescript@5.8.3) + + vue-tsc@3.0.8(typescript@5.8.3): + dependencies: + '@volar/typescript': 2.4.23 + '@vue/language-core': 3.0.8(typescript@5.8.3) + typescript: 5.8.3 + + vue@3.5.22(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-sfc': 3.5.22 + '@vue/runtime-dom': 3.5.22 + '@vue/server-renderer': 3.5.22(vue@3.5.22(typescript@5.8.3)) + '@vue/shared': 3.5.22 + optionalDependencies: + typescript: 5.8.3 + + webpack-virtual-modules@0.6.2: {} + + ws@7.4.6: {} diff --git a/tools/Blockchain/EtherView2/frontend/public/vite.svg b/tools/Blockchain/EtherView2/frontend/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/App.vue b/tools/Blockchain/EtherView2/frontend/src/App.vue new file mode 100644 index 000000000..878ee70fc --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/App.vue @@ -0,0 +1,68 @@ + + + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/api/index.ts b/tools/Blockchain/EtherView2/frontend/src/api/index.ts new file mode 100644 index 000000000..2a34b440d --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/api/index.ts @@ -0,0 +1,48 @@ +import request from '@/utils/request' + +export enum URL { + ACCOUNTS_URL = '/get_accounts', + TXS_URL = '/tx', + Web3Url_URL = '/get_web3_url', + ETHERSCAN_URL = '/etherscan', + TX_FEES_URL = '/tx/fees', + TOTAL_ETH__URL = '/get_web3_total_eth', +} + +export const reqGetAccounts = () => { + return request.get( + URL.ACCOUNTS_URL, + ) +} + + +export const reqGetTXs = (params) => { + return request.get( + URL.TXS_URL, + {params} + ) +} + +export const reqGetWeb3Url = () => { + return request.get( + URL.Web3Url_URL, + ) +} + +export const reqGetEtherScan = () => { + return request.get( + URL.ETHERSCAN_URL, + ) +} + +export const reqGetTxFees = () => { + return request.get( + URL.TX_FEES_URL, + ) +} + +export const reqGetTotalETH = () => { + return request.get( + URL.TOTAL_ETH__URL, + ) +} \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/assets/seed.png b/tools/Blockchain/EtherView2/frontend/src/assets/seed.png new file mode 100644 index 000000000..0ebd667b6 Binary files /dev/null and b/tools/Blockchain/EtherView2/frontend/src/assets/seed.png differ diff --git a/tools/Blockchain/EtherView2/frontend/src/assets/vue.svg b/tools/Blockchain/EtherView2/frontend/src/assets/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/components/Pagination/index.vue b/tools/Blockchain/EtherView2/frontend/src/components/Pagination/index.vue new file mode 100644 index 000000000..c2a4e3d6d --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/components/Pagination/index.vue @@ -0,0 +1,80 @@ + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/main.ts b/tools/Blockchain/EtherView2/frontend/src/main.ts new file mode 100644 index 000000000..426d84aae --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/main.ts @@ -0,0 +1,11 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import '@/style/reset.scss' +import 'element-plus/dist/index.css'; +import pinia from '@/store'; + +const app = createApp(App) +app.use(router) +app.use(pinia) +app.mount('#app') \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/router/index.ts b/tools/Blockchain/EtherView2/frontend/src/router/index.ts new file mode 100644 index 000000000..01edbe0f6 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/router/index.ts @@ -0,0 +1,120 @@ +import {createRouter, createWebHistory} from 'vue-router' +import {useGlobalStore} from "@/store" +import {ElNotification} from "element-plus"; + +const routes = [ + { + path: '/', + redirect: {name: 'Home'}, + children: [ + { + path: '/home', + name: 'Home', + component: () => import('@/views/HomeView.vue'), + meta: { + title: 'Home' + } + }, + { + path: '/blockchain', + name: 'blockchain', + children: [ + { + path: '/blockchain/tx', + name: 'transactions', + component: () => import('@/views/blockchain/TXView.vue'), + meta: { + title: 'Transactions' + } + }, + { + path: '/blockchain/ptx', + name: 'pending transactions', + component: () => import('@/views/blockchain/PTXView.vue'), + meta: { + title: 'Pending Transactions' + } + }, + { + path: '/blockchain/citx', + name: 'contract internal transactions', + component: () => import('@/views/blockchain/CITXView.vue'), + meta: { + title: 'Contract Internal Transactions' + } + }, + { + path: '/blockchain/blocks', + name: 'blocks', + component: () => import('@/views/blockchain/BlocksView.vue'), + meta: { + title: 'Blocks' + } + }, + { + path: '/blockchain/accounts', + name: 'accounts', + component: () => import('@/views/blockchain/AccountsView.vue'), + meta: { + title: 'Accounts' + } + }, + { + path: '/blockchain/block/:id', + name: 'blockInfo', + component: () => import('@/views/blockchain/BlockInfoView.vue'), + meta: { + title: 'Block Info' + } + }, + { + path: '/blockchain/tx/:id', + name: 'txInfo', + component: () => import('@/views/blockchain/TxInfoView.vue'), + meta: { + title: 'Transactions Info' + } + }, + ] + } + ] + }, + { + path: '/:pathMatch(.*)*', + component: () => import('@/views/NotFoundView.vue'), + name: '404', + meta: { + title: '404', + icon: 'Failed' + } + } +] + +const router = createRouter({ + history: createWebHistory(import.meta.env.VITE_URL_PREFIX), //url的基础路径 + routes +}) + +router.beforeEach(async (to, from, next) => { + let globalStore = useGlobalStore() + document.title = to.meta.title || 'EtherView' + + if (globalStore.web3Url) { + next() + } else { + try { + await globalStore.getWeb3Url() + } catch (err) { + setTimeout(() => { + ElNotification({ + type: 'error', + message: 'getWeb3Url error' + } as any) + }, 1000) + } finally { + next({...to, replace: true}) + } + } +}) + +export default router \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/store/index.ts b/tools/Blockchain/EtherView2/frontend/src/store/index.ts new file mode 100644 index 000000000..e501e8241 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/store/index.ts @@ -0,0 +1,26 @@ +import {createPinia, defineStore} from 'pinia' +import {reqGetWeb3Url} from '@/api/index'; + + +export const useGlobalStore = defineStore('Global', { + state: () => { + return { + web3Url: localStorage.getItem('web3Url') + } + }, + actions: { + async getWeb3Url() { + let res = await reqGetWeb3Url() + if (res.status) { + this.web3Url = res.data + localStorage.setItem('web3Url', res.data) + } else { + return Promise.reject(new Error(res.message)) + } + }, + }, + getters: {} +}) + +let pinia = createPinia() +export default pinia \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/style/blockchain/index.scss b/tools/Blockchain/EtherView2/frontend/src/style/blockchain/index.scss new file mode 100644 index 000000000..61148c49c --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/style/blockchain/index.scss @@ -0,0 +1,5 @@ +$t-height: 300px; + +:export { + tHeight: $t-height +} \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/style/element/index.scss b/tools/Blockchain/EtherView2/frontend/src/style/element/index.scss new file mode 100644 index 000000000..9810d5c28 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/style/element/index.scss @@ -0,0 +1,20 @@ +/* src/styles/element/index.scss */ +@use "element-plus/theme-chalk/src/common/var.scss" as *; + +/* 下面覆盖 Element Plus 的主题变量 */ +$colors: ( + "primary": ( + "base": #ef913a, // 主色 + ), + "success": ( + "base": #67c23a, + ) +); + +/* 其他自定义变量 */ +$border-radius-base: 6px; + +$el-table-width: 100%; +$el-table-min-height: 400px; +$el-table-max-height: 500px; +$el-table-margin-bottom: 20px; \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/style/home.module.scss b/tools/Blockchain/EtherView2/frontend/src/style/home.module.scss new file mode 100644 index 000000000..61148c49c --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/style/home.module.scss @@ -0,0 +1,5 @@ +$t-height: 300px; + +:export { + tHeight: $t-height +} \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/style/reset.scss b/tools/Blockchain/EtherView2/frontend/src/style/reset.scss new file mode 100644 index 000000000..70535823d --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/style/reset.scss @@ -0,0 +1,564 @@ +/** + * Modern CSS Reset Tweaks + * ================================================== */ +html { + -webkit-text-size-adjust: 100%; +} + +html:focus-within { + scroll-behavior: smooth; +} + +body { + text-size-adjust: 100%; + position: relative; + width: 100%; + min-height: 100vh; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeSpeed; +} + +/* Box sizing normalization */ +*, +::after, +::before { + box-sizing: border-box; +} + +/* Elements that don't have a class get default styles */ +a:not([class]) { + text-decoration-skip-ink: auto; +} + +/** + * CSS Reset Tweaks + * + * http://meyerweb.com/eric/tools/css/reset/ + * v2.0-modified | 20110126 + * License: none (public domain) + */ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + //font-size: 100%; + //font: inherit; + margin: 0; + padding: 0; + border: 0; + vertical-align: baseline; +} + +/* make sure to set some focus styles for accessibility */ +:focus { + outline: 0; +} + +/* HTML5 display-role reset for older browsers */ +main, +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} + +ol, +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, +q:after { + content: ""; + content: none; +} + +/** + * Input Reset + */ +input:required, +input { + box-shadow: none; +} + +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +input:-webkit-autofill:active { + -webkit-box-shadow: 0 0 0 30px white inset; +} + +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration, +input[type=search]::-webkit-search-results-button, +input[type=search]::-webkit-search-results-decoration { + -webkit-appearance: none; + -moz-appearance: none; +} + +input[type=search] { + -webkit-appearance: none; + -moz-appearance: none; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +textarea { + overflow: auto; + vertical-align: top; + resize: vertical; +} + +input:focus { + outline: none; +} + +/** + * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. + */ +audio, +canvas, +video { + display: inline-block; + max-width: 100%; +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. + */ +[hidden] { + display: none; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ +a:active, +a:hover { + outline: none; +} + +/* Make images easier to work with */ +img { + max-width: 100%; + display: inline-block; + vertical-align: middle; + height: auto; +} + +/* Make pictures easier to work with */ +picture { + display: inline-block; +} + +/** + * Address Firefox 3+ setting `Line-height` on `input` using `!important` in + * the UA stylesheet. + */ +button, +input { + line-height: normal; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. + * Correct `select` style inheritance in Firefox 4+ and Opera. + */ +button, +select { + text-transform: none; +} + +button, +html input[type=button], +input[type=reset], +input[type=submit] { + -webkit-appearance: button; + cursor: pointer; + border: 0; + background: transparent; +} + +/** + * Re-set default cursor for disabled elements. + */ +button[disabled], +html input[disabled] { + cursor: default; +} + +[disabled] { + pointer-events: none; +} + +/** + * 1. Address box sizing set to content-box in IE 8/9. + */ +input[type=checkbox], +input[type=radio] { + padding: 0; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ +input[type=search] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Remove inner padding and border in Firefox 3+. + */ +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +button { + border: 0; + background: transparent; +} + +textarea { + overflow: auto; + vertical-align: top; + resize: vertical; +} + +/** + * Remove most spacing between table cells. + */ +table { + border-collapse: collapse; + border-spacing: 0; + text-indent: 0; +} + +/** + * Based on normalize.css v8.0.1 + * github.com/necolas/normalize.css + */ +hr { + box-sizing: content-box; + overflow: visible; + background: #000; + border: 0; + height: 1px; + line-height: 0; + margin: 0; + padding: 0; + page-break-after: always; + width: 100%; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + */ +pre { + font-family: monospace, monospace; + font-size: 100%; +} + +/** + * Remove the gray background on active links in IE 10. + */ +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ +abbr[title] { + border-bottom: none; + text-decoration: none; +} + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; +} + +/** + * Add the correct font size in all browsers. + */ +small { + font-size: 75%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the Line height in + * all browsers. + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -5px; +} + +sup { + top: -5px; +} + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1; + margin: 0; + padding: 0; +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ +button, +input { + /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ +button, +select { + /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ +button, +[type=button], +[type=reset], +[type=submit] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ +button::-moz-focus-inner, +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner { + border-style: none; + padding: 0; + outline: 0; +} + +legend { + color: inherit; + white-space: normal; + display: block; + border: 0; + max-width: 100%; + width: 100%; +} + +fieldset { + min-width: 0; +} + +body:not(:-moz-handler-blocked) fieldset { + display: block; +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ +progress { + vertical-align: baseline; +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ +[type=search] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ +[type=search]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* Interactive + ========================================================================== */ +/* + * Add the correct display in all browsers. + */ +summary { + display: list-item; +} + +/* + * Misc + * ========================================================================== */ +template { + display: none; +} \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/utils/ethersTool.ts b/tools/Blockchain/EtherView2/frontend/src/utils/ethersTool.ts new file mode 100644 index 000000000..a29a3e30a --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/utils/ethersTool.ts @@ -0,0 +1,352 @@ +import Decimal from 'decimal.js'; +import {ethers, providers} from "ethers"; +import {useGlobalStore} from "@/store" + +const globalStore = useGlobalStore() +const provider_url = globalStore.web3Url +// const provider_url = "http://10.1.101.81:8545" +// const provider_url = "http://192.168.254.128:8545" +const BATCH_SIZE = 1000 + +export function get_provider(): providers.BaseProvider { + return ethers.getDefaultProvider(provider_url); +} + +export async function get_blocks_total(provider?: providers.BaseProvider) { + provider = provider ?? get_provider(); + return await provider.getBlockNumber(); +} + + +export async function get_blocks_with_transactions( + provider?: providers.BaseProvider, + start = 0, + end = Infinity, + txn_limit = Infinity, + reverse = false, + batchSize = BATCH_SIZE +) { + provider = provider ?? get_provider(); + + const blocks: any[] = []; + const transactions: any[] = []; + const latestBlockNumber = await provider.getBlockNumber(); + + if (latestBlockNumber <= 0) return {blocks, total: latestBlockNumber}; + + // 参数合法化 + if (start < 0) start = 0; + if (end == null || end > latestBlockNumber) end = latestBlockNumber; + + // 处理 reverse 场景 + if (reverse) { + const _end = latestBlockNumber - start; + start = _end - (end - start); + end = _end; + } + + // 生成需要遍历的区块号数组(从 end 往 start 递减) + const blockNumbers: number[] = []; + for (let i = end; i >= start; i--) { + blockNumbers.push(i); + } + if (batchSize > blockNumbers.length) { + batchSize = blockNumbers.length + } + + // 按 batchSize 切分并并发请求 + for (let i = 0; i < blockNumbers.length; i += batchSize) { + const slice = blockNumbers.slice(i, i + batchSize); // 本批次的区块号 + + // 并发获取每个区块(含交易) + const batchPromises = slice.map((num) => provider!.getBlockWithTransactions(num)); + const rawBlocks = await Promise.all(batchPromises); + + // 对每个区块执行 rebuildBlockData(如果该函数本身是异步的,需要再并发) + const rebuildPromises = rawBlocks.map((blk) => rebuildBlockData(provider!, blk)); + await Promise.all(rebuildPromises); + + // 将处理好的区块加入结果数组(保持原来的顺序) + blocks.push(...rawBlocks); + } + for (const blk of blocks) { + if (transactions.length > txn_limit) { + break + } + const txCount = blk.transactions.length; + const remainCount = txn_limit - transactions.length + transactions.push(...blk.transactions + .slice(Math.max(0, txCount - remainCount)) // 取最后 10 条(若不足则全部) + .reverse()) + } + + return {blocks, transactions, total: latestBlockNumber}; +} + + +export async function getPendingTxs(provider?: providers.BaseProvider) { + provider = provider ?? get_provider(); + + let allPendingTxs: any[] = []; + try { + const txpoolContent = await provider.send("txpool_content"); + for (const [address, txs] of Object.entries(txpoolContent.pending)) { + for (const [nonce, tx] of Object.entries(txs)) { + allPendingTxs.push({ + time: tx.time, + from: address, + to: tx.to, + hash: tx.hash, + nonce: parseInt(nonce), + value: ethers.utils.formatEther(tx.value), + gasPrice: ethers.utils.formatUnits(tx.gasPrice, 'gwei'), + gas: tx.gas, + input: tx.input + }); + } + } + } catch (error) { + console.error("Error fetching txpool content:", error); + } + return allPendingTxs +} + +export async function countPendingTxLastHour(provider?: providers.BaseProvider): Promise { + provider = provider ?? get_provider(); + const allPendingTxs = await getPendingTxs(provider) + let count = 0 + const now = Date.now(); + for (let tx of allPendingTxs) { + if (tx.time) { + const txTimeMs = Number(tx.time) * 1000; + if (now - txTimeMs <= 60 * 60 * 1000) { + count++; + } + } else { + count++; + } + } + + return count +} + +export async function get_transaction_info(hash: string, provider?: providers.BaseProvider) { + provider = provider ?? get_provider(); + const tx = await provider.getTransaction(hash); + if (!tx) { + console.log('未找到对应的交易'); + return {}; + } + const receipt = await provider.getTransactionReceipt(hash); + + return { + hash: tx.hash, + from: tx.from, + to: tx.to, + value: ethers.utils.formatEther(tx.value), + gasPrice: ethers.utils.formatUnits(tx.gasPrice, 'gwei') + ' gwei', + nonce: tx.nonce, + data: tx.data, + blockNumber: tx.blockNumber, + // 回执信息 + status: receipt ? (receipt.status === 1 ? 'Success' : 'Failed') : 'Pending', + gasUsed: receipt ? receipt.gasUsed.toString() : null, + cumulativeGasUsed: receipt ? receipt.cumulativeGasUsed.toString() : null, + transactionIndex: receipt ? receipt.transactionIndex : null, + } +} + +export async function get_block(provider?: providers.BaseProvider, block_number: string) { + provider = provider ?? get_provider(); + + return await provider.getBlockWithTransactions(parseInt(block_number)); +} + +export async function get_blocks_24H(provider?: providers.BaseProvider, batchSize = BATCH_SIZE, includeTxs = true): Promise { + provider = provider ?? get_provider(); + + // 1️⃣ 获取最新区块号 + const latestNumber = await provider.getBlockNumber(); + + // 2️⃣ 计算时间阈值(Unix 秒) + const now = Math.floor(Date.now() / 1000); + const cutoff = now - 24 * 60 * 60; + + // 4️⃣ 二分查找最早满足条件的区块号 + let low = 0; + let high = latestNumber; + let startNumber = latestNumber; // 默认全部满足 + + while (low <= high) { + const mid = Math.floor((low + high) / 2); + const block = await provider.getBlock(mid); // 只取时间戳即可 + if (Number(block.timestamp) < cutoff) { + low = mid + 1; + } else { + startNumber = mid; + high = mid - 1; + } + } + + const result: providers.Block[] = []; + for (let start = startNumber; start <= latestNumber; start += batchSize) { + let batchPayload = []; + for (let n = start; n < start + batchSize; n++) { + batchPayload.push({ + jsonrpc: "2.0", + id: n, + method: "eth_getBlockByNumber", + params: [ethers.utils.hexValue(n), includeTxs], + }) + } + const response = await fetch(provider_url, { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(batchPayload), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status} – ${response.statusText}`); + } + + const rawResults: any[] = await response.json(); + + // 5️⃣ 解析并再次过滤时间戳(防止节点返回的块时间略早) + for (const raw of rawResults) { + const block = raw.result as providers.Block; + if (block && Number(block.timestamp) >= cutoff) { + result.push(block); + } + } + } + + // 按区块号升序返回(可自行改为降序) + result.sort((a, b) => a.number - b.number); + return result; +} + +/** + * 1️⃣ 网络利用率 + * utilization = Σ(gasUsed) / Σ(gasLimit) + */ +export async function get_networkUtilization(blocks: ethers.providers.Block[]): Promise { + let totalUsed = ethers.BigNumber.from(0); + let totalLimit = ethers.BigNumber.from(0); + + for (const b of blocks) { + totalUsed = totalUsed.add(b.gasUsed); + totalLimit = totalLimit.add(b.gasLimit); + } + + if (totalLimit.isZero()) return "0"; + + return `${(Number(totalUsed) * 10000 / Number(totalLimit) / 100).toFixed(8)}%` +} + +export async function get_blocks_by_mevBuilders(blocks: ethers.providers.Block[], lastBlockNumber: number): Promise { + let count = 0; + for (const b of blocks) { + const proposer = (b as any).miner || (b as any).proposer; + if (proposer) { + count++; + } + } + if (lastBlockNumber === 0) { + return "0" + } + return (count * 100 / lastBlockNumber).toFixed(2) + '%'; +} + +export async function get_BurntFees(blocks: ethers.providers.Block[]): Promise { + let totalBurnWei = ethers.BigNumber.from(0); + + for (const block of blocks) { + if (block.baseFeePerGas) { + const baseFee = ethers.BigNumber.from(block.baseFeePerGas) + const burn = baseFee.mul(block.gasUsed); + totalBurnWei = totalBurnWei.add(burn); + } + } + + return `${Number(ethers.utils.formatEther(totalBurnWei)).toFixed(8)} ETH`; +} + +export async function get_GAS_price(provider?: providers.BaseProvider) { + provider = provider ?? get_provider(); + let price = await provider.getGasPrice() + + return `${Number(ethers.utils.formatUnits(price, "gwei")).toFixed(8)} Gwei` +} + +export const get_balance = async (provider?: providers.BaseProvider, address: string) => { + provider = provider ?? get_provider(); + + let balance = await provider.getBalance(address); + + let eth_balance = ethers.utils.formatEther(balance, {commify: true}); + eth_balance = (+eth_balance).toPrecision(10); + return eth_balance; +} + +export const get_nonce = async (provider?: providers.BaseProvider, address: string) => { + provider = provider ?? get_provider(); + + return await provider.getTransactionCount(address, 'pending'); +} + +async function rebuildBlockData(provider?: providers.BaseProvider, block: ethers.providers.Block) { + provider = provider ?? get_provider(); + + block.block_number = block.number; + block.txn = block.transactions.length; + block.reward = ethers.BigNumber.from(0); + block.time = block.timestamp; + block.burntFee = block.baseFeePerGas.mul(block.gasUsed); + block._baseFeePerGas = ethers.utils.formatUnits(block.baseFeePerGas, "gwei"); + block.gasUsed = block.gasUsed.toString(); + block.gasLimit = block.gasLimit.toString(); + block.time = timeTo(block.timestamp) + for (let j = 0; j < block.txn; j++) { + let hash = block.transactions[j].hash; + let tx = await provider.getTransaction(hash); + let tx_r = await provider.getTransactionReceipt(hash); + const gasPrice: ethers.BigNumber = tx.gasPrice ?? ethers.constants.Zero; // 若 undefined 则为 0 + const gasUsed: ethers.BigNumber = tx_r.gasUsed; // 已是 BigNumber + + block.reward = block.reward.add(gasPrice.mul(gasUsed)); + } + block.reward = + ethers.utils.formatUnits(block.reward.sub(block.burntFee), "ether") + + " ETH"; + block.burntFee = ethers.utils.formatUnits(block.burntFee, "ether"); +} + +export function timeTo(timestamp: number) { + let timeStr = '' + let sec = Math.floor((Date.now() - timestamp * 1000) / 1000); + if (sec < 0) { + sec = 0 + } + if (sec < 60) { + timeStr = `${sec} secs ago`; + } else { + let min = Math.floor(sec / 60); + if (min < 60) { + timeStr = `${min} mins ago`; + } else { + let hour = Math.floor(min * 10 / 60) / 10; + if (hour < 24) { + timeStr = `${hour} hours ago`; + } else { + let day = Math.floor(hour * 10 / 24) / 10; + timeStr = `${day} days ago`; + } + } + } + return timeStr +} + +export const calcMarketCap = (etherPrice: string, totalETH: Decimal): string => { + const _etherPrice = parseFloat(etherPrice.replace(/[^\d.-]/g, "")) + return `$${totalETH.times(_etherPrice).toFixed(4)}` +} \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/utils/request.ts b/tools/Blockchain/EtherView2/frontend/src/utils/request.ts new file mode 100644 index 000000000..f4df2b9c8 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/utils/request.ts @@ -0,0 +1,25 @@ +// axios 二次封装 +import axios from "axios" +import {ElMessage} from "element-plus" + + +let request = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_URL, + timeout: import.meta.env.VITE_APP_TIMEOUT, +}) + +request.interceptors.request.use((config) => { + return config +}) + +request.interceptors.response.use((response) => { + return response.data +}, (error) => { + ElMessage({ + type: 'error', + message: error.response.status, + }) + return Promise.reject(error) +}) + +export default request \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/utils/tools.ts b/tools/Blockchain/EtherView2/frontend/src/utils/tools.ts new file mode 100644 index 000000000..9be13f6a8 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/utils/tools.ts @@ -0,0 +1,10 @@ +import {ElLoading} from "element-plus"; + +export const AllLoading = () => { + return ElLoading.service({ + lock: true, + text: 'Loading…', + background: 'rgba(0,0,0,0.3)', + fullscreen: true + }) +} \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/HomeView.vue b/tools/Blockchain/EtherView2/frontend/src/views/HomeView.vue new file mode 100644 index 000000000..c83b6ac96 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/HomeView.vue @@ -0,0 +1,255 @@ + + + + + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/NotFoundView.vue b/tools/Blockchain/EtherView2/frontend/src/views/NotFoundView.vue new file mode 100644 index 000000000..63ce27f4e --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/NotFoundView.vue @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/blockchain/AccountsView.vue b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/AccountsView.vue new file mode 100644 index 000000000..a10f99b20 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/AccountsView.vue @@ -0,0 +1,165 @@ + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/blockchain/BlockInfoView.vue b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/BlockInfoView.vue new file mode 100644 index 000000000..08f3bd781 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/BlockInfoView.vue @@ -0,0 +1,75 @@ + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/blockchain/BlocksView.vue b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/BlocksView.vue new file mode 100644 index 000000000..aa819f06d --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/BlocksView.vue @@ -0,0 +1,163 @@ + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/blockchain/CITXView.vue b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/CITXView.vue new file mode 100644 index 000000000..c3da5fe17 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/CITXView.vue @@ -0,0 +1,120 @@ + + + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/blockchain/PTXView.vue b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/PTXView.vue new file mode 100644 index 000000000..1f1d2d584 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/PTXView.vue @@ -0,0 +1,129 @@ + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/blockchain/TXView.vue b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/TXView.vue new file mode 100644 index 000000000..7dfa5f5f3 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/TXView.vue @@ -0,0 +1,161 @@ + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/src/views/blockchain/TxInfoView.vue b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/TxInfoView.vue new file mode 100644 index 000000000..1ad840db7 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/src/views/blockchain/TxInfoView.vue @@ -0,0 +1,75 @@ + + + + + \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/frontend/tsconfig.app.json b/tools/Blockchain/EtherView2/frontend/tsconfig.app.json new file mode 100644 index 000000000..8d16e4255 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tools/Blockchain/EtherView2/frontend/tsconfig.json b/tools/Blockchain/EtherView2/frontend/tsconfig.json new file mode 100644 index 000000000..1ffef600d --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tools/Blockchain/EtherView2/frontend/tsconfig.node.json b/tools/Blockchain/EtherView2/frontend/tsconfig.node.json new file mode 100644 index 000000000..a1da8dd77 --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/tsconfig.node.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": [], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + + "baseUrl": "./", + "paths": { + // 映射路径,相当于baseUrl + "@/*": [ + "src/*" + ] + } + }, + "include": ["vite.config.ts"] +} diff --git a/tools/Blockchain/EtherView2/frontend/vite.config.ts b/tools/Blockchain/EtherView2/frontend/vite.config.ts new file mode 100644 index 000000000..22110fd3f --- /dev/null +++ b/tools/Blockchain/EtherView2/frontend/vite.config.ts @@ -0,0 +1,80 @@ +import {resolve} from 'path' +import vue from '@vitejs/plugin-vue' +import {loadEnv, defineConfig} from 'vite' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import {ElementPlusResolver} from 'unplugin-vue-components/resolvers' + +const envDir = '.env' +export default defineConfig(({mode}) => { + const env = loadEnv(mode, envDir) + return { + // 指定 env 文件所在目录 + envDir: envDir, + plugins: [ + vue(), + // 自动按需导入 Element Plus 组件 + Components({ + resolvers: [ElementPlusResolver()], + }), + AutoImport({ + resolvers: [ElementPlusResolver()], + // 也可以自动导入 Vue 的常用 API + imports: ['vue', 'vue-router'], + }), + ], + css: { + preprocessorOptions: { + scss: { + // 无需在组件中导入全局scss变量,可直接使用 + additionalData: `@use "@/style/element/index.scss" as *;`, + }, + }, + }, + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + // 构建配置 + build: { + outDir: '../server/static/frontend', + // 构建前自动清空 outDir(默认在根目录下时为 true) + emptyOutDir: true, + assetsDir: 'assets', + rollupOptions: { + output: { + chunkFileNames: 'assets/js/[name]-[hash].js', + entryFileNames: 'assets/js/[name]-[hash].js', + assetFileNames: (assetInfo) => { + const extType = assetInfo.name.split('.')[1] + if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) { + return 'assets/images/[name]-[hash][extname]' + } + if (/css/i.test(extType)) { + return 'assets/css/[name]-[hash][extname]' + } + return 'assets/[name]-[hash][extname]' + } + } + } + }, + // 开发服务器配置(用于开发阶段) + server: { + cors: true, + strictPort: true, // 端口被占用时直接报错,不会自动切换 + port: Number(env.VITE_APP_PORT) || 5173, + open: env.VITE_OPEN === 'true', + host: env.VITE_APP_HOST, + proxy: { + [env.VITE_APP_BASE_URL]: { + target: env.VITE_APP_SERVER, + // 是否开启跨域 + changeOrigin: true, + }, + }, + }, + // 静态资源的基础路径配置 + base: env.VITE_STATIC_ASSET_PREFIX + } +}) \ No newline at end of file diff --git a/tools/Blockchain/EtherView2/run.sh b/tools/Blockchain/EtherView2/run.sh new file mode 100644 index 000000000..8ddab43ac --- /dev/null +++ b/tools/Blockchain/EtherView2/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +FLASK_APP=server flask run + diff --git a/tools/Blockchain/EtherView2/server/__init__.py b/tools/Blockchain/EtherView2/server/__init__.py new file mode 100644 index 000000000..2e0856d2e --- /dev/null +++ b/tools/Blockchain/EtherView2/server/__init__.py @@ -0,0 +1,318 @@ +import os +import sys +import time +import json +import docker +import logging +from web3 import Web3 +from flask_cors import CORS +from flask import Flask, jsonify +from werkzeug.exceptions import HTTPException +from logging.handlers import RotatingFileHandler +from flask_apscheduler import APScheduler +from web3.middleware import geth_poa_middleware + +from .config import Config +from server.utils.models import db +from .tx_monitor import run_tx_monitor + + +def create_app(test_config=None): + app = Flask(__name__, instance_relative_config=True) + CORS(app, resources={r"/api/*": {"origins": "http://localhost:5173"}}) + # os.environ['DOCKER_HOST'] = 'tcp://192.168.254.128:2375' + # os.environ['DOCKER_HOST'] = 'tcp://10.1.101.81:2375' + client = docker.from_env() + + is_ready = 0 + containers_len = len(client.containers.list()) + while True: + print("waiting for all containers to be ready...") + time.sleep(1) + new_containers_len = len(client.containers.list()) + if containers_len == new_containers_len: + is_ready += 1 + else: + is_ready = 0 + if is_ready > 3: + break + containers_len = new_containers_len + + # Load the configuration + if test_config is None: + app.config.from_object(Config) + else: + app.config.from_mapping(test_config) + + @app.errorhandler(Exception) + def handle_all_exceptions(e): + # 如果是 HTTPException(如 404、500),可以保留原始状态码 + if isinstance(e, HTTPException): + code = e.code + description = e.description + else: + code = 500 + description = str(e) + + # 返回统一的 JSON 结构 + return jsonify({ + "status": False, + "code": code, + "message": description + }) + + # Set the global parameters using the configuration data + app.configure = {} + app.configure['eth_node_name_pattern'] = Config.ETH_NODE_NAME_PATTERN + app.configure['client_waiting_time'] = Config.CLIENT_WAITING_TIME + app.configure['key_derivation_path'] = Config.KEY_DERIVATION_PATH + app.configure['mnemonic_phrase'] = Config.MNEMONIC_PHRASE + app.configure['local_account_names'] = Config.LOCAL_ACCOUNT_NAMES + + # Load the data from the emulator + app.eth_accounts = load_eth_accounts(app.root_path) + app.eth_nodes = load_eth_nodes(app.root_path) + # Pick the first for the default web3 URL + eth_nodes_list = list(app.eth_nodes.items()) + assert len(eth_nodes_list) > 0 + app.web3_url = "http://%s:8545" % eth_nodes_list[0][1]['ip'] + # app.web3_url = 'http://192.168.254.128:8545' + # app.web3_url = 'http://10.1.101.81:8545' + print(app.web3_url) + + setup_logging(app) + setup_db(app) + setup_scheduler(app) + + # Get the consensus from the emulator + app.consensus = get_eth_consensus() + + from server.blueprint import RegexConverter + from server.blueprint.api.views import api + from server.blueprint.base.views import base + + app.url_map.converters['regex'] = RegexConverter + app.register_blueprint(base) + app.register_blueprint(api) + + return app + + +# 基础日志配置 +def setup_logging(app): + app.logger.setLevel(app.config.get('LOG_LEVEL', logging.INFO)) + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + if not os.path.exists('logs'): + os.mkdir('logs') + + file_handler = RotatingFileHandler( + filename=app.config.get('LOG_FILE', 'logs/server.log'), + maxBytes=10240, + backupCount=10 + ) + file_handler.setFormatter(formatter) + app.logger.addHandler(file_handler) + + logging.getLogger('werkzeug').setLevel(logging.WARNING) + + +def setup_db(app): + db.init_app(app) + with app.app_context(): + db.create_all() + + +def setup_scheduler(app): + scheduler = APScheduler() + scheduler.start() + scheduler.add_job( + id="tx_monitor", + func=run_tx_monitor, + trigger='date', + args=[app] + ) + + +# Load all the accounts from the emulator +def load_eth_accounts(root_path): + path = os.path.join(root_path, "emulator_data") + filename = os.path.join(path, "accounts.json") + + if os.path.exists(filename) is False: # the file does not exist + getEmulatorAccounts(path, "accounts.json") + + with open(filename) as json_file: + eth_accounts = json.load(json_file) + + counters = {} + new_eth_accounts = {} + for address in eth_accounts: + account = eth_accounts[address] + # if self._chain_id != int(account['chain_id']): + # continue + + # Name might be duplicate, should we deal with it? + name = account['name'] + if name not in counters: # name already exists + counters[name] = 0 + else: + counters[name] += 1 + name = name + "-%d" % counters[name] + + new_eth_accounts[address] = {"name": name, + "chain_id": account['chain_id']} + + return new_eth_accounts + + +# Get the ethereum nodes info: container name and ID +def load_eth_nodes(root_path): + path = os.path.join(root_path, "emulator_data") + filename = os.path.join(path, "containers.json") + if os.path.exists(filename) is False: # the file does not exist + getContainerInfo(path, "containers.json") + + with open(filename) as json_file: + eth_nodes = json.load(json_file) + + return eth_nodes + + +# Cache the emulator account information in a file. +def getEmulatorAccounts(path, filename): + os.system("mkdir -p {}".format(path)) + + client = docker.from_env() + all_containers = client.containers.list() + + mapping = {} + counters = {} + for container in all_containers: + labels = container.attrs['Config']['Labels'] + if 'EthereumService' in labels.get('org.seedsecuritylabs.seedemu.meta.class', []): + chain_id = labels.get('org.seedsecuritylabs.seedemu.meta.ethereum.chain_id') + + # record which container each key file comes from + # cmd = ['docker', 'exec', container.short_id, 'ls', '-1', '/root/.ethereum/keystore'] + exit_code, output = container.exec_run('ls /root/.ethereum/keystore') + keyfilenames = output.decode("utf-8").rstrip().split('\n') + for keyfilename in keyfilenames: + keyfile = '/root/.ethereum/keystore/' + keyfilename + keyname = labels.get('org.seedsecuritylabs.seedemu.meta.displayname') + + print("Getting the key file from %s" % keyname) + # cmd = ['docker', 'exec', container.short_id, 'cat', keyfile] + exit_code, output = container.exec_run('cat {}'.format(keyfile)) + encrypted_key = output.decode("utf-8").rstrip().split('\n')[0] + account = json.loads(encrypted_key) + address = Web3.toChecksumAddress(account["address"]) + mapping[address] = { + 'name': keyname, + 'chain_id': chain_id + } + + save_to_file = os.path.join(path, filename) + with open(save_to_file, 'w') as json_file: + json.dump(mapping, json_file, indent=4) + + return + + +# Cache the container information in a file. +def getContainerInfo(path, filename): + os.system("mkdir -p {}".format(path)) + + client = docker.from_env() + all_containers = client.containers.list() + + mapping_all = {} + for container in all_containers: + labels = container.attrs['Config']['Labels'] + if 'EthereumService' in labels.get('org.seedsecuritylabs.seedemu.meta.class', []): + info_map = {} + info_map["container_id"] = container.short_id + info_map["displayname"] = labels.get("org.seedsecuritylabs.seedemu.meta.displayname") + ip = labels.get("org.seedsecuritylabs.seedemu.meta.net.0.address") + info_map["ip"] = ip.replace("/24", "") # remove the network mask + info_map["node_id"] = labels.get("org.seedsecuritylabs.seedemu.meta.ethereum.node_id") + info_map["chain_id"] = labels.get("org.seedsecuritylabs.seedemu.meta.ethereum.chain_id") + info_map["node_role"] = labels.get("org.seedsecuritylabs.seedemu.meta.ethereum.role") + info_map["consensus"] = labels.get("org.seedsecuritylabs.seedemu.meta.ethereum.consensus") + + mapping_all[container.name] = info_map + + save_to_file = os.path.join(path, filename) + with open(save_to_file, 'w') as json_file: + json.dump(mapping_all, json_file, indent=4) + + return + + +# Get the consensus type from the emulator +def get_eth_consensus(): + client = docker.from_env() + all_containers = client.containers.list() + + for container in all_containers: + labels = container.attrs['Config']['Labels'] + if 'EthereumService' in labels.get('org.seedsecuritylabs.seedemu.meta.class', []): + return labels.get("org.seedsecuritylabs.seedemu.meta.ethereum.consensus") + + +# Load the timestamp of genesis block +def load_genesis_time(root_path): + path = os.path.join(root_path, "emulator_data") + filename = os.path.join(path, "genesis_timestamp.json") + + if os.path.exists(filename) is False: # the file does not exist + return -1 + + with open(filename) as json_file: + timestamp = json.load(json_file) + + return timestamp + + +# Get the timestamp of genesis block +def get_genesis_time(web3_url, consensus): + web3 = connect_to_geth(web3_url, consensus) + return web3.eth.getBlock(0).timestamp + + +# Connect to a geth node +def connect_to_geth(url, consensus): + if consensus == 'POA': + return connect_to_geth_poa(url) + elif consensus == 'POS': + return connect_to_geth_pos(url) + elif consensus == 'POW': + return connect_to_geth_pow(url) + + +# Connect to a geth node +def connect_to_geth_pos(url): + web3 = Web3(Web3.HTTPProvider(url)) + if not web3.isConnected(): + sys.exit("Connection failed!") + web3.middleware_onion.inject(geth_poa_middleware, layer=0) + + return web3 + + +# Connect to a geth node +def connect_to_geth_poa(url): + web3 = Web3(Web3.HTTPProvider(url)) + if not web3.isConnected(): + sys.exit("Connection failed!") + web3.middleware_onion.inject(geth_poa_middleware, layer=0) + return web3 + + +# Connect to a geth node +def connect_to_geth_pow(url): + web3 = Web3(Web3.HTTPProvider(url)) + if not web3.isConnected(): + sys.exit("Connection failed!") + return web3 diff --git a/tools/Blockchain/EtherView2/server/blueprint/__init__.py b/tools/Blockchain/EtherView2/server/blueprint/__init__.py new file mode 100644 index 000000000..d8480fb2c --- /dev/null +++ b/tools/Blockchain/EtherView2/server/blueprint/__init__.py @@ -0,0 +1,31 @@ +import os +from werkzeug.routing import BaseConverter +from flask import jsonify, send_from_directory, current_app + +from ..config import Config + + +def serve_vue_app(path=None): + """服务 Vue 应用的主入口""" + try: + # 首先尝试直接返回请求的文件 + if path and path != 'index.html': + # 检查文件是否存在 + file_path = os.path.join(Config.FRONTEND_DIST_DIR, path) + if os.path.isfile(file_path): + return send_from_directory(Config.FRONTEND_DIST_DIR, path) + + # 对于前端路由,返回 Vue 的 index.html + return send_from_directory(Config.FRONTEND_DIST_DIR, 'index.html') + except Exception as e: + current_app.logger.error(f"Error serving Vue app: {e}") + return jsonify({"error": "Frontend application not available"}), 500 + + +class RegexConverter(BaseConverter): + """在路由中使用正则表达式的转换器""" + + def __init__(self, url_map, regex): + super().__init__(url_map) + self.regex = regex # 正则表达式字符串 + diff --git a/tools/Blockchain/EtherView2/server/blueprint/api/__init__.py b/tools/Blockchain/EtherView2/server/blueprint/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/Blockchain/EtherView2/server/blueprint/api/etherscan/__init__.py b/tools/Blockchain/EtherView2/server/blueprint/api/etherscan/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/Blockchain/EtherView2/server/blueprint/api/etherscan/views.py b/tools/Blockchain/EtherView2/server/blueprint/api/etherscan/views.py new file mode 100644 index 000000000..b7152b543 --- /dev/null +++ b/tools/Blockchain/EtherView2/server/blueprint/api/etherscan/views.py @@ -0,0 +1,11 @@ +from flask import jsonify, Blueprint +from server.utils.etherscan import EtherScan + +etherscan = Blueprint('etherscan', __name__, url_prefix='/etherscan') + + +@etherscan.route('/') +async def get_ether_price(): + home_data = await EtherScan.get_home_data() + data = home_data + return jsonify({"data": data, "status": True}) diff --git a/tools/Blockchain/EtherView2/server/blueprint/api/transaction/__init__.py b/tools/Blockchain/EtherView2/server/blueprint/api/transaction/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/Blockchain/EtherView2/server/blueprint/api/transaction/views.py b/tools/Blockchain/EtherView2/server/blueprint/api/transaction/views.py new file mode 100644 index 000000000..7cb1f54d8 --- /dev/null +++ b/tools/Blockchain/EtherView2/server/blueprint/api/transaction/views.py @@ -0,0 +1,44 @@ +from web3 import Web3 +from datetime import datetime, timedelta +from flask import jsonify, Blueprint, request +from server.utils.models import Transaction + +tx = Blueprint('tx', __name__, url_prefix='/tx') + + +@tx.route('/') +def get_txs(): + page, page_size = int(request.args.get('page', 1)), int(request.args.get('page_size', 50)) + txs = Transaction.query.order_by(Transaction.timestamp.desc()).paginate(page=page, per_page=page_size, + error_out=False) + data = { + 'total': txs.total, + 'txs': [tx.to_dict() for tx in txs.items], + } + return jsonify({"data": data, "status": True}) + + +@tx.route('/fees') +def get_tx_fees(): + now = datetime.utcnow() + hour_ago = now - timedelta(hours=24) + + txs = Transaction.query.with_entities( + Transaction.gasUsed, Transaction.gasPrice + ).filter(Transaction.timestamp >= hour_ago).all() + if txs: + total_fee_wei = sum([tx.gasUsed * tx.gasPrice for tx in txs]) + total_fee_eth = Web3.fromWei(total_fee_wei, 'ether') + data = { + 'total': len(txs), + 'totalFee': f"{total_fee_eth:.8f} ETH", + 'avgFee': f"{(total_fee_eth / len(txs)):.8f} ETH", + } + else: + data = { + 'total': 0, + 'totalFee': "0 ETH", + 'avgFee': "0 Gwei", + } + + return jsonify({"data": data, "status": True}) diff --git a/tools/Blockchain/EtherView2/server/blueprint/api/views.py b/tools/Blockchain/EtherView2/server/blueprint/api/views.py new file mode 100644 index 000000000..0d45aa936 --- /dev/null +++ b/tools/Blockchain/EtherView2/server/blueprint/api/views.py @@ -0,0 +1,68 @@ +from web3 import Web3 +from flask import jsonify, Blueprint, current_app +from eth_account import Account +from .transaction.views import tx +from .etherscan.views import etherscan + +api = Blueprint('api', __name__, url_prefix='/api') +api.register_blueprint(tx) +api.register_blueprint(etherscan) + + +@api.route('/data') +def api_data(): + return jsonify({"message": "Hello from Flask API!", "status": "success"}) + + +@api.route('/get_accounts') +def get_accounts(): + accounts = [] + + # Get the accounts from the emulator + for address in current_app.eth_accounts: + item = current_app.eth_accounts[address] + accounts.append({"address": address, + "name": item["name"], + "type": "emulator"}) + + # Generate local accounts using the mnemonic phrase. + Account.enable_unaudited_hdwallet_features() + local_account_names = current_app.configure['local_account_names'] + for index in range(len(local_account_names)): + account = Account.from_mnemonic(current_app.configure['mnemonic_phrase'], + account_path=current_app.configure['key_derivation_path'].format(index)) + accounts.append({"address": account.address, + "name": local_account_names[index], + "type": "local"}) + + return accounts + + +@api.route('/get_web3_providers') +def get_web3_providers(): + providers = [] + for key in current_app.eth_nodes: + node = current_app.eth_nodes[key] + providers.append("http://%s:8545" % node['ip']) + + return providers + + +@api.route('/get_web3_url') +def get_web3_url(): + return jsonify({"data": current_app.web3_url, "status": True}) + + +@api.route('/get_web3_total_eth') +def get_web3_total_eth(): + total_wei = 0 + accounts = get_accounts() + + w3 = Web3(Web3.HTTPProvider(current_app.web3_url)) + if not w3.isConnected(): + return str(total_wei) + + for acct in accounts: + total_wei += w3.eth.get_balance(acct['address']) + total_eth = w3.fromWei(total_wei, 'ether') + return f'{total_eth:.8f}' diff --git a/tools/Blockchain/EtherView2/server/blueprint/base/__init__.py b/tools/Blockchain/EtherView2/server/blueprint/base/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/Blockchain/EtherView2/server/blueprint/base/views.py b/tools/Blockchain/EtherView2/server/blueprint/base/views.py new file mode 100644 index 000000000..cf46a4f9b --- /dev/null +++ b/tools/Blockchain/EtherView2/server/blueprint/base/views.py @@ -0,0 +1,37 @@ +import os +from flask import Blueprint, send_from_directory, redirect, current_app as app +from server.config import Config +from server.blueprint import serve_vue_app + +base = Blueprint('base', __name__) + + +# 静态资源路由 +@base.route(f'{Config.STATIC_ASSET_PREFIX}/assets/') +def serve_assets(filename): + """处理 Vue 应用的静态资源""" + return send_from_directory(os.path.join(Config.FRONTEND_DIST_DIR, 'assets'), filename) + + +# 其他静态文件路由(如 favicon, manifest 等) +@base.route(f'{Config.STATIC_ASSET_PREFIX}/') +def serve_root_files(filename): + """处理根目录下的静态文件""" + if '.' in filename: + return send_from_directory(Config.FRONTEND_DIST_DIR, filename) + return serve_vue_app() + + +@base.route(Config.URL_PREFIX, defaults={'path': ''}) +@base.route(f'{Config.URL_PREFIX}/') +def serve_vue_static_path(path): + """处理 /frontend/ 下的路径""" + return serve_vue_app(path) + + +# 捕获所有未匹配路由(必须放在最后) +@base.route('/', defaults={'path': ''}) +def catch_all(path): + print(f'未匹配的路径: /{path}', 404) + # return serve_vue_app(path) + return redirect(Config.URL_PREFIX) diff --git a/tools/Blockchain/EtherView2/server/config.py b/tools/Blockchain/EtherView2/server/config.py new file mode 100644 index 000000000..6b2198673 --- /dev/null +++ b/tools/Blockchain/EtherView2/server/config.py @@ -0,0 +1,38 @@ +import logging +from urllib.parse import quote_plus + + +class Config(object): + NAME = 'SEED Labs' + CONSENSUS = 'POA' + DEFAULT_URL = 'http://10.154.0.72:8545' + ETH_NODE_NAME_PATTERN = 'Ethereum-POS' + DEFAULT_CHAIN_ID = 1337 + CLIENT_WAITING_TIME = 10 # seconds + + KEY_DERIVATION_PATH = "m/44'/60'/0'/0/{}" + MNEMONIC_PHRASE = "great amazing fun seed lab protect network system " \ + "security prevent attack future" + LOCAL_ACCOUNT_NAMES = ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'] + + # frontend + URL_PREFIX = '/frontend' + STATIC_ASSET_PREFIX = '/static' + FRONTEND_DIST_DIR = 'static/frontend' + + # mysql + DB_HOST = 'localhost' + DB_PORT = 3306 + DB_USER = 'root' + DB_PASSWORD = 'Root@123#' + DB_NAME = 'eth_monitor' + # 使用 Flask‑SQLAlchemy 时的 URI 写法 + SQLALCHEMY_DATABASE_URI = ( + f'mysql+pymysql://{DB_USER}:{quote_plus(DB_PASSWORD)}@{DB_HOST}:{DB_PORT}/{DB_NAME}' + ) + # 关闭 SQLAlchemy 的事件系统(可选,提升性能) + SQLALCHEMY_TRACK_MODIFICATIONS = False + + # log + LOG_LEVEL = logging.INFO + LOG_FILE = 'logs/server.log' diff --git a/tools/Blockchain/EtherView2/server/tx_monitor.py b/tools/Blockchain/EtherView2/server/tx_monitor.py new file mode 100644 index 000000000..d07478734 --- /dev/null +++ b/tools/Blockchain/EtherView2/server/tx_monitor.py @@ -0,0 +1,143 @@ +import time +import logging +from web3 import Web3 +from server.utils.models import db + + +class SimpleTransactionMonitor: + def __init__(self, websocket_url, mysql_config, logger=None): + self.w3 = Web3(Web3.HTTPProvider(websocket_url)) + self.mysql_config = mysql_config + self.conn = db.engine.raw_connection() + self._setup_logging(logger) + self.init_data_total = 100 + + def _setup_logging(self, logger): + if logger is None: + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' + ) + self.logging = logging + else: + self.logging = logger + + def _save_transaction(self, data_list): + """保存交易到数据库""" + try: + with self.conn.cursor() as cursor: + cursor.executemany(""" + INSERT INTO transaction + (tx_hash, block_number, status, from_address, to_address, value, nonce, gasPriceGwei, gasPrice, gasUsed, timestamp) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + block_number = VALUES(block_number), + value = VALUES(value), + nonce = VALUES(nonce), + gasPriceGwei = VALUES(gasPriceGwei), + timestamp = VALUES(timestamp), + gasPrice = VALUES(gasPrice), + gasUsed = VALUES(gasUsed), + status = VALUES(status); + """, data_list) + self.conn.commit() + self.logging.debug(f"save success: {data_list}") + self.logging.info("save success") + except Exception as e: + self.logging.error(f"save failed : {e}") + + def start_monitoring(self): + """开始监控""" + if not self.w3.isConnected(): + self.logging.error("connect web3 failed") + return + + self.logging.info("start_monitoring...") + last_block = self.w3.eth.blockNumber + self._init_data(last_block) + try: + while True: + current_block = self.w3.eth.blockNumber + while current_block > last_block: + try: + last_block += 1 + self.logging.info(f"deal new block #{last_block}") + txs = self._get_tx_by_block_number(current_block) + if not txs: + continue + self._save_transaction(txs) + except Exception as e: + self.logging.info(f"transaction error: {e}") + time.sleep(2) + except Exception as e: + self.logging.info(f"monitor error: {e}") + finally: + if self.conn: + self.conn.close() + + def _get_tx_by_block_number(self, block_number): + # 获取区块详情 + block = self.w3.eth.getBlock(block_number, full_transactions=True) + txs = [] + # 处理区块中的每笔交易 + for tx in block.transactions: + tx_hash = tx.hash.hex() + # 获取交易状态 + receipt = self.w3.eth.getTransactionReceipt(tx.hash) + if receipt: + status = "success" if receipt.status == 1 else "failed" + else: + status = "pending" + # 准备交易数据 + tx_data = { + 'block_number': tx.blockNumber, + 'timestamp': block.timestamp, + 'from': tx['from'], + 'to': tx['to'], + 'nonce': tx.nonce, + 'value': float(self.w3.fromWei(tx.value, 'ether')), + 'gasPriceGwei': float(self.w3.fromWei(tx.gasPrice, 'gwei')), + 'gasPrice': tx.gasPrice, + 'gasUsed': receipt.gasUsed, + } + txs.append( + (tx_hash, + tx_data['block_number'], + status, + tx_data['from'], + tx_data['to'], + tx_data['value'], + tx_data['nonce'], + tx_data['gasPriceGwei'], + tx_data['gasPrice'], + tx_data['gasUsed'], + tx_data['timestamp']) + ) + return txs + + def _init_data(self, last_block_number): + start = last_block_number - self.init_data_total + if start < 0: + start = 0 + for i in range(start, last_block_number + 1): + txs = self._get_tx_by_block_number(i) + if not txs: + continue + self._save_transaction(txs) + + +def run_tx_monitor(app): + with app.app_context(): + monitor = SimpleTransactionMonitor( + websocket_url=app.web3_url, + mysql_config={ + 'host': app.config.get('DB_HOST', 'localhost'), + 'port': app.config.get('DB_PORT', 3306), + 'user': app.config.get('DB_USER', 'root'), + 'password': app.config.get('DB_PASSWORD', 'Root@123#'), + 'database': app.config.get('DB_DATABASE', 'eth_monitor'), + 'charset': 'utf8mb4' + }, + logger=app.logger + ) + monitor.start_monitoring() diff --git a/tools/Blockchain/EtherView2/server/utils/etherscan.py b/tools/Blockchain/EtherView2/server/utils/etherscan.py new file mode 100644 index 000000000..ad27e27db --- /dev/null +++ b/tools/Blockchain/EtherView2/server/utils/etherscan.py @@ -0,0 +1,33 @@ +import re +import aiohttp +import asyncio +import requests + + +class EtherScan(object): + @staticmethod + async def get_home_data(): + url = 'https://goto.etherscan.com/' + ether_price = gas_price = market_cap = -1 + headers = { + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', + } + try: + async with aiohttp.ClientSession(headers=headers) as session: + async with session.get(url) as resp: + resp.raise_for_status() # 若状态码非 2xx,会抛异常 + html_text = await resp.text() + pattern = re.compile( + r"(.*?).*?(.*?).*?(.*?)", + re.S) + ret = re.findall(pattern, html_text) + if ret: + ether_price, gas_price, market_cap = ret[0][0], f"{ret[0][1]} Gwei", ret[0][2] + except Exception as e: + print(e) + + return { + 'etherPrice': ether_price, + 'gasPrice': gas_price, + 'marketCap': market_cap, + } diff --git a/tools/Blockchain/EtherView2/server/utils/models.py b/tools/Blockchain/EtherView2/server/utils/models.py new file mode 100644 index 000000000..fcb3b65a9 --- /dev/null +++ b/tools/Blockchain/EtherView2/server/utils/models.py @@ -0,0 +1,88 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() # 绑定 SQLAlchemy + + +class Transaction(db.Model): + __tablename__ = "transaction" # 表名 + __table_args__ = {"mysql_engine": "InnoDB"} + + id = db.Column( + db.Integer, + primary_key=True, + autoincrement=True, + comment="自增主键" + ) + tx_hash = db.Column( + db.String(66), + unique=True, + nullable=False, + comment="交易哈希(唯一)" + ) + block_number = db.Column( + db.BigInteger, + nullable=True, + comment="区块高度" + ) + status = db.Column( + db.String(20), + nullable=True, + comment="交易状态" + ) + from_address = db.Column( + db.String(42), + nullable=True, + comment="交易From" + ) + to_address = db.Column( + db.String(42), + nullable=True, + comment="交易To" + ) + value = db.Column( + db.Numeric(30, 18), + nullable=False, + comment="Value" + ) + nonce = db.Column( + db.Integer, + nullable=False, + comment="nonce" + ) + gasPriceGwei = db.Column( + db.Numeric(20, 9), + nullable=True, + comment='Gas价格(Gwei单位)' + ) + gasUsed = db.Column(db.BigInteger, nullable=True) + gasPrice = db.Column(db.BigInteger, nullable=True) + timestamp = db.Column( + db.BigInteger, + nullable=False, + comment='区块时间戳' + ) + created_time = db.Column( + db.DateTime, + server_default=db.func.current_timestamp(), + nullable=False, + comment="记录创建时间" + ) + + def __repr__(self): + return f"" + + def to_dict(self): + """基础序列化方法""" + return { + 'id': self.id, + 'hash': self.tx_hash, + 'block_number': self.block_number, + 'from': self.from_address, + 'to': self.to_address, + 'nonce': self.nonce, + 'value': float(self.value) if self.value else 0.0, + 'gasPriceGwei': float(self.gasPriceGwei) if self.gasPriceGwei else None, + 'status': self.status, + 'timestamp': self.timestamp, + 'created_time': self.created_time, + } diff --git a/tools/Blockchain/EtherView2/start.sh b/tools/Blockchain/EtherView2/start.sh new file mode 100644 index 000000000..c8f0995cf --- /dev/null +++ b/tools/Blockchain/EtherView2/start.sh @@ -0,0 +1,2 @@ +#!/bin/sh +while true; do FLASK_APP=server flask run --host=0.0.0.0 --port=3000; done