From dd976c88a7b9f3d9ffb2d9e35ec79474efbae7d6 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Sun, 22 Feb 2026 18:36:29 +0300 Subject: [PATCH] feat: add WalletConnect v2 integration for mobile wallet connection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add @walletconnect/sign-client for dApp-side WC v2 support - Create walletconnect-service.ts with session management and Signer adapter - Create WalletConnectModal.tsx with QR code display - Update PezkuwiContext with connectWalletConnect(), session restore, walletSource tracking - Update WalletContext signing router: mobile → WalletConnect → extension - Update WalletModal with "Connect with pezWallet (Mobile)" button - Move WalletConnect projectId to env variable - Fix vite build: disable assetsInclude for JSON (crypto-browserify compat) --- .github/workflows/quality-gate.yml | 1 + web/.env.example | 6 + web/package-lock.json | 1155 ++++++++++++++++- web/package.json | 2 + .../components/wallet/WalletConnectModal.tsx | 158 +++ web/src/components/wallet/WalletModal.tsx | 49 +- web/src/contexts/PezkuwiContext.tsx | 134 +- web/src/contexts/WalletContext.tsx | 64 +- web/src/lib/walletconnect-service.ts | 292 +++++ web/vite.config.ts | 2 +- 10 files changed, 1825 insertions(+), 38 deletions(-) create mode 100644 web/src/components/wallet/WalletConnectModal.tsx create mode 100644 web/src/lib/walletconnect-service.ts diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml index 2762f53b..ac00a1c6 100644 --- a/.github/workflows/quality-gate.yml +++ b/.github/workflows/quality-gate.yml @@ -67,6 +67,7 @@ jobs: VITE_WS_ENDPOINT_FALLBACK_1: wss://mainnet.pezkuwichain.io VITE_ASSET_HUB_ENDPOINT: wss://asset-hub-rpc.pezkuwichain.io VITE_PEOPLE_CHAIN_ENDPOINT: wss://people-rpc.pezkuwichain.io + VITE_WALLETCONNECT_PROJECT_ID: 8292a793b7640e8364c378e331e76d04 - name: Upload build artifact uses: actions/upload-artifact@v4 diff --git a/web/.env.example b/web/.env.example index 435c2f19..790a8aa4 100644 --- a/web/.env.example +++ b/web/.env.example @@ -94,6 +94,12 @@ VITE_ENABLE_P2P_MARKET=true VITE_ENABLE_GOVERNANCE=true VITE_ENABLE_STAKING=true +# ======================================== +# WALLETCONNECT +# ======================================== +# Get from: https://cloud.walletconnect.com (Reown dashboard) +VITE_WALLETCONNECT_PROJECT_ID=your_walletconnect_project_id_here + # ======================================== # DEVELOPMENT & DEBUGGING # ======================================== diff --git a/web/package-lock.json b/web/package-lock.json index a58d52c5..25f1346e 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "vite_react_shadcn_ts", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vite_react_shadcn_ts", - "version": "0.0.0", + "version": "1.0.0", "dependencies": { "@hookform/resolvers": "^3.9.0", "@pezkuwi/api": "^16.5.36", @@ -48,6 +48,8 @@ "@types/dompurify": "^3.0.5", "@types/react-syntax-highlighter": "^15.5.13", "@types/uuid": "^10.0.0", + "@walletconnect/sign-client": "^2.23.6", + "@walletconnect/types": "^2.23.6", "buffer": "^6.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -121,6 +123,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -1131,6 +1139,27 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@msgpack/msgpack": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz", + "integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==", + "license": "ISC", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/curves": { "version": "1.9.7", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", @@ -3842,6 +3871,33 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sentry-internal/browser-utils": { "version": "10.38.0", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.38.0.tgz", @@ -5026,6 +5082,775 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@walletconnect/core": { + "version": "2.23.6", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.23.6.tgz", + "integrity": "sha512-rAV0Hgd/uPzeo2nWv2MZfGaP5diniKZICMVATGwzrsgGqDMeC1WYRkX3hXnIGJhMF4mhZxjaKFHcms03bqm/jw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "3.0.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.23.6", + "@walletconnect/utils": "2.23.6", + "@walletconnect/window-getters": "1.0.1", + "es-toolkit": "1.44.0", + "events": "3.3.0", + "uint8arrays": "3.1.1" + }, + "engines": { + "node": ">=18.20.8" + } + }, + "node_modules/@walletconnect/core/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/core/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/core/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/core/node_modules/unstorage": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/environment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.1.tgz", + "integrity": "sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/environment/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/events": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/events/-/events-1.0.1.tgz", + "integrity": "sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==", + "license": "MIT", + "dependencies": { + "keyvaluestorage-interface": "^1.0.0", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/events/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/heartbeat": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@walletconnect/heartbeat/-/heartbeat-1.2.2.tgz", + "integrity": "sha512-uASiRmC5MwhuRuf05vq4AT48Pq8RMi876zV8rr8cV969uTOzWdB/k+Lj5yI2PBtB1bGQisGen7MM1GcZlQTBXw==", + "license": "MIT", + "dependencies": { + "@walletconnect/events": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-provider": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", + "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.8", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-types": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz", + "integrity": "sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ==", + "license": "MIT", + "dependencies": { + "events": "^3.3.0", + "keyvaluestorage-interface": "^1.0.0" + } + }, + "node_modules/@walletconnect/jsonrpc-utils": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz", + "integrity": "sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw==", + "license": "MIT", + "dependencies": { + "@walletconnect/environment": "^1.0.1", + "@walletconnect/jsonrpc-types": "^1.0.3", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/jsonrpc-utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/jsonrpc-ws-connection": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.16.tgz", + "integrity": "sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0", + "ws": "^7.5.1" + } + }, + "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@walletconnect/logger": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/logger/-/logger-3.0.2.tgz", + "integrity": "sha512-7wR3wAwJTOmX4gbcUZcFMov8fjftY05+5cO/d4cpDD8wDzJ+cIlKdYOXaXfxHLSYeDazMXIsxMYjHYVDfkx+nA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.2", + "pino": "10.0.0" + } + }, + "node_modules/@walletconnect/relay-api": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", + "integrity": "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-types": "^1.0.2" + } + }, + "node_modules/@walletconnect/relay-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.1.0.tgz", + "integrity": "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.0", + "@noble/hashes": "1.7.0", + "@walletconnect/safe-json": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/@walletconnect/relay-auth/node_modules/@noble/curves": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.0.tgz", + "integrity": "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/relay-auth/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/safe-json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.2.tgz", + "integrity": "sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/safe-json/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/sign-client": { + "version": "2.23.6", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.23.6.tgz", + "integrity": "sha512-cqMZFmq9ABkQOVML1EZPYbfPenI4jBIbzfsqa0A17notdRJR2Dio4GTNoA9jyLFP6JxqwaH635dc98EUFvre5w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@walletconnect/core": "2.23.6", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "3.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.23.6", + "@walletconnect/utils": "2.23.6", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/time/-/time-1.0.2.tgz", + "integrity": "sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/time/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/types": { + "version": "2.23.6", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.23.6.tgz", + "integrity": "sha512-ZoAa/TnNzq1hfh4b7J+GU5IXaYidWw469N4nSji5irPg2KQXvQWW+mhzswGrzYEzD3K/OTFzo0bdkhYVrvTTMw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "3.0.2", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/types/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/types/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/types/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/types/node_modules/unstorage": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils": { + "version": "2.23.6", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.23.6.tgz", + "integrity": "sha512-xQMwUHSkNXheUFv/b+hhzyDi6tUKLZE8AJD3vnd+oQm7LB/Sbu0MfgR5SlWMFeSqlELZeL+49q7uJdbUO3gUtg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@msgpack/msgpack": "3.1.3", + "@noble/ciphers": "1.3.0", + "@noble/curves": "1.9.7", + "@noble/hashes": "1.8.0", + "@scure/base": "1.2.6", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "3.0.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.23.6", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "blakejs": "1.2.1", + "detect-browser": "5.3.0", + "ox": "0.9.3", + "uint8arrays": "3.1.1" + } + }, + "node_modules/@walletconnect/utils/node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/utils/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/unstorage": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/@walletconnect/window-getters": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.1.tgz", + "integrity": "sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/window-getters/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/window-metadata": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz", + "integrity": "sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==", + "license": "MIT", + "dependencies": { + "@walletconnect/window-getters": "^1.0.1", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/window-metadata/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -5345,6 +6170,15 @@ "node": ">= 0.4" } }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.24", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", @@ -5457,6 +6291,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "license": "MIT" + }, "node_modules/bn.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", @@ -6041,6 +6881,12 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -6117,6 +6963,15 @@ "node": ">= 8" } }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, "node_modules/crypto-browserify": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", @@ -6512,6 +7367,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -6533,6 +7394,18 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-browser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", + "license": "MIT" + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -6905,6 +7778,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", @@ -7197,7 +8080,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -7634,6 +8516,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/h3": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -7923,6 +8822,12 @@ "node": ">=20.0.0" } }, + "node_modules/idb-keyval": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "license": "Apache-2.0" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -8031,6 +8936,15 @@ "node": ">=12" } }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -8688,6 +9602,12 @@ "json-buffer": "3.0.1" } }, + "node_modules/keyvaluestorage-interface": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", + "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8788,7 +9708,6 @@ "version": "11.2.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -8960,6 +9879,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -9058,6 +9983,18 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -9289,6 +10226,26 @@ ], "license": "MIT" }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9332,6 +10289,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ox": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.3.tgz", + "integrity": "sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9532,6 +10540,43 @@ "node": ">=0.10.0" } }, + "node_modules/pino": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.0.0.tgz", + "integrity": "sha512-eI9pKwWEix40kfvSzqEP6ldqOoBIN7dwD/o91TY5z8vQI12sAffpR/pOqAD1IVVwIVHDpHjkq0joBPdJD0rafA==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -9829,6 +10874,22 @@ "dev": true, "license": "MIT" }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9959,6 +11020,18 @@ ], "license": "MIT" }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10272,6 +11345,15 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/recharts": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", @@ -10683,6 +11765,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -10904,6 +11995,12 @@ "dev": true, "license": "ISC" }, + "node_modules/slow-redact": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", + "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", + "license": "MIT" + }, "node_modules/smoldot": { "version": "2.0.40", "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.40.tgz", @@ -10914,6 +12011,15 @@ "ws": "^8.8.1" } }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/sonner": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", @@ -10943,6 +12049,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -11319,6 +12434,15 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -11610,7 +12734,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11644,6 +12768,21 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -11663,6 +12802,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/web/package.json b/web/package.json index d7712cd5..30564425 100644 --- a/web/package.json +++ b/web/package.json @@ -60,6 +60,8 @@ "@types/dompurify": "^3.0.5", "@types/react-syntax-highlighter": "^15.5.13", "@types/uuid": "^10.0.0", + "@walletconnect/sign-client": "^2.23.6", + "@walletconnect/types": "^2.23.6", "buffer": "^6.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/web/src/components/wallet/WalletConnectModal.tsx b/web/src/components/wallet/WalletConnectModal.tsx new file mode 100644 index 00000000..a8553822 --- /dev/null +++ b/web/src/components/wallet/WalletConnectModal.tsx @@ -0,0 +1,158 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Smartphone, Loader2, CheckCircle, XCircle } from 'lucide-react'; +import QRCode from 'qrcode'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { usePezkuwi } from '@/contexts/PezkuwiContext'; + +interface WalletConnectModalProps { + isOpen: boolean; + onClose: () => void; +} + +type ConnectionState = 'generating' | 'waiting' | 'connected' | 'error'; + +export const WalletConnectModal: React.FC = ({ isOpen, onClose }) => { + const { t } = useTranslation(); + const { connectWalletConnect, selectedAccount, wcPeerName } = usePezkuwi(); + const [qrDataUrl, setQrDataUrl] = useState(''); + const [connectionState, setConnectionState] = useState('generating'); + const [errorMsg, setErrorMsg] = useState(''); + + const startConnection = useCallback(async () => { + setConnectionState('generating'); + setErrorMsg(''); + + try { + const uri = await connectWalletConnect(); + + // Generate QR code as data URL + const dataUrl = await QRCode.toDataURL(uri, { + width: 300, + margin: 2, + color: { + dark: '#000000', + light: '#ffffff', + }, + }); + + setQrDataUrl(dataUrl); + setConnectionState('waiting'); + } catch (err) { + setConnectionState('error'); + setErrorMsg(err instanceof Error ? err.message : 'Connection failed'); + } + }, [connectWalletConnect]); + + // Start connection when modal opens + useEffect(() => { + if (isOpen) { + startConnection(); + } + + return () => { + setQrDataUrl(''); + setConnectionState('generating'); + }; + }, [isOpen, startConnection]); + + // Listen for successful connection + useEffect(() => { + const handleConnected = () => { + setConnectionState('connected'); + // Auto-close after brief success display + setTimeout(() => onClose(), 1500); + }; + + window.addEventListener('walletconnect_connected', handleConnected); + return () => window.removeEventListener('walletconnect_connected', handleConnected); + }, [onClose]); + + return ( + + + + + + WalletConnect + + + {t('walletModal.wcScanQR', 'Scan with pezWallet to connect')} + + + +
+ {/* Generating state */} + {connectionState === 'generating' && ( +
+ +

+ {t('walletModal.wcGenerating', 'Generating QR code...')} +

+
+ )} + + {/* QR Code display - waiting for scan */} + {connectionState === 'waiting' && qrDataUrl && ( + <> +
+ WalletConnect QR Code +
+
+ + {t('walletModal.wcWaiting', 'Waiting for wallet to connect...')} +
+

+ {t('walletModal.wcInstructions', 'Open pezWallet app → Settings → WalletConnect → Scan QR code')} +

+ + )} + + {/* Connected state */} + {connectionState === 'connected' && ( +
+ +

+ {t('walletModal.wcConnected', 'Connected!')} +

+ {wcPeerName && ( +

{wcPeerName}

+ )} + {selectedAccount && ( + + {selectedAccount.address.slice(0, 8)}...{selectedAccount.address.slice(-6)} + + )} +
+ )} + + {/* Error state */} + {connectionState === 'error' && ( +
+ +

{errorMsg}

+ +
+ )} +
+
+
+ ); +}; diff --git a/web/src/components/wallet/WalletModal.tsx b/web/src/components/wallet/WalletModal.tsx index 33bac597..a40d0627 100644 --- a/web/src/components/wallet/WalletModal.tsx +++ b/web/src/components/wallet/WalletModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield } from 'lucide-react'; +import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield, Smartphone } from 'lucide-react'; import { Dialog, DialogContent, @@ -12,6 +12,7 @@ import { Button } from '@/components/ui/button'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; import { formatAddress } from '@pezkuwi/lib/wallet'; import { getAllScores, type UserScores } from '@pezkuwi/lib/scores'; +import { WalletConnectModal } from './WalletConnectModal'; interface WalletModalProps { isOpen: boolean; @@ -28,11 +29,14 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) => api, isApiReady, peopleApi, + walletSource, + wcPeerName, error } = usePezkuwi(); const { t } = useTranslation(); const [copied, setCopied] = useState(false); + const [showWCModal, setShowWCModal] = useState(false); const [scores, setScores] = useState({ trustScore: 0, referralScore: 0, @@ -241,8 +245,15 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) =>
{t('walletModal.source')}
-
- {selectedAccount.meta.source || 'pezkuwi'} +
+ {walletSource === 'walletconnect' ? ( + <> + + WalletConnect{wcPeerName ? ` (${wcPeerName})` : ''} + + ) : ( + selectedAccount.meta.source || 'pezkuwi' + )}
@@ -321,7 +332,7 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) => ) : ( - // No accounts, show connect button + // No accounts, show connect buttons
+
+
+ +
+
+ + {t('walletModal.or', 'or')} + +
+
+ + +
{t('walletModal.noWallet')}{' '} = ({ isOpen, onClose }) =>
)} + + {/* WalletConnect QR Modal */} + { + setShowWCModal(false); + // If connected via WC, close the main modal too + if (selectedAccount) onClose(); + }} + /> ); }; \ No newline at end of file diff --git a/web/src/contexts/PezkuwiContext.tsx b/web/src/contexts/PezkuwiContext.tsx index 3cdcf7df..4c64f223 100644 --- a/web/src/contexts/PezkuwiContext.tsx +++ b/web/src/contexts/PezkuwiContext.tsx @@ -26,6 +26,16 @@ import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types'; import { DEFAULT_ENDPOINT } from '../../../shared/blockchain/pezkuwi'; import { getCurrentNetworkConfig } from '../../../shared/blockchain/endpoints'; import { isMobileApp, getNativeWalletAddress, getNativeAccountName } from '@/lib/mobile-bridge'; +import { + initWalletConnect, + connectWithQR, + getSessionAccounts, + getSessionPeerName, + restoreSession, + disconnectWC, + isWCConnected, + createWCSigner, +} from '@/lib/walletconnect-service'; // Get network config from shared endpoints const networkConfig = getCurrentNetworkConfig(); @@ -34,6 +44,8 @@ const networkConfig = getCurrentNetworkConfig(); const ASSET_HUB_ENDPOINT = import.meta.env.VITE_ASSET_HUB_ENDPOINT || networkConfig.assetHubEndpoint || 'wss://asset-hub-rpc.pezkuwichain.io'; const PEOPLE_CHAIN_ENDPOINT = import.meta.env.VITE_PEOPLE_CHAIN_ENDPOINT || networkConfig.peopleChainEndpoint || 'wss://people-rpc.pezkuwichain.io'; +export type WalletSource = 'extension' | 'native' | 'walletconnect' | null; + interface PezkuwiContextType { api: ApiPromise | null; assetHubApi: ApiPromise | null; @@ -46,7 +58,10 @@ interface PezkuwiContextType { selectedAccount: InjectedAccountWithMeta | null; setSelectedAccount: (account: InjectedAccountWithMeta | null) => void; connectWallet: () => Promise; + connectWalletConnect: () => Promise; disconnectWallet: () => void; + walletSource: WalletSource; + wcPeerName: string | null; error: string | null; sudoKey: string | null; } @@ -72,6 +87,8 @@ export const PezkuwiProvider: React.FC = ({ const [selectedAccount, setSelectedAccount] = useState(null); const [error, setError] = useState(null); const [sudoKey, setSudoKey] = useState(null); + const [walletSource, setWalletSource] = useState(null); + const [wcPeerName, setWcPeerName] = useState(null); // Wrapper to trigger events when wallet changes const handleSetSelectedAccount = (account: InjectedAccountWithMeta | null) => { @@ -310,12 +327,44 @@ export const PezkuwiProvider: React.FC = ({ } } - // Desktop: Try to restore from localStorage + // Try to restore WalletConnect session first + try { + const wcSession = await restoreSession(); + if (wcSession) { + const wcAddresses = getSessionAccounts(); + if (wcAddresses.length > 0) { + const peerName = getSessionPeerName(); + const wcAccounts: InjectedAccountWithMeta[] = wcAddresses.map((addr) => ({ + address: addr, + meta: { + name: peerName || 'WalletConnect', + source: 'walletconnect', + }, + type: 'sr25519' as const, + })); + + setAccounts(wcAccounts); + handleSetSelectedAccount(wcAccounts[0]); + setWalletSource('walletconnect'); + setWcPeerName(peerName); + if (import.meta.env.DEV) { + console.log('✅ WalletConnect session restored:', wcAddresses[0].slice(0, 8) + '...'); + } + return; + } + } + } catch (err) { + if (import.meta.env.DEV) { + console.warn('Failed to restore WC session:', err); + } + } + + // Desktop: Try to restore from localStorage (extension) const savedAddress = localStorage.getItem('selectedWallet'); if (!savedAddress) return; try { - // Enable extension + // Enable extension (works for both desktop extension and pezWallet DApps browser) const extensions = await web3Enable('PezkuwiChain'); if (extensions.length === 0) return; @@ -328,6 +377,7 @@ export const PezkuwiProvider: React.FC = ({ if (savedAccount) { setAccounts(allAccounts); handleSetSelectedAccount(savedAccount); + setWalletSource('extension'); if (import.meta.env.DEV) { console.log('✅ Wallet restored:', savedAddress.slice(0, 8) + '...'); } @@ -361,13 +411,12 @@ export const PezkuwiProvider: React.FC = ({ try { setError(null); - // Check if running in mobile app + // Check if running in mobile app (native WebView bridge) if (isMobileApp()) { const nativeAddress = getNativeWalletAddress(); const nativeAccountName = getNativeAccountName(); if (nativeAddress) { - // Create a virtual account for the mobile wallet const mobileAccount: InjectedAccountWithMeta = { address: nativeAddress, meta: { @@ -379,30 +428,27 @@ export const PezkuwiProvider: React.FC = ({ setAccounts([mobileAccount]); handleSetSelectedAccount(mobileAccount); + setWalletSource('native'); if (import.meta.env.DEV) { console.log('[Mobile] Native wallet connected:', nativeAddress.slice(0, 8) + '...'); } return; } else { - // Request wallet connection from native app setError('Please connect your wallet in the app'); return; } } - // Desktop: Check if extension is installed first + // Desktop / pezWallet DApps browser: Try extension (injected provider) const hasExtension = !!(window as unknown as { injectedWeb3?: Record }).injectedWeb3; - // Enable extension const extensions = await web3Enable('PezkuwiChain'); if (extensions.length === 0) { if (hasExtension) { - // Extension is installed but user didn't authorize - don't redirect setError('Please authorize the connection in your Pezkuwi Wallet extension'); } else { - // Extension not installed - show install link setError('Pezkuwi Wallet extension not found. Please install from Chrome Web Store.'); window.open('https://chrome.google.com/webstore/detail/pezkuwi-wallet/fbnboicjjeebjhgnapneaeccpgjcdibn', '_blank'); } @@ -413,7 +459,6 @@ export const PezkuwiProvider: React.FC = ({ console.log('✅ Pezkuwi.js extension enabled'); } - // Get accounts const allAccounts = await web3Accounts(); if (allAccounts.length === 0) { @@ -423,14 +468,13 @@ export const PezkuwiProvider: React.FC = ({ setAccounts(allAccounts); - // Try to restore previously selected account, otherwise use first const savedAddress = localStorage.getItem('selectedWallet'); const accountToSelect = savedAddress ? allAccounts.find(acc => acc.address === savedAddress) || allAccounts[0] : allAccounts[0]; - // Use wrapper to trigger events handleSetSelectedAccount(accountToSelect); + setWalletSource('extension'); if (import.meta.env.DEV) { console.log(`✅ Found ${allAccounts.length} account(s)`); @@ -444,12 +488,67 @@ export const PezkuwiProvider: React.FC = ({ } }; - // Disconnect wallet - const disconnectWallet = () => { + // Connect via WalletConnect v2 - returns pairing URI for QR code + const connectWalletConnect = async (): Promise => { + if (!api || !isApiReady) { + throw new Error('API not ready. Please wait for blockchain connection.'); + } + + setError(null); + const genesisHash = api.genesisHash.toHex(); + + try { + await initWalletConnect(); + const { uri, approval } = await connectWithQR(genesisHash); + + // Start approval listener in background + approval().then((session) => { + const wcAddresses = getSessionAccounts(); + if (wcAddresses.length > 0) { + const peerName = getSessionPeerName(); + const wcAccounts: InjectedAccountWithMeta[] = wcAddresses.map((addr) => ({ + address: addr, + meta: { + name: peerName || 'WalletConnect', + source: 'walletconnect', + }, + type: 'sr25519' as const, + })); + + setAccounts(wcAccounts); + handleSetSelectedAccount(wcAccounts[0]); + setWalletSource('walletconnect'); + setWcPeerName(peerName); + window.dispatchEvent(new Event('walletconnect_connected')); + + if (import.meta.env.DEV) { + console.log('✅ WalletConnect session established:', session.topic); + } + } + }).catch((err) => { + if (import.meta.env.DEV) console.error('WalletConnect approval failed:', err); + setError('WalletConnect connection was rejected'); + }); + + return uri; + } catch (err) { + if (import.meta.env.DEV) console.error('WalletConnect connection failed:', err); + setError('Failed to start WalletConnect'); + throw err; + } + }; + + // Disconnect wallet (extension, native, or WalletConnect) + const disconnectWallet = async () => { + if (walletSource === 'walletconnect') { + await disconnectWC(); + setWcPeerName(null); + } setAccounts([]); handleSetSelectedAccount(null); + setWalletSource(null); if (import.meta.env.DEV) { - if (import.meta.env.DEV) console.log('🔌 Wallet disconnected'); + console.log('🔌 Wallet disconnected'); } }; @@ -460,12 +559,15 @@ export const PezkuwiProvider: React.FC = ({ isApiReady, isAssetHubReady, isPeopleReady, - isConnected: isApiReady, // Alias for backward compatibility + isConnected: isApiReady, accounts, selectedAccount, setSelectedAccount: handleSetSelectedAccount, connectWallet, + connectWalletConnect, disconnectWallet, + walletSource, + wcPeerName, error, sudoKey, }; diff --git a/web/src/contexts/WalletContext.tsx b/web/src/contexts/WalletContext.tsx index cec9b764..8b0d0789 100644 --- a/web/src/contexts/WalletContext.tsx +++ b/web/src/contexts/WalletContext.tsx @@ -11,6 +11,7 @@ import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types'; import type { Signer } from '@pezkuwi/api/types'; import { web3FromAddress } from '@pezkuwi/extension-dapp'; import { isMobileApp, signTransactionNative, type TransactionPayload } from '@/lib/mobile-bridge'; +import { createWCSigner, isWCConnected } from '@/lib/walletconnect-service'; interface TokenBalances { HEZ: string; @@ -255,11 +256,25 @@ export const WalletProvider: React.FC<{ children: React.ReactNode }> = ({ childr return blockHash; } - // Desktop: Use browser extension for signing + // WalletConnect: Use WC signer + if (pezkuwi.walletSource === 'walletconnect' && isWCConnected() && pezkuwi.api) { + if (import.meta.env.DEV) console.log('[WC] Using WalletConnect for transaction signing'); + + const genesisHash = pezkuwi.api.genesisHash.toHex(); + const wcSigner = createWCSigner(genesisHash, pezkuwi.selectedAccount.address); + + const hash = await (tx as { signAndSend: (address: string, options: { signer: unknown }) => Promise<{ toHex: () => string }> }).signAndSend( + pezkuwi.selectedAccount.address, + { signer: wcSigner } + ); + + return hash.toHex(); + } + + // Desktop / pezWallet DApps browser: Use extension signer const { web3FromAddress } = await import('@pezkuwi/extension-dapp'); const injector = await web3FromAddress(pezkuwi.selectedAccount.address); - // Sign and send transaction const hash = await (tx as { signAndSend: (address: string, options: { signer: unknown }) => Promise<{ toHex: () => string }> }).signAndSend( pezkuwi.selectedAccount.address, { signer: injector.signer } @@ -279,6 +294,23 @@ export const WalletProvider: React.FC<{ children: React.ReactNode }> = ({ childr } try { + // WalletConnect signing + if (pezkuwi.walletSource === 'walletconnect' && isWCConnected() && pezkuwi.api) { + if (import.meta.env.DEV) console.log('[WC] Using WalletConnect for message signing'); + + const genesisHash = pezkuwi.api.genesisHash.toHex(); + const wcSigner = createWCSigner(genesisHash, pezkuwi.selectedAccount.address); + + const { signature } = await wcSigner.signRaw({ + address: pezkuwi.selectedAccount.address, + data: message, + type: 'bytes', + }); + + return signature; + } + + // Extension signing const { web3FromAddress } = await import('@pezkuwi/extension-dapp'); const injector = await web3FromAddress(pezkuwi.selectedAccount.address); @@ -297,27 +329,35 @@ export const WalletProvider: React.FC<{ children: React.ReactNode }> = ({ childr if (import.meta.env.DEV) console.error('Message signing failed:', error); throw new Error(error instanceof Error ? error.message : 'Failed to sign message'); } - }, [pezkuwi.selectedAccount]); + }, [pezkuwi.selectedAccount, pezkuwi.walletSource, pezkuwi.api]); - // Get signer from extension when account changes + // Get signer from extension or WalletConnect when account changes useEffect(() => { const getSigner = async () => { - if (pezkuwi.selectedAccount) { - try { + if (!pezkuwi.selectedAccount) { + setSigner(null); + return; + } + + try { + if (pezkuwi.walletSource === 'walletconnect' && isWCConnected() && pezkuwi.api) { + const genesisHash = pezkuwi.api.genesisHash.toHex(); + const wcSigner = createWCSigner(genesisHash, pezkuwi.selectedAccount.address); + setSigner(wcSigner as unknown as Signer); + if (import.meta.env.DEV) console.log('✅ WC Signer obtained for', pezkuwi.selectedAccount.address); + } else { const injector = await web3FromAddress(pezkuwi.selectedAccount.address); setSigner(injector.signer); - if (import.meta.env.DEV) console.log('✅ Signer obtained for', pezkuwi.selectedAccount.address); - } catch (error) { - if (import.meta.env.DEV) console.error('Failed to get signer:', error); - setSigner(null); + if (import.meta.env.DEV) console.log('✅ Extension Signer obtained for', pezkuwi.selectedAccount.address); } - } else { + } catch (error) { + if (import.meta.env.DEV) console.error('Failed to get signer:', error); setSigner(null); } }; getSigner(); - }, [pezkuwi.selectedAccount]); + }, [pezkuwi.selectedAccount, pezkuwi.walletSource, pezkuwi.api]); // Update balance when selected account changes or Asset Hub becomes ready useEffect(() => { diff --git a/web/src/lib/walletconnect-service.ts b/web/src/lib/walletconnect-service.ts new file mode 100644 index 00000000..86963875 --- /dev/null +++ b/web/src/lib/walletconnect-service.ts @@ -0,0 +1,292 @@ +/** + * WalletConnect v2 Service for Pezkuwi dApp + * + * Handles WalletConnect v2 session management and provides a Signer adapter + * compatible with @pezkuwi/api's Signer interface. + * + * Flow A: Mobile browser -> QR code -> pezWallet scans -> session established + * Flow B: pezWallet DApps browser -> injected provider (handled by extension-dapp, not here) + */ + +import SignClient from '@walletconnect/sign-client'; +import type { SessionTypes, SignClientTypes } from '@walletconnect/types'; + +const PROJECT_ID = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID || ''; +const WC_SESSION_KEY = 'wc_session_topic'; + +// WalletConnect Polkadot namespace methods +const POLKADOT_METHODS = ['polkadot_signTransaction', 'polkadot_signMessage']; +const POLKADOT_EVENTS = ['chainChanged', 'accountsChanged']; + +let signClient: SignClient | null = null; +let currentSession: SessionTypes.Struct | null = null; +let requestId = 0; + +/** + * Initialize the WalletConnect SignClient + */ +export async function initWalletConnect(): Promise { + if (signClient) return signClient; + + signClient = await SignClient.init({ + projectId: PROJECT_ID, + metadata: { + name: 'PezkuwiChain', + description: 'Pezkuwi Web Application', + url: 'https://app.pezkuwichain.io', + icons: ['https://app.pezkuwichain.io/logo.png'], + }, + }); + + // Listen for session events + signClient.on('session_delete', () => { + currentSession = null; + localStorage.removeItem(WC_SESSION_KEY); + window.dispatchEvent(new Event('walletconnect_disconnected')); + }); + + signClient.on('session_expire', () => { + currentSession = null; + localStorage.removeItem(WC_SESSION_KEY); + window.dispatchEvent(new Event('walletconnect_disconnected')); + }); + + signClient.on('session_update', ({ params }) => { + if (currentSession) { + currentSession = { ...currentSession, namespaces: params.namespaces }; + } + }); + + return signClient; +} + +/** + * Build the polkadot: chain ID from genesis hash + * Format: polkadot: + */ +export function buildChainId(genesisHash: string): string { + const hash = genesisHash.startsWith('0x') ? genesisHash.slice(2) : genesisHash; + // WalletConnect uses first 32 bytes (64 hex chars) of genesis hash + return `polkadot:${hash.slice(0, 64)}`; +} + +/** + * Start a WalletConnect pairing session + * Returns the URI for QR code display + */ +export async function connectWithQR(genesisHash: string): Promise<{ + uri: string; + approval: () => Promise; +}> { + const client = await initWalletConnect(); + const chainId = buildChainId(genesisHash); + + const { uri, approval } = await client.connect({ + requiredNamespaces: { + polkadot: { + methods: POLKADOT_METHODS, + chains: [chainId], + events: POLKADOT_EVENTS, + }, + }, + }); + + if (!uri) { + throw new Error('Failed to generate WalletConnect pairing URI'); + } + + return { + uri, + approval: async () => { + const session = await approval(); + currentSession = session; + localStorage.setItem(WC_SESSION_KEY, session.topic); + return session; + }, + }; +} + +/** + * Get accounts from the current WalletConnect session + * Returns array of SS58 addresses + */ +export function getSessionAccounts(): string[] { + if (!currentSession) return []; + + const polkadotNamespace = currentSession.namespaces['polkadot']; + if (!polkadotNamespace?.accounts) return []; + + // Account format: polkadot:: + return polkadotNamespace.accounts.map((account) => { + const parts = account.split(':'); + return parts[parts.length - 1]; // Last part is the SS58 address + }); +} + +/** + * Get the peer wallet name from current session + */ +export function getSessionPeerName(): string | null { + if (!currentSession) return null; + return currentSession.peer.metadata.name || null; +} + +/** + * Get the peer wallet icon from current session + */ +export function getSessionPeerIcon(): string | null { + if (!currentSession) return null; + return currentSession.peer.metadata.icons?.[0] || null; +} + +/** + * Create a Signer adapter compatible with @pezkuwi/api's Signer interface + * Routes signPayload and signRaw through WalletConnect + */ +export function createWCSigner(genesisHash: string, address: string) { + const chainId = buildChainId(genesisHash); + const wcAccount = `polkadot:${chainId.split(':')[1]}:${address}`; + + return { + signPayload: async (payload: { + address: string; + blockHash: string; + blockNumber: string; + era: string; + genesisHash: string; + method: string; + nonce: string; + specVersion: string; + tip: string; + transactionVersion: string; + signedExtensions: string[]; + version: number; + }) => { + if (!signClient || !currentSession) { + throw new Error('WalletConnect session not active'); + } + + const id = ++requestId; + + const result = await signClient.request<{ signature: string }>({ + topic: currentSession.topic, + chainId, + request: { + method: 'polkadot_signTransaction', + params: { + address: wcAccount, + transactionPayload: payload, + }, + }, + }); + + return { + id, + signature: result.signature as `0x${string}`, + }; + }, + + signRaw: async (raw: { + address: string; + data: string; + type: 'bytes' | 'payload'; + }) => { + if (!signClient || !currentSession) { + throw new Error('WalletConnect session not active'); + } + + const id = ++requestId; + + const result = await signClient.request<{ signature: string }>({ + topic: currentSession.topic, + chainId, + request: { + method: 'polkadot_signMessage', + params: { + address: wcAccount, + message: raw.data, + }, + }, + }); + + return { + id, + signature: result.signature as `0x${string}`, + }; + }, + }; +} + +/** + * Restore a previous WalletConnect session from localStorage + */ +export async function restoreSession(): Promise { + const client = await initWalletConnect(); + const savedTopic = localStorage.getItem(WC_SESSION_KEY); + + if (!savedTopic) return null; + + // Check if the session still exists + const sessions = client.session.getAll(); + const session = sessions.find((s) => s.topic === savedTopic); + + if (session) { + // Check if session is not expired + const now = Math.floor(Date.now() / 1000); + if (session.expiry > now) { + currentSession = session; + return session; + } + } + + // Session expired or not found + localStorage.removeItem(WC_SESSION_KEY); + return null; +} + +/** + * Disconnect the current WalletConnect session + */ +export async function disconnectWC(): Promise { + if (!signClient || !currentSession) return; + + try { + await signClient.disconnect({ + topic: currentSession.topic, + reason: { + code: 6000, + message: 'User disconnected', + }, + }); + } catch { + // Ignore disconnect errors + } + + currentSession = null; + localStorage.removeItem(WC_SESSION_KEY); +} + +/** + * Check if there's an active WalletConnect session + */ +export function isWCConnected(): boolean { + return currentSession !== null; +} + +/** + * Get the current session + */ +export function getCurrentSession(): SessionTypes.Struct | null { + return currentSession; +} + +/** + * Listen for WalletConnect session proposals (for debugging) + */ +export function onSessionEvent( + event: SignClientTypes.Event, + callback: (data: unknown) => void +): void { + if (!signClient) return; + signClient.on(event, callback); +} diff --git a/web/vite.config.ts b/web/vite.config.ts index 78c0e60a..1531a835 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -103,5 +103,5 @@ export default defineConfig(() => ({ }, chunkSizeWarningLimit: 600 }, - assetsInclude: ['**/*.json'], + // assetsInclude: ['**/*.json'], // Disabled: interferes with node_modules JSON imports (crypto-browserify) })); \ No newline at end of file