feat: add WTT support

This commit is contained in:
Danish Arora 2025-04-10 14:40:59 +05:30
parent b3ac4f7bab
commit 9bc665efb5
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
13 changed files with 771 additions and 59 deletions

View File

@ -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. 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 ## TODO
- [ ] add info about using with nwaku/nwaku-compose/waku-simulator - [ ] add info about using with nwaku/nwaku-compose/waku-simulator
- [x] fix rate limit fetch - [x] fix rate limit fetch

403
package-lock.json generated
View File

@ -10,8 +10,9 @@
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "^5.2.5", "@fontsource-variable/inter": "^5.2.5",
"@fontsource-variable/jetbrains-mono": "^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-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.3",
"@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3",
@ -1787,15 +1788,6 @@
"fast-glob": "3.3.1" "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": { "node_modules/@next/swc-darwin-arm64": {
"version": "15.1.7", "version": "15.1.7",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.7.tgz", "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": { "node_modules/@radix-ui/react-direction": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", "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": { "node_modules/@radix-ui/react-menu": {
"version": "2.1.6", "version": "2.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.6.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.6.tgz",

View File

@ -12,7 +12,9 @@
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "^5.2.5", "@fontsource-variable/inter": "^5.2.5",
"@fontsource-variable/jetbrains-mono": "^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-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.3",
"@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3",

View File

@ -5,7 +5,6 @@ import { useKeystore } from '../../../contexts/keystore/KeystoreContext';
import { useRLN } from '../../../contexts/rln/RLNContext'; import { useRLN } from '../../../contexts/rln/RLNContext';
import { readKeystoreFromFile, saveKeystoreCredentialToFile } from '../../../utils/keystore'; import { readKeystoreFromFile, saveKeystoreCredentialToFile } from '../../../utils/keystore';
import { DecryptedCredentials, MembershipInfo, MembershipState } from '@waku/rln'; import { DecryptedCredentials, MembershipInfo, MembershipState } from '@waku/rln';
import { useAppState } from '../../../contexts/AppStateContext';
import { TerminalWindow } from '../../ui/terminal-window'; import { TerminalWindow } from '../../ui/terminal-window';
import { Button } from '../../ui/button'; import { Button } from '../../ui/button';
import { Copy, Eye, Download, Trash2, ArrowDownToLine } from 'lucide-react'; import { Copy, Eye, Download, Trash2, ArrowDownToLine } from 'lucide-react';

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import React from 'react'; import React, { useState, ChangeEvent } from 'react';
import { useWallet } from '../contexts/wallet'; import { useWallet } from '../contexts/wallet';
import { import {
DropdownMenu, DropdownMenu,
@ -11,10 +11,32 @@ import {
DropdownMenuSeparator DropdownMenuSeparator
} from '../components/ui/dropdown-menu'; } from '../components/ui/dropdown-menu';
import { Button } from '../components/ui/button'; 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() { 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<HTMLInputElement>) => {
setMintAmount(e.target.value);
};
if (!isConnected || !address) { if (!isConnected || !address) {
return ( return (
@ -30,46 +52,111 @@ export function WalletDropdown() {
} }
return ( return (
<DropdownMenu> <>
<DropdownMenuTrigger asChild> <DropdownMenu>
<Button <DropdownMenuTrigger asChild>
variant="outline" <Button
size="sm" variant="outline"
className="font-mono text-xs border-terminal-border hover:border-primary hover:text-primary" size="sm"
> className="font-mono text-xs border-terminal-border hover:border-primary hover:text-primary"
<span >
className={`inline-block h-2 w-2 rounded-full mr-1.5 bg-success-DEFAULT`} <span
/> className={`inline-block h-2 w-2 rounded-full mr-1.5 bg-success-DEFAULT`}
<span className="cursor-blink"> />
{address.slice(0, 6)}...{address.slice(-4)} <span className="cursor-blink">
</span> {address.slice(0, 6)}...{address.slice(-4)}
<ChevronDown className="ml-1 h-4 w-4" /> </span>
</Button> <ChevronDown className="ml-1 h-4 w-4" />
</DropdownMenuTrigger> </Button>
<DropdownMenuContent align="end" className="w-56"> </DropdownMenuTrigger>
<DropdownMenuLabel>Wallet</DropdownMenuLabel> <DropdownMenuContent align="end" className="w-64 font-mono border-terminal-border bg-terminal-background/95">
<DropdownMenuItem className="text-xs flex justify-between"> <DropdownMenuLabel className="border-b border-terminal-border text-primary">Wallet</DropdownMenuLabel>
<span className="text-muted-foreground">Address:</span>
<span className="text-primary truncate ml-2">{address}</span> <div className="p-2 space-y-2">
</DropdownMenuItem> {/* Address */}
<DropdownMenuItem className="text-xs flex justify-between"> <div className="grid grid-cols-12 gap-1 text-xs">
<span className="text-muted-foreground">Network:</span> <span className="text-muted-foreground col-span-3">Address:</span>
<span className="text-primary ml-2">{chainId === 59141 ? "Linea Sepolia" : `Unknown (${chainId})`}</span> <span className="text-primary col-span-9 truncate">{address}</span>
</DropdownMenuItem> </div>
{balance && (
<DropdownMenuItem className="text-xs flex justify-between"> {/* Network */}
<span className="text-muted-foreground">Balance:</span> <div className="grid grid-cols-12 gap-1 text-xs">
<span className="text-primary ml-2">{parseFloat(balance).toFixed(4)} ETH</span> <span className="text-muted-foreground col-span-3">Network:</span>
<span className="text-green-400 col-span-9">{chainId === 59141 ? "Linea Sepolia" : `Unknown (${chainId})`}</span>
</div>
{/* ETH Balance */}
{balance && (
<div className="grid grid-cols-12 gap-1 text-xs">
<span className="text-muted-foreground col-span-3">Balance:</span>
<span className="text-primary col-span-9">{parseFloat(balance).toFixed(4)} ETH</span>
</div>
)}
{/* WTT Balance with Mint Button */}
{wttBalance && (
<div className="text-xs">
<div className="grid grid-cols-12 gap-1 mb-1">
<span className="text-muted-foreground col-span-3">WTT</span>
<span className="text-primary col-span-9">{parseFloat(wttBalance).toFixed(4)}</span>
</div>
<div className="grid grid-cols-12 gap-1">
<span className="text-muted-foreground col-span-3">Balance:</span>
<span className="text-green-400 col-span-7">WTT</span>
<div className="col-span-2 text-right">
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="h-5 w-5 text-primary hover:text-green-400">
<PlusCircle className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] bg-terminal-background border-terminal-border">
<DialogHeader>
<DialogTitle className="text-primary">Mint WTT Tokens</DialogTitle>
<DialogDescription>
Enter the amount of WTT tokens you want to mint.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="amount" className="text-right">
Amount
</Label>
<Input
id="amount"
type="number"
value={mintAmount}
onChange={handleAmountChange}
className="col-span-3 bg-terminal-background border-terminal-border text-primary"
/>
</div>
</div>
<DialogFooter>
<Button
variant="terminal"
onClick={handleMint}
className="text-xs"
>
Mint Tokens
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
</div>
)}
</div>
<DropdownMenuSeparator className="bg-terminal-border" />
<DropdownMenuItem
onClick={disconnectWallet}
className="text-destructive focus:text-destructive cursor-pointer"
>
Disconnect
</DropdownMenuItem> </DropdownMenuItem>
)} </DropdownMenuContent>
<DropdownMenuSeparator /> </DropdownMenu>
<DropdownMenuItem </>
onClick={disconnectWallet}
className="text-destructive focus:text-destructive cursor-pointer"
>
Disconnect
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
); );
} }

View File

@ -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<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-terminal-border bg-terminal-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@ -0,0 +1,24 @@
import * as React from "react"
import { cn } from "../../lib/utils"
type InputProps = React.InputHTMLAttributes<HTMLInputElement>
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-terminal-border bg-terminal-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View File

@ -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<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@ -31,7 +31,7 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
const [decryptedCredentials, setDecryptedCredentials] = useState<KeystoreEntity | null>(null); const [decryptedCredentials, setDecryptedCredentials] = useState<KeystoreEntity | null>(null);
// Initialize keystore // Initialize keystore
useEffect(() => { useEffect(() => {
try { try {
const storedKeystore = localStorage.getItem(LOCAL_STORAGE_KEYSTORE_KEY); const storedKeystore = localStorage.getItem(LOCAL_STORAGE_KEYSTORE_KEY);
let keystoreInstance: Keystore; let keystoreInstance: Keystore;
@ -197,7 +197,9 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
exportEntireKeystore, exportEntireKeystore,
importKeystore, importKeystore,
removeCredential, removeCredential,
getDecryptedCredential getDecryptedCredential,
decryptedCredentials,
hideCredentials: () => {},
}; };
return ( return (

View File

@ -4,8 +4,9 @@ import { createContext, useContext, useState, useEffect, ReactNode, useCallback
import { extractMetaMaskSigner } from '@waku/rln'; import { extractMetaMaskSigner } from '@waku/rln';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { WalletContextType } from './types'; 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<WalletContextType | undefined>(undefined); const WalletContext = createContext<WalletContextType | undefined>(undefined);
@ -14,14 +15,65 @@ export function WalletProvider({ children }: { children: ReactNode }) {
const [address, setAddress] = useState<string | null>(null); const [address, setAddress] = useState<string | null>(null);
const [signer, setSigner] = useState<ethers.Signer | null>(null); const [signer, setSigner] = useState<ethers.Signer | null>(null);
const [balance, setBalance] = useState<string | null>(null); const [balance, setBalance] = useState<string | null>(null);
const [wttBalance, setWttBalance] = useState<string | null>(null);
const [chainId, setChainId] = useState<number | null>(null); const [chainId, setChainId] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(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 // Function to disconnect wallet - defined first to avoid reference issues
const disconnectWallet = useCallback(() => { const disconnectWallet = useCallback(() => {
setSigner(null); setSigner(null);
setAddress(null); setAddress(null);
setBalance(null); setBalance(null);
setWttBalance(null);
setChainId(null); setChainId(null);
setIsConnected(false); setIsConnected(false);
@ -46,13 +98,16 @@ export function WalletProvider({ children }: { children: ReactNode }) {
const balanceEth = ethers.utils.formatEther(balanceWei); const balanceEth = ethers.utils.formatEther(balanceWei);
setBalance(balanceEth); setBalance(balanceEth);
// Fetch WTT token balance
await fetchWttBalance(address, provider);
setIsConnected(true); setIsConnected(true);
} catch (err) { } catch (err) {
console.error('Error connecting wallet:', err); console.error('Error connecting wallet:', err);
setError(err instanceof Error ? err.message : 'Failed to connect wallet'); setError(err instanceof Error ? err.message : 'Failed to connect wallet');
disconnectWallet(); disconnectWallet();
} }
}, [disconnectWallet]); }, [disconnectWallet, fetchWttBalance]);
// Handle account changes // Handle account changes
const handleAccountsChanged = useCallback((accounts: string[]) => { const handleAccountsChanged = useCallback((accounts: string[]) => {
@ -119,9 +174,11 @@ export function WalletProvider({ children }: { children: ReactNode }) {
address, address,
signer, signer,
balance, balance,
wttBalance,
chainId, chainId,
connectWallet, connectWallet,
disconnectWallet, disconnectWallet,
mintWTT,
error error
}} }}
> >

View File

@ -5,9 +5,11 @@ export interface WalletContextType {
address: string | null; address: string | null;
signer: ethers.Signer | null; signer: ethers.Signer | null;
balance: string | null; balance: string | null;
wttBalance: string | null;
chainId: number | null; chainId: number | null;
connectWallet: () => Promise<void>; connectWallet: () => Promise<void>;
disconnectWallet: () => void; disconnectWallet: () => void;
mintWTT: (amount: number) => Promise<void>;
error: string | null; error: string | null;
} }

View File

@ -0,0 +1,2 @@
export const WAKU_TESTNET_TOKEN_ADDRESS = '0x185A0015aC462a0aECb81beCc0497b649a64B9ea';
export const RLN_REGISTRATION_CONTRACT_ADDRESS = '0xB9cd878C90E49F797B4431fBF4fb333108CB90e6';

View File

@ -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"}];