Merge pull request #3 from waku-org/feat/wtt-mint

feat: WTT view+mint
This commit is contained in:
Danish Arora 2025-04-11 16:49:40 +05:30 committed by GitHub
commit c464960589
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 812 additions and 50 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,20 +1,73 @@
"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,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel,
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,
Wallet,
ExternalLink,
Copy,
LogOut,
Coins
} from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
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);
};
// Format address in the "0xabc...xyz" pattern
const formatAddress = (address: string) => {
if (!address) return '';
return `0x${address.slice(2, 5)}...${address.slice(-3)}`;
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
};
const getNetworkColor = () => {
if (chainId === 59141) return "text-green-400";
return "text-yellow-400";
};
const getNetworkName = () => {
if (chainId === 59141) return "Linea Sepolia";
return `Unknown (${chainId})`;
};
if (!isConnected || !address) { if (!isConnected || !address) {
return ( return (
@ -22,8 +75,9 @@ export function WalletDropdown() {
onClick={connectWallet} onClick={connectWallet}
variant="terminal" variant="terminal"
size="sm" size="sm"
className="text-xs" className="text-xs flex items-center gap-1.5"
> >
<Wallet className="h-3.5 w-3.5" />
Connect Wallet Connect Wallet
</Button> </Button>
); );
@ -35,39 +89,122 @@ export function WalletDropdown() {
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="font-mono text-xs border-terminal-border hover:border-primary hover:text-primary" className="font-mono text-xs border-terminal-border hover:border-primary hover:text-primary flex items-center gap-1.5"
> >
<span <span className="inline-block h-2 w-2 rounded-full bg-success-DEFAULT" />
className={`inline-block h-2 w-2 rounded-full mr-1.5 bg-success-DEFAULT`} <span className="cursor-blink font-medium">
/> {formatAddress(address)}
<span className="cursor-blink">
{address.slice(0, 6)}...{address.slice(-4)}
</span> </span>
<ChevronDown className="ml-1 h-4 w-4" /> <ChevronDown className="h-3.5 w-3.5" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56"> <DropdownMenuContent align="end" className="w-72 font-mono border-terminal-border bg-terminal-background/95 p-0">
<DropdownMenuLabel>Wallet</DropdownMenuLabel> <div className="p-3 border-b border-terminal-border bg-terminal-background/80">
<DropdownMenuItem className="text-xs flex justify-between"> <div className="flex items-center justify-between mb-2">
<span className="text-muted-foreground">Address:</span> <div className="flex items-center gap-1.5">
<span className="text-primary truncate ml-2">{address}</span> <Wallet className="h-4 w-4 text-primary" />
</DropdownMenuItem> <span className="text-primary text-sm font-medium">Wallet</span>
<DropdownMenuItem className="text-xs flex justify-between"> </div>
<span className="text-muted-foreground">Network:</span> <div className={`text-xs px-1.5 py-0.5 rounded-sm ${getNetworkColor()} bg-terminal-background/80 border border-terminal-border`}>
<span className="text-primary ml-2">{chainId === 59141 ? "Linea Sepolia" : `Unknown (${chainId})`}</span> {getNetworkName()}
</DropdownMenuItem> </div>
{balance && ( </div>
<DropdownMenuItem className="text-xs flex justify-between">
<span className="text-muted-foreground">Balance:</span> <div className="flex items-center justify-between">
<span className="text-primary ml-2">{parseFloat(balance).toFixed(4)} ETH</span> <div className="truncate text-primary text-xs font-medium">{address}</div>
</DropdownMenuItem> <div className="flex gap-1">
)} <Button
<DropdownMenuSeparator /> variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-primary"
onClick={() => copyToClipboard(address || '')}
>
<Copy className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-primary"
onClick={() => window.open(`https://explorer.linea.build/address/${address}`, '_blank')}
>
<ExternalLink className="h-3.5 w-3.5" />
</Button>
</div>
</div>
</div>
<div className="p-3 space-y-3">
{/* Balances section */}
<div className="space-y-2">
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-muted-foreground">
<Coins className="h-3.5 w-3.5" />
<span>ETH Balance</span>
</div>
<span className="text-primary font-medium">{parseFloat(balance || '0').toFixed(4)}</span>
</div>
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-muted-foreground">
<Coins className="h-3.5 w-3.5" />
<span>WTT Balance</span>
</div>
<div className="flex items-center gap-1.5">
<span className="text-primary font-medium">{parseFloat(wttBalance || '0').toFixed(4)}</span>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="h-5 w-5 p-0 text-muted-foreground hover:text-green-400">
<PlusCircle className="h-3.5 w-3.5" />
</Button>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent side="left">
<p className="font-mono text-xs">Mint WTT tokens for testing</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DialogContent className="sm:max-w-[350px] bg-terminal-background border-terminal-border">
<DialogHeader>
<DialogTitle className="text-primary">Mint WTT Tokens</DialogTitle>
<DialogDescription className="text-muted-foreground text-xs">
Specify how many WTT tokens you want to mint for testing.
</DialogDescription>
</DialogHeader>
<div className="py-3">
<div className="flex items-center gap-3">
<Input
id="amount"
type="number"
value={mintAmount}
onChange={handleAmountChange}
className="bg-terminal-background/60 border-terminal-border text-primary h-9"
/>
<Button
variant="terminal"
onClick={handleMint}
className="text-xs h-9"
>
Mint
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</div>
</div>
<DropdownMenuSeparator className="bg-terminal-border m-0" />
<DropdownMenuItem <DropdownMenuItem
onClick={disconnectWallet} onClick={disconnectWallet}
className="text-destructive focus:text-destructive cursor-pointer" className="m-0 rounded-none text-xs py-2.5 text-destructive focus:text-destructive cursor-pointer flex items-center gap-2"
> >
Disconnect <LogOut className="h-3.5 w-3.5" />
Disconnect Wallet
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </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"}];