From 9bc665efb5ef9ce638091f4d9e230d1739c1b9ba Mon Sep 17 00:00:00 2001 From: Danish Arora Date: Thu, 10 Apr 2025 14:40:59 +0530 Subject: [PATCH] feat: add WTT support --- README.md | 5 + package-lock.json | 403 +++++++++++++++++- package.json | 2 + .../Tabs/KeystoreTab/KeystoreManagement.tsx | 1 - src/components/WalletDropdown.tsx | 173 ++++++-- src/components/ui/dialog.tsx | 122 ++++++ src/components/ui/input.tsx | 24 ++ src/components/ui/label.tsx | 26 ++ src/contexts/keystore/KeystoreContext.tsx | 6 +- src/contexts/wallet/WalletContext.tsx | 63 ++- src/contexts/wallet/types.ts | 2 + src/contracts/constants.ts | 2 + src/contracts/waku_testnet_token_abi.ts | 1 + 13 files changed, 771 insertions(+), 59 deletions(-) create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/contracts/constants.ts create mode 100644 src/contracts/waku_testnet_token_abi.ts diff --git a/README.md b/README.md index ab69670..3f77e32 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,11 @@ When registering for RLN membership, you'll need to complete two transactions: If you encounter an "ERC20: insufficient allowance" error, it means the token approval transaction was not completed successfully. Please try again and make sure to approve the token spending in your wallet. +## Contracts + +1. Waku Testnet Tokens: 0x185A0015aC462a0aECb81beCc0497b649a64B9ea +2. RLN Registration Contract: 0xB9cd878C90E49F797B4431fBF4fb333108CB90e6 + ## TODO - [ ] add info about using with nwaku/nwaku-compose/waku-simulator - [x] fix rate limit fetch diff --git a/package-lock.json b/package-lock.json index c735cb4..be555fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,9 @@ "dependencies": { "@fontsource-variable/inter": "^5.2.5", "@fontsource-variable/jetbrains-mono": "^5.2.5", - "@next/font": "^14.2.15", + "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.3", "@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", @@ -1787,15 +1788,6 @@ "fast-glob": "3.3.1" } }, - "node_modules/@next/font": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/font/-/font-14.2.15.tgz", - "integrity": "sha512-QopYhBmCDDrNDynbi+ZD1hDZXmQXVFo7TmAFp4DQgO/kogz1OLbQ92hPigJbj572eZ3GaaVxNIyYVn3/eAsehg==", - "license": "MIT", - "peerDependencies": { - "next": "*" - } - }, "node_modules/@next/swc-darwin-arm64": { "version": "15.1.7", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.7.tgz", @@ -2125,6 +2117,318 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.7.tgz", + "integrity": "sha512-EIdma8C0C/I6kL6sO02avaCRqi3fmWJpxH6mqbVScorW6nNktzKJT/le7VPho3o/7wCsyRg3z0+Q+Obr0Gy/VQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.6", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.3", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.5", + "@radix-ui/react-presence": "1.1.3", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-use-controllable-state": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.6.tgz", + "integrity": "sha512-7gpgMT2gyKym9Jz2ZhlRXSg2y6cNQIK8d/cqBZ0RBCaps8pFryCWXiUKI+uHGFrhMrbGUP7U6PWgiXzIxoyF3Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.3.tgz", + "integrity": "sha512-4XaDlq0bPt7oJwR+0k0clCiCO/7lO7NKZTAaJBYxDNQT/vj4ig0/UvctrRscZaFREpRvUTkpKR96ov1e6jptQg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.5.tgz", + "integrity": "sha512-ps/67ZqsFm+Mb6lSPJpfhRLrVL2i2fntgCmGMqqth4eaGUf+knAuuRtWVJrNjUhExgmdRqftSgzpf0DF0n6yXA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.3.tgz", + "integrity": "sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.3.tgz", + "integrity": "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.1.tgz", + "integrity": "sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -2254,6 +2558,85 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.3.tgz", + "integrity": "sha512-zwSQ1NzSKG95yA0tvBMgv6XPHoqapJCcg9nsUBaQQ66iRBhZNhlpaQG2ERYYX4O4stkYFK5rxj5NsWfO9CS+Hg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.3.tgz", + "integrity": "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.6.tgz", diff --git a/package.json b/package.json index ce7fe58..6eb6207 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "dependencies": { "@fontsource-variable/inter": "^5.2.5", "@fontsource-variable/jetbrains-mono": "^5.2.5", + "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.3", "@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", diff --git a/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx b/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx index d7d340c..ff35095 100644 --- a/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx +++ b/src/components/Tabs/KeystoreTab/KeystoreManagement.tsx @@ -5,7 +5,6 @@ import { useKeystore } from '../../../contexts/keystore/KeystoreContext'; import { useRLN } from '../../../contexts/rln/RLNContext'; import { readKeystoreFromFile, saveKeystoreCredentialToFile } from '../../../utils/keystore'; import { DecryptedCredentials, MembershipInfo, MembershipState } from '@waku/rln'; -import { useAppState } from '../../../contexts/AppStateContext'; import { TerminalWindow } from '../../ui/terminal-window'; import { Button } from '../../ui/button'; import { Copy, Eye, Download, Trash2, ArrowDownToLine } from 'lucide-react'; diff --git a/src/components/WalletDropdown.tsx b/src/components/WalletDropdown.tsx index 2fac970..b7d940b 100644 --- a/src/components/WalletDropdown.tsx +++ b/src/components/WalletDropdown.tsx @@ -1,6 +1,6 @@ "use client"; -import React from 'react'; +import React, { useState, ChangeEvent } from 'react'; import { useWallet } from '../contexts/wallet'; import { DropdownMenu, @@ -11,10 +11,32 @@ import { DropdownMenuSeparator } from '../components/ui/dropdown-menu'; import { Button } from '../components/ui/button'; -import { ChevronDown } from 'lucide-react'; +import { ChevronDown, PlusCircle } from 'lucide-react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../components/ui/dialog"; +import { Input } from "../components/ui/input"; +import { Label } from "../components/ui/label"; export function WalletDropdown() { - const { isConnected, address, chainId, connectWallet, disconnectWallet, balance } = useWallet(); + const { isConnected, address, chainId, connectWallet, disconnectWallet, balance, wttBalance, mintWTT } = useWallet(); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [mintAmount, setMintAmount] = useState('1000'); + + const handleMint = async () => { + await mintWTT(Number(mintAmount)); + setIsDialogOpen(false); + }; + + const handleAmountChange = (e: ChangeEvent) => { + setMintAmount(e.target.value); + }; if (!isConnected || !address) { return ( @@ -30,46 +52,111 @@ export function WalletDropdown() { } return ( - - - - - - Wallet - - Address: - {address} - - - Network: - {chainId === 59141 ? "Linea Sepolia" : `Unknown (${chainId})`} - - {balance && ( - - Balance: - {parseFloat(balance).toFixed(4)} ETH + <> + + + + + + Wallet + +
+ {/* Address */} +
+ Address: + {address} +
+ + {/* Network */} +
+ Network: + {chainId === 59141 ? "Linea Sepolia" : `Unknown (${chainId})`} +
+ + {/* ETH Balance */} + {balance && ( +
+ Balance: + {parseFloat(balance).toFixed(4)} ETH +
+ )} + + {/* WTT Balance with Mint Button */} + {wttBalance && ( +
+
+ WTT + {parseFloat(wttBalance).toFixed(4)} +
+
+ Balance: + WTT +
+ + + + + + + Mint WTT Tokens + + Enter the amount of WTT tokens you want to mint. + + +
+
+ + +
+
+ + + +
+
+
+
+
+ )} +
+ + + + Disconnect - )} - - - Disconnect - -
-
+
+
+ ); } \ No newline at end of file diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..c6d008b --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "../../lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} \ No newline at end of file diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..4f958a3 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,24 @@ +import * as React from "react" + +import { cn } from "../../lib/utils" + +type InputProps = React.InputHTMLAttributes + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } \ No newline at end of file diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..7f2fd14 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "../../lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } \ No newline at end of file diff --git a/src/contexts/keystore/KeystoreContext.tsx b/src/contexts/keystore/KeystoreContext.tsx index 0783e24..e05e11e 100644 --- a/src/contexts/keystore/KeystoreContext.tsx +++ b/src/contexts/keystore/KeystoreContext.tsx @@ -31,7 +31,7 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { const [decryptedCredentials, setDecryptedCredentials] = useState(null); // Initialize keystore - useEffect(() => { + useEffect(() => { try { const storedKeystore = localStorage.getItem(LOCAL_STORAGE_KEYSTORE_KEY); let keystoreInstance: Keystore; @@ -197,7 +197,9 @@ export function KeystoreProvider({ children }: { children: ReactNode }) { exportEntireKeystore, importKeystore, removeCredential, - getDecryptedCredential + getDecryptedCredential, + decryptedCredentials, + hideCredentials: () => {}, }; return ( diff --git a/src/contexts/wallet/WalletContext.tsx b/src/contexts/wallet/WalletContext.tsx index 5640a11..b6b3124 100644 --- a/src/contexts/wallet/WalletContext.tsx +++ b/src/contexts/wallet/WalletContext.tsx @@ -4,8 +4,9 @@ import { createContext, useContext, useState, useEffect, ReactNode, useCallback import { extractMetaMaskSigner } from '@waku/rln'; import { ethers } from 'ethers'; import { WalletContextType } from './types'; - - +import { WAKU_TESTNET_TOKEN_ABI } from '../../contracts/waku_testnet_token_abi'; +import { WAKU_TESTNET_TOKEN_ADDRESS } from '../../contracts/constants'; +import { toast } from 'sonner'; const WalletContext = createContext(undefined); @@ -14,14 +15,65 @@ export function WalletProvider({ children }: { children: ReactNode }) { const [address, setAddress] = useState(null); const [signer, setSigner] = useState(null); const [balance, setBalance] = useState(null); + const [wttBalance, setWttBalance] = useState(null); const [chainId, setChainId] = useState(null); const [error, setError] = useState(null); + // Function to fetch WTT token balance + const fetchWttBalance = useCallback(async (userAddress: string, provider: ethers.providers.Provider) => { + try { + const tokenContract = new ethers.Contract(WAKU_TESTNET_TOKEN_ADDRESS, WAKU_TESTNET_TOKEN_ABI, provider); + const balance = await tokenContract.balanceOf(userAddress); + const formattedBalance = ethers.utils.formatUnits(balance, 18); // Assuming 18 decimals for the token + setWttBalance(formattedBalance); + } catch (err) { + console.error('Error fetching WTT balance:', err); + setWttBalance(null); + } + }, []); + + // Function to mint WTT tokens + const mintWTT = useCallback(async (amount: number) => { + if (!signer || !address) { + toast.error('Please connect your wallet first'); + return; + } + + try { + toast.loading('Minting WTT tokens...'); + + const tokenContract = new ethers.Contract( + WAKU_TESTNET_TOKEN_ADDRESS, + WAKU_TESTNET_TOKEN_ABI, + signer + ); + + // Convert amount to wei (assuming 18 decimals) + const amountInWei = ethers.utils.parseUnits(amount.toString(), 18); + + // Call the mint function + const tx = await tokenContract.mint(address, amountInWei); + await tx.wait(); + + // Refresh the balance + const provider = signer.provider as ethers.providers.Web3Provider; + await fetchWttBalance(address, provider); + + toast.dismiss(); + toast.success(`Successfully minted ${amount} WTT tokens`); + } catch (err) { + toast.dismiss(); + console.error('Error minting WTT tokens:', err); + toast.error(err instanceof Error ? err.message : 'Failed to mint WTT tokens'); + } + }, [signer, address, fetchWttBalance]); + // Function to disconnect wallet - defined first to avoid reference issues const disconnectWallet = useCallback(() => { setSigner(null); setAddress(null); setBalance(null); + setWttBalance(null); setChainId(null); setIsConnected(false); @@ -46,13 +98,16 @@ export function WalletProvider({ children }: { children: ReactNode }) { const balanceEth = ethers.utils.formatEther(balanceWei); setBalance(balanceEth); + // Fetch WTT token balance + await fetchWttBalance(address, provider); + setIsConnected(true); } catch (err) { console.error('Error connecting wallet:', err); setError(err instanceof Error ? err.message : 'Failed to connect wallet'); disconnectWallet(); } - }, [disconnectWallet]); + }, [disconnectWallet, fetchWttBalance]); // Handle account changes const handleAccountsChanged = useCallback((accounts: string[]) => { @@ -119,9 +174,11 @@ export function WalletProvider({ children }: { children: ReactNode }) { address, signer, balance, + wttBalance, chainId, connectWallet, disconnectWallet, + mintWTT, error }} > diff --git a/src/contexts/wallet/types.ts b/src/contexts/wallet/types.ts index e61a903..d4477cc 100644 --- a/src/contexts/wallet/types.ts +++ b/src/contexts/wallet/types.ts @@ -5,9 +5,11 @@ export interface WalletContextType { address: string | null; signer: ethers.Signer | null; balance: string | null; + wttBalance: string | null; chainId: number | null; connectWallet: () => Promise; disconnectWallet: () => void; + mintWTT: (amount: number) => Promise; error: string | null; } diff --git a/src/contracts/constants.ts b/src/contracts/constants.ts new file mode 100644 index 0000000..7bdeca7 --- /dev/null +++ b/src/contracts/constants.ts @@ -0,0 +1,2 @@ +export const WAKU_TESTNET_TOKEN_ADDRESS = '0x185A0015aC462a0aECb81beCc0497b649a64B9ea'; +export const RLN_REGISTRATION_CONTRACT_ADDRESS = '0xB9cd878C90E49F797B4431fBF4fb333108CB90e6'; \ No newline at end of file diff --git a/src/contracts/waku_testnet_token_abi.ts b/src/contracts/waku_testnet_token_abi.ts new file mode 100644 index 0000000..b37cd34 --- /dev/null +++ b/src/contracts/waku_testnet_token_abi.ts @@ -0,0 +1 @@ +export const WAKU_TESTNET_TOKEN_ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]; \ No newline at end of file