diff --git a/.cspell.json b/.cspell.json index e6c6f1fa0a..9bcb16b2b7 100644 --- a/.cspell.json +++ b/.cspell.json @@ -104,6 +104,7 @@ "reactjs", "recid", "rlnrelay", + "rlnv", "roadmap", "sandboxed", "scanf", @@ -132,7 +133,9 @@ "upgrader", "vacp", "varint", + "viem", "vkey", + "wagmi", "waku", "wakuconnect", "wakunode", diff --git a/.gitignore b/.gitignore index 1e6952cf2f..188172b1d3 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ packages/discovery/mock_local_storage CLAUDE.md .env postgres-data/ +packages/rln/waku-rlnv2-contract/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..31530f9b50 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packages/rln/waku-rlnv2-contract"] + path = packages/rln/waku-rlnv2-contract + url = git@github.com:waku-org/waku-rlnv2-contract.git diff --git a/package-lock.json b/package-lock.json index 9bc9d3b4ed..57bd6f48d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,12 @@ "wscat": "^6.0.1" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -2654,164 +2660,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@ethersproject/abi": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", - "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/address": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/hash": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/strings": "^5.8.0" - } - }, - "node_modules/@ethersproject/abstract-provider": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", - "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/networks": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "@ethersproject/web": "^5.8.0" - } - }, - "node_modules/@ethersproject/abstract-signer": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", - "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abstract-provider": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0" - } - }, - "node_modules/@ethersproject/address": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", - "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/rlp": "^5.8.0" - } - }, - "node_modules/@ethersproject/base64": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", - "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0" - } - }, - "node_modules/@ethersproject/basex": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.8.0.tgz", - "integrity": "sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/properties": "^5.8.0" - } - }, - "node_modules/@ethersproject/bignumber": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", - "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "bn.js": "^5.2.1" - } - }, "node_modules/@ethersproject/bytes": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", @@ -2831,161 +2679,6 @@ "@ethersproject/logger": "^5.8.0" } }, - "node_modules/@ethersproject/constants": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", - "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bignumber": "^5.8.0" - } - }, - "node_modules/@ethersproject/contracts": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.8.0.tgz", - "integrity": "sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abi": "^5.8.0", - "@ethersproject/abstract-provider": "^5.8.0", - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/transactions": "^5.8.0" - } - }, - "node_modules/@ethersproject/hash": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", - "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/base64": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/strings": "^5.8.0" - } - }, - "node_modules/@ethersproject/hdnode": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.8.0.tgz", - "integrity": "sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/basex": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/pbkdf2": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/sha2": "^5.8.0", - "@ethersproject/signing-key": "^5.8.0", - "@ethersproject/strings": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "@ethersproject/wordlists": "^5.8.0" - } - }, - "node_modules/@ethersproject/json-wallets": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz", - "integrity": "sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/hdnode": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/pbkdf2": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/random": "^5.8.0", - "@ethersproject/strings": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "aes-js": "3.0.0", - "scrypt-js": "3.0.1" - } - }, - "node_modules/@ethersproject/keccak256": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", - "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "js-sha3": "0.8.0" - } - }, "node_modules/@ethersproject/logger": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", @@ -3002,143 +2695,6 @@ ], "license": "MIT" }, - "node_modules/@ethersproject/networks": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", - "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/pbkdf2": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz", - "integrity": "sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/sha2": "^5.8.0" - } - }, - "node_modules/@ethersproject/properties": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", - "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/providers": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.8.0.tgz", - "integrity": "sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abstract-provider": "^5.8.0", - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/base64": "^5.8.0", - "@ethersproject/basex": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/hash": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/networks": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/random": "^5.8.0", - "@ethersproject/rlp": "^5.8.0", - "@ethersproject/sha2": "^5.8.0", - "@ethersproject/strings": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "@ethersproject/web": "^5.8.0", - "bech32": "1.1.4", - "ws": "8.18.0" - } - }, - "node_modules/@ethersproject/providers/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@ethersproject/random": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.8.0.tgz", - "integrity": "sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, "node_modules/@ethersproject/rlp": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", @@ -3159,223 +2715,6 @@ "@ethersproject/logger": "^5.8.0" } }, - "node_modules/@ethersproject/sha2": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz", - "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/signing-key": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", - "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "bn.js": "^5.2.1", - "elliptic": "6.6.1", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/solidity": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.8.0.tgz", - "integrity": "sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/sha2": "^5.8.0", - "@ethersproject/strings": "^5.8.0" - } - }, - "node_modules/@ethersproject/strings": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", - "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/transactions": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", - "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/address": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/rlp": "^5.8.0", - "@ethersproject/signing-key": "^5.8.0" - } - }, - "node_modules/@ethersproject/units": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.8.0.tgz", - "integrity": "sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/wallet": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.8.0.tgz", - "integrity": "sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abstract-provider": "^5.8.0", - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/hash": "^5.8.0", - "@ethersproject/hdnode": "^5.8.0", - "@ethersproject/json-wallets": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/random": "^5.8.0", - "@ethersproject/signing-key": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "@ethersproject/wordlists": "^5.8.0" - } - }, - "node_modules/@ethersproject/web": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", - "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/base64": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/strings": "^5.8.0" - } - }, - "node_modules/@ethersproject/wordlists": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.8.0.tgz", - "integrity": "sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/hash": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/strings": "^5.8.0" - } - }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -6810,7 +6149,7 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -6831,7 +6170,7 @@ "version": "18.3.24", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -7532,6 +6871,151 @@ "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", "license": "MIT" }, + "node_modules/@wagmi/cli": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@wagmi/cli/-/cli-2.8.0.tgz", + "integrity": "sha512-2VhDj8u8vwLZwMZ8CX4pTuO0Qm28Z9uH9qOEWgF/xXUCeVV+4e4YsknEyGcoxYwEmTkdlCmuCvMj4up2XK6vxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abitype": "^1.0.4", + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "change-case": "^5.4.4", + "chokidar": "4.0.1", + "dedent": "^0.7.0", + "dotenv": "^16.3.1", + "dotenv-expand": "^10.0.0", + "esbuild": "~0.25.4", + "escalade": "3.2.0", + "fdir": "^6.1.1", + "nanospinner": "1.2.2", + "pathe": "^1.1.2", + "picocolors": "^1.0.0", + "picomatch": "^3.0.0", + "prettier": "^3.0.3", + "viem": "2.x", + "zod": "^4.1.11" + }, + "bin": { + "wagmi": "dist/esm/cli.js" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wagmi/cli/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@wagmi/cli/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@wagmi/cli/node_modules/picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@wagmi/cli/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@wagmi/cli/node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@wagmi/core": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.22.1.tgz", + "integrity": "sha512-cG/xwQWsBEcKgRTkQVhH29cbpbs/TdcUJVFXCyri3ZknxhMyGv0YEjTcrNpRgt2SaswL1KrvslSNYKKo+5YEAg==", + "license": "MIT", + "dependencies": { + "eventemitter3": "5.0.1", + "mipd": "0.0.7", + "zustand": "5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/query-core": ">=5.0.0", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@tanstack/query-core": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@wagmi/core/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/@waku/browser-tests": { "resolved": "packages/browser-tests", "link": true @@ -7659,6 +7143,11 @@ "integrity": "sha512-2Xp7e92y4qZpsiTPGBSVr4gVJ9mJTLaudlo0DQxNpxJUBtoJKpxdH5xDCQDiorbkWZC2j9EId+ohhxHO/xC1QQ==", "license": "MIT or Apache2" }, + "node_modules/@waku/zerokit-rln-wasm-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm-utils/-/zerokit-rln-wasm-utils-0.1.0.tgz", + "integrity": "sha512-3ccyg9+CtRXFJfWaxI/kx8Aec5B2S9YUmZAVhPRdN1EG6iQYG2hgvAurx8ZF9/zOppdrhzzyvCgDPg5kRUlOfQ==" + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -7840,6 +7329,27 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "license": "BSD-2-Clause" }, + "node_modules/abitype": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.1.tgz", + "integrity": "sha512-Loe5/6tAgsBukY95eGaPSDmQHIjRZYQq8PB1MpsNccDIK8WiV+Uw6WzaIXipvaxTEL2yEB0OpEaQv3gs8pkS9Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -9467,12 +8977,6 @@ "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", "license": "Apache-2.0" }, - "node_modules/aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", - "license": "MIT" - }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -10443,12 +9947,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", - "license": "MIT" - }, "node_modules/before-after-hook": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", @@ -10486,12 +9984,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bn.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", - "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -10566,12 +10058,6 @@ "node": ">=8" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "license": "MIT" - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -10658,6 +10144,22 @@ "node": ">=10.0.0" } }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, "node_modules/byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", @@ -10720,6 +10222,16 @@ } } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -11066,6 +10578,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -12310,7 +11829,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/custom-event": { @@ -12514,6 +12033,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -13151,6 +12677,29 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -13451,27 +13000,6 @@ "node": ">=0.10.0" } }, - "node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", - "license": "MIT" - }, "node_modules/email-addresses": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", @@ -15002,54 +14530,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/ethers": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz", - "integrity": "sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/abi": "5.8.0", - "@ethersproject/abstract-provider": "5.8.0", - "@ethersproject/abstract-signer": "5.8.0", - "@ethersproject/address": "5.8.0", - "@ethersproject/base64": "5.8.0", - "@ethersproject/basex": "5.8.0", - "@ethersproject/bignumber": "5.8.0", - "@ethersproject/bytes": "5.8.0", - "@ethersproject/constants": "5.8.0", - "@ethersproject/contracts": "5.8.0", - "@ethersproject/hash": "5.8.0", - "@ethersproject/hdnode": "5.8.0", - "@ethersproject/json-wallets": "5.8.0", - "@ethersproject/keccak256": "5.8.0", - "@ethersproject/logger": "5.8.0", - "@ethersproject/networks": "5.8.0", - "@ethersproject/pbkdf2": "5.8.0", - "@ethersproject/properties": "5.8.0", - "@ethersproject/providers": "5.8.0", - "@ethersproject/random": "5.8.0", - "@ethersproject/rlp": "5.8.0", - "@ethersproject/sha2": "5.8.0", - "@ethersproject/signing-key": "5.8.0", - "@ethersproject/solidity": "5.8.0", - "@ethersproject/strings": "5.8.0", - "@ethersproject/transactions": "5.8.0", - "@ethersproject/units": "5.8.0", - "@ethersproject/wallet": "5.8.0", - "@ethersproject/web": "5.8.0", - "@ethersproject/wordlists": "5.8.0" - } - }, "node_modules/event-iterator": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", @@ -16550,16 +16030,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -16639,17 +16109,6 @@ "node": "*" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "license": "MIT", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -17891,6 +17350,21 @@ "whatwg-fetch": "^3.4.1" } }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -18382,12 +17856,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -19603,6 +19071,16 @@ "node": ">=4" } }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -21156,18 +20634,6 @@ "node": ">=4" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "license": "MIT" - }, "node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -21234,6 +20700,26 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mipd": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz", + "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", @@ -25572,6 +25058,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ox": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz", + "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/p-cancelable": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", @@ -26233,6 +25770,13 @@ "inherits": "2.0.3" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -26959,7 +26503,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -29102,12 +28645,6 @@ "dev": true, "license": "MIT" }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "license": "MIT" - }, "node_modules/semantic-release": { "version": "24.2.5", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.5.tgz", @@ -32965,6 +32502,72 @@ "node": ">= 0.8" } }, + "node_modules/viem": { + "version": "2.39.0", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.39.0.tgz", + "integrity": "sha512-rCN+IfnMESlrg/iPyyVL+M9NS/BHzyyNy72470tFmbTuscY3iPaZGMtJDcHKKV8TC6HV9DjWk0zWX6cpu0juyA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.1.0", + "isows": "1.0.7", + "ox": "0.9.6", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/abitype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", + "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -33755,12 +33358,41 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz", + "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -35092,18 +34724,20 @@ "dependencies": { "@chainsafe/bls-keystore": "3.0.0", "@noble/hashes": "^1.2.0", + "@wagmi/core": "^2.22.1", "@waku/core": "^0.0.40", "@waku/utils": "^0.0.27", "@waku/zerokit-rln-wasm": "^0.2.1", + "@waku/zerokit-rln-wasm-utils": "^0.1.0", "chai": "^5.1.2", "chai-as-promised": "^8.0.1", "chai-spies": "^1.1.0", "chai-subset": "^1.6.0", "ethereum-cryptography": "^3.1.0", - "ethers": "^5.7.2", "lodash": "^4.17.21", "sinon": "^19.0.2", - "uuid": "^11.0.5" + "uuid": "^11.0.5", + "viem": "^2.38.4" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", @@ -35114,9 +34748,9 @@ "@types/deep-equal-in-any-order": "^1.0.4", "@types/lodash": "^4.17.15", "@types/sinon": "^17.0.3", + "@wagmi/cli": "^2.7.0", "@waku/build-utils": "^1.0.0", - "@waku/interfaces": "0.0.34", - "@waku/message-encryption": "^0.0.38", + "@waku/message-encryption": "^0.0.37", "deep-equal-in-any-order": "^2.0.6", "fast-check": "^3.23.2", "rollup-plugin-copy": "^3.5.0" @@ -35140,6 +34774,89 @@ "@types/deep-eql": "*" } }, + "packages/rln/node_modules/@waku/message-encryption": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@waku/message-encryption/-/message-encryption-0.0.37.tgz", + "integrity": "sha512-8WmDxVnitqzxWrKRHdmHmOKk81LBh0OE9uLFzBNxirmH9G4uDx0va3+S4lazgCQ4kT0zKrsN8VldX0BfePTVDQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@noble/secp256k1": "^1.7.1", + "@waku/core": "0.0.39", + "@waku/interfaces": "0.0.34", + "@waku/proto": "0.0.14", + "@waku/utils": "0.0.27", + "debug": "^4.3.4", + "js-sha3": "^0.9.2", + "uint8arrays": "^5.0.1" + }, + "engines": { + "node": ">=22" + } + }, + "packages/rln/node_modules/@waku/message-encryption/node_modules/@waku/core": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.39.tgz", + "integrity": "sha512-Vgb52md4GOzM5z9xfULzjN2tvVHKszFmj5zc2mVDoIgySH4cFBgDTHtVtGEwrFRFWadWYKBtpKBdmG3X+W7SNA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@libp2p/ping": "2.0.35", + "@noble/hashes": "^1.3.2", + "@waku/enr": "^0.0.33", + "@waku/interfaces": "0.0.34", + "@waku/proto": "0.0.14", + "@waku/utils": "0.0.27", + "debug": "^4.3.4", + "it-all": "^3.0.4", + "it-length-prefixed": "^9.0.4", + "it-pipe": "^3.0.1", + "uint8arraylist": "^2.4.3", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@multiformats/multiaddr": "^12.0.0", + "libp2p": "2.8.11" + }, + "peerDependenciesMeta": { + "@multiformats/multiaddr": { + "optional": true + }, + "libp2p": { + "optional": true + } + } + }, + "packages/rln/node_modules/@waku/message-encryption/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "packages/rln/node_modules/@waku/proto": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.14.tgz", + "integrity": "sha512-8zKVHrKzzKQfZBVnpSmJ6G8H1Zd4Gqms1tj3L6K2WCE/NQDR8wJtFwziab3dJ/5rKUTjfPAWFJ57RN97ltzxGA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "protons-runtime": "^5.4.0" + }, + "engines": { + "node": ">=22" + } + }, "packages/rln/node_modules/assertion-error": { "version": "2.0.1", "license": "MIT", @@ -35192,6 +34909,13 @@ "node": ">=0.3.1" } }, + "packages/rln/node_modules/js-sha3": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", + "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==", + "dev": true, + "license": "MIT" + }, "packages/rln/node_modules/loupe": { "version": "3.1.3", "license": "MIT" diff --git a/packages/rln/.eslintrc.cjs b/packages/rln/.eslintrc.cjs index c49a7edc19..b274db12d2 100644 --- a/packages/rln/.eslintrc.cjs +++ b/packages/rln/.eslintrc.cjs @@ -3,5 +3,10 @@ module.exports = { tsconfigRootDir: __dirname, project: "./tsconfig.dev.json" }, - ignorePatterns: ["src/resources/**/*"] + ignorePatterns: ["src/resources/**/*"], + overrides: [ + { + files: ["*.config.ts", "*.config.js"] + } + ] }; diff --git a/packages/rln/.mocha.reporters.json b/packages/rln/.mocha.reporters.json new file mode 100644 index 0000000000..8b32b1de9c --- /dev/null +++ b/packages/rln/.mocha.reporters.json @@ -0,0 +1,6 @@ +{ + "reporterEnabled": "spec, allure-mocha", + "allureMochaReporter": { + "outputDir": "allure-results" + } + } \ No newline at end of file diff --git a/packages/rln/.mocharc.cjs b/packages/rln/.mocharc.cjs index 268cf0c611..fb31453aed 100644 --- a/packages/rln/.mocharc.cjs +++ b/packages/rln/.mocharc.cjs @@ -20,8 +20,11 @@ if (process.env.CI) { config.reporterOptions = { configFile: '.mocha.reporters.json' }; + // Exclude integration tests in CI (they require RPC access) + console.log("Excluding integration tests in CI environment"); + config.ignore = ['src/**/*.integration.spec.ts', 'src/**/*.browser.spec.ts']; } else { console.log("Running tests serially. To enable parallel execution update mocha config"); } -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/packages/rln/README.md b/packages/rln/README.md index 3de34d55a1..653cf4e998 100644 --- a/packages/rln/README.md +++ b/packages/rln/README.md @@ -12,6 +12,18 @@ This package provides RLN functionality for the Waku protocol, enabling rate-lim npm install @waku/rln ``` +## Smart Contract Type Generation + +We use `wagmi` to generate TypeScript bindings for interacting with the RLN smart contracts. + +When changes are pushed to the `waku-rlnv2-contract` repository, run the following script to fetch and build the latest contracts and generate the TypeScript bindings: + +``` +npm run setup:contract-abi +``` + +Note that we commit/bundle the generated typings, so it's not necessary to run this script unless the contracts are updated. + ## Usage ```typescript @@ -20,11 +32,6 @@ import { RLN } from '@waku/rln'; // Usage examples coming soon ``` -## Constants - -- Implementation contract: 0xde2260ca49300357d5af4153cda0d18f7b3ea9b3 -- Proxy contract: 0xb9cd878c90e49f797b4431fbf4fb333108cb90e6 - ## License -MIT OR Apache-2.0 \ No newline at end of file +MIT OR Apache-2.0 diff --git a/packages/rln/generate_contract_abi.sh b/packages/rln/generate_contract_abi.sh new file mode 100755 index 0000000000..3a41c7c4fa --- /dev/null +++ b/packages/rln/generate_contract_abi.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -e + +# Script to generate contract ABIs from waku-rlnv2-contract + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONTRACT_DIR="$SCRIPT_DIR/waku-rlnv2-contract" +REPO_URL="git@github.com:waku-org/waku-rlnv2-contract.git" + +echo "📦 Setting up waku-rlnv2-contract..." + +# Remove existing directory if it exists +if [ -d "$CONTRACT_DIR" ]; then + echo "🗑️ Removing existing waku-rlnv2-contract directory..." + rm -rf "$CONTRACT_DIR" +fi + +# Clone the repository +echo "📥 Cloning waku-rlnv2-contract..." +git clone "$REPO_URL" "$CONTRACT_DIR" + +# Navigate to contract directory +cd "$CONTRACT_DIR" + +# Install dependencies +echo "📦 Installing dependencies..." +npm install + +# Build contracts with Foundry +echo "🔨 Building contracts with Foundry..." +forge build + +# Navigate back to rln package +cd "$SCRIPT_DIR" + +# Generate ABIs with wagmi +echo "⚙️ Generating ABIs with wagmi..." +npx wagmi generate + +echo "✅ Contract ABIs generated successfully!" +echo "📄 Output: src/contract/wagmi/generated.ts" diff --git a/packages/rln/karma.conf.cjs b/packages/rln/karma.conf.cjs index f922aa1d9c..6befe4047b 100644 --- a/packages/rln/karma.conf.cjs +++ b/packages/rln/karma.conf.cjs @@ -38,9 +38,19 @@ module.exports = function (config) { watched: false, type: "wasm", nocache: true + }, + { + pattern: "../../node_modules/@waku/zerokit-rln-wasm-utils/*.wasm", + included: false, + served: true, + watched: false, + type: "wasm", + nocache: true } ], + exclude: process.env.CI ? ["src/**/*.integration.spec.ts"] : [], + preprocessors: { "src/**/*.spec.ts": ["webpack"] }, @@ -82,6 +92,12 @@ module.exports = function (config) { __dirname, "../../node_modules/@waku/zerokit-rln-wasm/rln_wasm_bg.wasm" ), + "/base/rln_wasm_utils_bg.wasm": + "/absolute" + + path.resolve( + __dirname, + "../../node_modules/@waku/zerokit-rln-wasm-utils/rln_wasm_utils_bg.wasm" + ), "/base/rln.wasm": "/absolute" + path.resolve(__dirname, "src/resources/rln.wasm"), "/base/rln_final.zkey": diff --git a/packages/rln/package.json b/packages/rln/package.json index 6d1962b6a0..2b479d65e0 100644 --- a/packages/rln/package.json +++ b/packages/rln/package.json @@ -43,7 +43,8 @@ "watch:build": "tsc -p tsconfig.json -w", "watch:test": "mocha --watch", "prepublish": "npm run build", - "reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build" + "reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build", + "setup:contract-abi": "./generate_contract_abi.sh" }, "engines": { "node": ">=22" @@ -54,12 +55,12 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@types/chai": "^5.0.1", "@types/chai-spies": "^1.0.6", - "@waku/interfaces": "0.0.34", "@types/deep-equal-in-any-order": "^1.0.4", "@types/lodash": "^4.17.15", "@types/sinon": "^17.0.3", + "@wagmi/cli": "^2.7.0", "@waku/build-utils": "^1.0.0", - "@waku/message-encryption": "^0.0.38", + "@waku/message-encryption": "^0.0.37", "deep-equal-in-any-order": "^2.0.6", "fast-check": "^3.23.2", "rollup-plugin-copy": "^3.5.0" @@ -76,18 +77,20 @@ ], "dependencies": { "@chainsafe/bls-keystore": "3.0.0", + "@noble/hashes": "^1.2.0", + "@wagmi/core": "^2.22.1", "@waku/core": "^0.0.40", "@waku/utils": "^0.0.27", - "@noble/hashes": "^1.2.0", "@waku/zerokit-rln-wasm": "^0.2.1", - "ethereum-cryptography": "^3.1.0", - "ethers": "^5.7.2", - "lodash": "^4.17.21", - "uuid": "^11.0.5", + "@waku/zerokit-rln-wasm-utils": "^0.1.0", "chai": "^5.1.2", "chai-as-promised": "^8.0.1", "chai-spies": "^1.1.0", "chai-subset": "^1.6.0", - "sinon": "^19.0.2" + "ethereum-cryptography": "^3.1.0", + "lodash": "^4.17.21", + "sinon": "^19.0.2", + "uuid": "^11.0.5", + "viem": "^2.38.4" } } diff --git a/packages/rln/src/contract/abi/price_calculator.ts b/packages/rln/src/contract/abi/price_calculator.ts deleted file mode 100644 index 8199e7764b..0000000000 --- a/packages/rln/src/contract/abi/price_calculator.ts +++ /dev/null @@ -1,93 +0,0 @@ -export const PRICE_CALCULATOR_ABI = [ - { - inputs: [ - { internalType: "address", name: "_token", type: "address" }, - { - internalType: "uint256", - name: "_pricePerMessagePerEpoch", - type: "uint256" - } - ], - stateMutability: "nonpayable", - type: "constructor" - }, - { inputs: [], name: "OnlyTokensAllowed", type: "error" }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "previousOwner", - type: "address" - }, - { - indexed: true, - internalType: "address", - name: "newOwner", - type: "address" - } - ], - name: "OwnershipTransferred", - type: "event" - }, - { - inputs: [{ internalType: "uint32", name: "_rateLimit", type: "uint32" }], - name: "calculate", - outputs: [ - { internalType: "address", name: "", type: "address" }, - { internalType: "uint256", name: "", type: "uint256" } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "owner", - outputs: [{ internalType: "address", name: "", type: "address" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "pricePerMessagePerEpoch", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "renounceOwnership", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "address", name: "_token", type: "address" }, - { - internalType: "uint256", - name: "_pricePerMessagePerEpoch", - type: "uint256" - } - ], - name: "setTokenAndPrice", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [], - name: "token", - outputs: [{ internalType: "address", name: "", type: "address" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [{ internalType: "address", name: "newOwner", type: "address" }], - name: "transferOwnership", - outputs: [], - stateMutability: "nonpayable", - type: "function" - } -]; diff --git a/packages/rln/src/contract/abi/rln.ts b/packages/rln/src/contract/abi/rln.ts deleted file mode 100644 index 858ec25de5..0000000000 --- a/packages/rln/src/contract/abi/rln.ts +++ /dev/null @@ -1,646 +0,0 @@ -export const RLN_ABI = [ - { inputs: [], stateMutability: "nonpayable", type: "constructor" }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "CannotEraseActiveMembership", - type: "error" - }, - { inputs: [], name: "CannotExceedMaxTotalRateLimit", type: "error" }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "CannotExtendNonGracePeriodMembership", - type: "error" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "InvalidIdCommitment", - type: "error" - }, - { inputs: [], name: "InvalidMembershipRateLimit", type: "error" }, - { - inputs: [ - { internalType: "uint256", name: "startIndex", type: "uint256" }, - { internalType: "uint256", name: "endIndex", type: "uint256" } - ], - name: "InvalidPaginationQuery", - type: "error" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "MembershipDoesNotExist", - type: "error" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "NonHolderCannotEraseGracePeriodMembership", - type: "error" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "NonHolderCannotExtend", - type: "error" - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "address", - name: "previousAdmin", - type: "address" - }, - { - indexed: false, - internalType: "address", - name: "newAdmin", - type: "address" - } - ], - name: "AdminChanged", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "beacon", - type: "address" - } - ], - name: "BeaconUpgraded", - type: "event" - }, - { - anonymous: false, - inputs: [ - { indexed: false, internalType: "uint8", name: "version", type: "uint8" } - ], - name: "Initialized", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "uint256", - name: "idCommitment", - type: "uint256" - }, - { - indexed: false, - internalType: "uint32", - name: "membershipRateLimit", - type: "uint32" - }, - { indexed: false, internalType: "uint32", name: "index", type: "uint32" } - ], - name: "MembershipErased", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "uint256", - name: "idCommitment", - type: "uint256" - }, - { - indexed: false, - internalType: "uint32", - name: "membershipRateLimit", - type: "uint32" - }, - { indexed: false, internalType: "uint32", name: "index", type: "uint32" } - ], - name: "MembershipExpired", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "uint256", - name: "idCommitment", - type: "uint256" - }, - { - indexed: false, - internalType: "uint32", - name: "membershipRateLimit", - type: "uint32" - }, - { indexed: false, internalType: "uint32", name: "index", type: "uint32" }, - { - indexed: false, - internalType: "uint256", - name: "newGracePeriodStartTimestamp", - type: "uint256" - } - ], - name: "MembershipExtended", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "uint256", - name: "idCommitment", - type: "uint256" - }, - { - indexed: false, - internalType: "uint256", - name: "membershipRateLimit", - type: "uint256" - }, - { indexed: false, internalType: "uint32", name: "index", type: "uint32" } - ], - name: "MembershipRegistered", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "previousOwner", - type: "address" - }, - { - indexed: true, - internalType: "address", - name: "newOwner", - type: "address" - } - ], - name: "OwnershipTransferred", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "implementation", - type: "address" - } - ], - name: "Upgraded", - type: "event" - }, - { - inputs: [], - name: "MAX_MEMBERSHIP_SET_SIZE", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "MERKLE_TREE_DEPTH", - outputs: [{ internalType: "uint8", name: "", type: "uint8" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "Q", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "activeDurationForNewMemberships", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "currentTotalRateLimit", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "deployedBlockNumber", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "address", name: "holder", type: "address" }, - { internalType: "address", name: "token", type: "address" } - ], - name: "depositsToWithdraw", - outputs: [{ internalType: "uint256", name: "balance", type: "uint256" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint256[]", name: "idCommitments", type: "uint256[]" } - ], - name: "eraseMemberships", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "uint256[]", name: "idCommitments", type: "uint256[]" }, - { internalType: "bool", name: "eraseFromMembershipSet", type: "bool" } - ], - name: "eraseMemberships", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "uint256[]", name: "idCommitments", type: "uint256[]" } - ], - name: "extendMemberships", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "getMembershipInfo", - outputs: [ - { internalType: "uint32", name: "", type: "uint32" }, - { internalType: "uint32", name: "", type: "uint32" }, - { internalType: "uint256", name: "", type: "uint256" } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [{ internalType: "uint40", name: "index", type: "uint40" }], - name: "getMerkleProof", - outputs: [{ internalType: "uint256[20]", name: "", type: "uint256[20]" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint32", name: "startIndex", type: "uint32" }, - { internalType: "uint32", name: "endIndex", type: "uint32" } - ], - name: "getRateCommitmentsInRangeBoundsInclusive", - outputs: [{ internalType: "uint256[]", name: "", type: "uint256[]" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "gracePeriodDurationForNewMemberships", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [{ internalType: "uint256", name: "", type: "uint256" }], - name: "indicesOfLazilyErasedMemberships", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "address", name: "_priceCalculator", type: "address" }, - { internalType: "uint32", name: "_maxTotalRateLimit", type: "uint32" }, - { - internalType: "uint32", - name: "_minMembershipRateLimit", - type: "uint32" - }, - { - internalType: "uint32", - name: "_maxMembershipRateLimit", - type: "uint32" - }, - { internalType: "uint32", name: "_activeDuration", type: "uint32" }, - { internalType: "uint32", name: "_gracePeriod", type: "uint32" } - ], - name: "initialize", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "_idCommitment", type: "uint256" } - ], - name: "isExpired", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "_idCommitment", type: "uint256" } - ], - name: "isInGracePeriod", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "isInMembershipSet", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "isValidIdCommitment", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "pure", - type: "function" - }, - { - inputs: [{ internalType: "uint32", name: "rateLimit", type: "uint32" }], - name: "isValidMembershipRateLimit", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "maxMembershipRateLimit", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "maxTotalRateLimit", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "_idCommitment", type: "uint256" } - ], - name: "membershipExpirationTimestamp", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" } - ], - name: "memberships", - outputs: [ - { internalType: "uint256", name: "depositAmount", type: "uint256" }, - { internalType: "uint32", name: "activeDuration", type: "uint32" }, - { - internalType: "uint256", - name: "gracePeriodStartTimestamp", - type: "uint256" - }, - { internalType: "uint32", name: "gracePeriodDuration", type: "uint32" }, - { internalType: "uint32", name: "rateLimit", type: "uint32" }, - { internalType: "uint32", name: "index", type: "uint32" }, - { internalType: "address", name: "holder", type: "address" }, - { internalType: "address", name: "token", type: "address" } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "merkleTree", - outputs: [ - { internalType: "uint40", name: "maxIndex", type: "uint40" }, - { internalType: "uint40", name: "numberOfLeaves", type: "uint40" } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "minMembershipRateLimit", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "nextFreeIndex", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "owner", - outputs: [{ internalType: "address", name: "", type: "address" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "priceCalculator", - outputs: [ - { internalType: "contract IPriceCalculator", name: "", type: "address" } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [], - name: "proxiableUUID", - outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { internalType: "uint256", name: "idCommitment", type: "uint256" }, - { internalType: "uint32", name: "rateLimit", type: "uint32" }, - { - internalType: "uint256[]", - name: "idCommitmentsToErase", - type: "uint256[]" - } - ], - name: "register", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "address", name: "owner", type: "address" }, - { internalType: "uint256", name: "deadline", type: "uint256" }, - { internalType: "uint8", name: "v", type: "uint8" }, - { internalType: "bytes32", name: "r", type: "bytes32" }, - { internalType: "bytes32", name: "s", type: "bytes32" }, - { internalType: "uint256", name: "idCommitment", type: "uint256" }, - { internalType: "uint32", name: "rateLimit", type: "uint32" }, - { - internalType: "uint256[]", - name: "idCommitmentsToErase", - type: "uint256[]" - } - ], - name: "registerWithPermit", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [], - name: "renounceOwnership", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [], - name: "root", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint32", - name: "_activeDurationForNewMembership", - type: "uint32" - } - ], - name: "setActiveDuration", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "uint32", - name: "_gracePeriodDurationForNewMembership", - type: "uint32" - } - ], - name: "setGracePeriodDuration", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "uint32", - name: "_maxMembershipRateLimit", - type: "uint32" - } - ], - name: "setMaxMembershipRateLimit", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "uint32", name: "_maxTotalRateLimit", type: "uint32" } - ], - name: "setMaxTotalRateLimit", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "uint32", - name: "_minMembershipRateLimit", - type: "uint32" - } - ], - name: "setMinMembershipRateLimit", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "address", name: "_priceCalculator", type: "address" } - ], - name: "setPriceCalculator", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [{ internalType: "address", name: "newOwner", type: "address" }], - name: "transferOwnership", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "address", name: "newImplementation", type: "address" } - ], - name: "upgradeTo", - outputs: [], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { internalType: "address", name: "newImplementation", type: "address" }, - { internalType: "bytes", name: "data", type: "bytes" } - ], - name: "upgradeToAndCall", - outputs: [], - stateMutability: "payable", - type: "function" - }, - { - inputs: [{ internalType: "address", name: "token", type: "address" }], - name: "withdraw", - outputs: [], - stateMutability: "nonpayable", - type: "function" - } -]; diff --git a/packages/rln/src/contract/constants.ts b/packages/rln/src/contract/constants.ts index 7869e65bc6..9e895424d1 100644 --- a/packages/rln/src/contract/constants.ts +++ b/packages/rln/src/contract/constants.ts @@ -1,16 +1,15 @@ -import { PRICE_CALCULATOR_ABI } from "./abi/price_calculator.js"; -import { RLN_ABI } from "./abi/rln.js"; +import { linearPriceCalculatorAbi, wakuRlnV2Abi } from "./wagmi/generated.js"; export const RLN_CONTRACT = { chainId: 59141, address: "0xb9cd878c90e49f797b4431fbf4fb333108cb90e6", - abi: RLN_ABI + abi: wakuRlnV2Abi }; export const PRICE_CALCULATOR_CONTRACT = { chainId: 59141, address: "0xBcfC0660Df69f53ab409F32bb18A3fb625fcE644", - abi: PRICE_CALCULATOR_ABI + abi: linearPriceCalculatorAbi }; /** diff --git a/packages/rln/src/contract/price_calculator.spec.ts b/packages/rln/src/contract/price_calculator.spec.ts index 2b36585509..e2fa53b1ec 100644 --- a/packages/rln/src/contract/price_calculator.spec.ts +++ b/packages/rln/src/contract/price_calculator.spec.ts @@ -1,28 +1,39 @@ import { expect, use } from "chai"; import chaiAsPromised from "chai-as-promised"; -import { ethers } from "ethers"; import sinon from "sinon"; import { RLNBaseContract } from "./rln_base_contract.js"; use(chaiAsPromised); -function createMockRLNBaseContract(provider: any): RLNBaseContract { +function createMockRLNBaseContract( + mockContract: any, + mockRpcClient: any +): RLNBaseContract { const dummy = Object.create(RLNBaseContract.prototype); - dummy.contract = { provider }; + dummy.contract = mockContract; + dummy.rpcClient = mockRpcClient; return dummy as RLNBaseContract; } describe("RLNBaseContract.getPriceForRateLimit (unit)", function () { - let provider: any; - let calculateStub: sinon.SinonStub; - let mockContractFactory: any; + let mockContract: any; + let mockRpcClient: any; + let priceCalculatorReadStub: sinon.SinonStub; + let readContractStub: sinon.SinonStub; beforeEach(() => { - provider = {}; - calculateStub = sinon.stub(); - mockContractFactory = function () { - return { calculate: calculateStub }; + priceCalculatorReadStub = sinon.stub(); + readContractStub = sinon.stub(); + + mockContract = { + read: { + priceCalculator: priceCalculatorReadStub + } + }; + + mockRpcClient = { + readContract: readContractStub }; }); @@ -32,35 +43,53 @@ describe("RLNBaseContract.getPriceForRateLimit (unit)", function () { it("returns token and price for valid calculate", async () => { const fakeToken = "0x1234567890abcdef1234567890abcdef12345678"; - const fakePrice = ethers.BigNumber.from(42); - calculateStub.resolves([fakeToken, fakePrice]); + const fakePrice = 42n; + const priceCalculatorAddress = "0xabcdef1234567890abcdef1234567890abcdef12"; + + priceCalculatorReadStub.resolves(priceCalculatorAddress); + readContractStub.resolves([fakeToken, fakePrice]); + + const rlnBase = createMockRLNBaseContract(mockContract, mockRpcClient); + const result = await rlnBase.getPriceForRateLimit(20); - const rlnBase = createMockRLNBaseContract(provider); - const result = await rlnBase.getPriceForRateLimit(20, mockContractFactory); expect(result.token).to.equal(fakeToken); - expect(result.price).to.not.be.null; - if (result.price) { - expect(result.price.eq(fakePrice)).to.be.true; - } - expect(calculateStub.calledOnceWith(20)).to.be.true; + expect(result.price).to.equal(fakePrice); + expect(priceCalculatorReadStub.calledOnce).to.be.true; + expect(readContractStub.calledOnce).to.be.true; + + const readContractCall = readContractStub.getCall(0); + expect(readContractCall.args[0]).to.deep.include({ + address: priceCalculatorAddress, + functionName: "calculate", + args: [20] + }); }); it("throws if calculate throws", async () => { - calculateStub.rejects(new Error("fail")); + const priceCalculatorAddress = "0xabcdef1234567890abcdef1234567890abcdef12"; + + priceCalculatorReadStub.resolves(priceCalculatorAddress); + readContractStub.rejects(new Error("fail")); + + const rlnBase = createMockRLNBaseContract(mockContract, mockRpcClient); + await expect(rlnBase.getPriceForRateLimit(20)).to.be.rejectedWith("fail"); - const rlnBase = createMockRLNBaseContract(provider); - await expect( - rlnBase.getPriceForRateLimit(20, mockContractFactory) - ).to.be.rejectedWith("fail"); - expect(calculateStub.calledOnceWith(20)).to.be.true; + expect(priceCalculatorReadStub.calledOnce).to.be.true; + expect(readContractStub.calledOnce).to.be.true; }); - it("throws if calculate returns malformed data", async () => { - calculateStub.resolves([null, null]); + it("returns null values if calculate returns malformed data", async () => { + const priceCalculatorAddress = "0xabcdef1234567890abcdef1234567890abcdef12"; + + priceCalculatorReadStub.resolves(priceCalculatorAddress); + readContractStub.resolves([null, null]); + + const rlnBase = createMockRLNBaseContract(mockContract, mockRpcClient); + const result = await rlnBase.getPriceForRateLimit(20); - const rlnBase = createMockRLNBaseContract(provider); - const result = await rlnBase.getPriceForRateLimit(20, mockContractFactory); expect(result.token).to.be.null; expect(result.price).to.be.null; + expect(priceCalculatorReadStub.calledOnce).to.be.true; + expect(readContractStub.calledOnce).to.be.true; }); }); diff --git a/packages/rln/src/contract/rln_base_contract.ts b/packages/rln/src/contract/rln_base_contract.ts index 9934360be3..75c4f7a4ef 100644 --- a/packages/rln/src/contract/rln_base_contract.ts +++ b/packages/rln/src/contract/rln_base_contract.ts @@ -1,92 +1,74 @@ import { Logger } from "@waku/utils"; -import { ethers } from "ethers"; +import { + type Address, + decodeEventLog, + getContract, + type GetContractReturnType, + type Hash, + type PublicClient, + type WalletClient +} from "viem"; import { IdentityCredential } from "../identity.js"; -import { DecryptedCredentials } from "../keystore/types.js"; +import type { DecryptedCredentials } from "../keystore/types.js"; +import type { RpcClient } from "../utils/index.js"; -import { RLN_ABI } from "./abi/rln.js"; import { DEFAULT_RATE_LIMIT, - PRICE_CALCULATOR_CONTRACT, - RATE_LIMIT_PARAMS + RATE_LIMIT_PARAMS, + RLN_CONTRACT } from "./constants.js"; import { - CustomQueryOptions, - FetchMembersOptions, - Member, MembershipInfo, - MembershipRegisteredEvent, MembershipState, - RLNContractInitOptions + RLNContractOptions } from "./types.js"; +import { iPriceCalculatorAbi, wakuRlnV2Abi } from "./wagmi/generated.js"; const log = new Logger("rln:contract:base"); export class RLNBaseContract { - public contract: ethers.Contract; - private deployBlock: undefined | number; + public contract: GetContractReturnType< + typeof wakuRlnV2Abi, + PublicClient | WalletClient + >; + public rpcClient: RpcClient; private rateLimit: number; private minRateLimit?: number; private maxRateLimit?: number; - protected _members: Map = new Map(); - private _membersFilter: ethers.EventFilter; - private _membershipErasedFilter: ethers.EventFilter; - private _membersExpiredFilter: ethers.EventFilter; - /** * Private constructor for RLNBaseContract. Use static create() instead. */ - protected constructor(options: RLNContractInitOptions) { - const { - address, - signer, - rateLimit = DEFAULT_RATE_LIMIT, - contract - } = options; + protected constructor(options: RLNContractOptions) { + const { address, rpcClient, rateLimit = DEFAULT_RATE_LIMIT } = options; log.info("Initializing RLNBaseContract", { address, rateLimit }); - this.contract = contract || new ethers.Contract(address, RLN_ABI, signer); + this.rpcClient = rpcClient; + this.contract = getContract({ + address, + abi: wakuRlnV2Abi, + client: this.rpcClient + }); this.rateLimit = rateLimit; - - try { - log.info("Setting up event filters"); - // Initialize event filters - this._membersFilter = this.contract.filters.MembershipRegistered(); - this._membershipErasedFilter = this.contract.filters.MembershipErased(); - this._membersExpiredFilter = this.contract.filters.MembershipExpired(); - log.info("Event filters initialized successfully"); - } catch (error) { - log.error("Failed to initialize event filters", { error }); - throw new Error( - "Failed to initialize event filters: " + (error as Error).message - ); - } - - // Initialize members and subscriptions - this.fetchMembers() - .then(() => { - this.subscribeToMembers(); - }) - .catch((error) => { - log.error("Failed to initialize members", { error }); - }); } /** * Static async factory to create and initialize RLNBaseContract */ public static async create( - options: RLNContractInitOptions + options: RLNContractOptions ): Promise { const instance = new RLNBaseContract(options); + const [min, max] = await Promise.all([ - instance.contract.minMembershipRateLimit(), - instance.contract.maxMembershipRateLimit() + instance.contract.read.minMembershipRateLimit(), + instance.contract.read.maxMembershipRateLimit() ]); - instance.minRateLimit = ethers.BigNumber.from(min).toNumber(); - instance.maxRateLimit = ethers.BigNumber.from(max).toNumber(); + + instance.minRateLimit = min; + instance.maxRateLimit = max; instance.validateRateLimit(instance.rateLimit); return instance; @@ -106,13 +88,6 @@ export class RLNBaseContract { return this.contract.address; } - /** - * Gets the contract provider - */ - public get provider(): ethers.providers.Provider { - return this.contract.provider; - } - /** * Gets the minimum allowed rate limit (cached) */ @@ -136,8 +111,7 @@ export class RLNBaseContract { * @returns Promise The maximum total rate limit in messages per epoch */ public async getMaxTotalRateLimit(): Promise { - const maxTotalRate = await this.contract.maxTotalRateLimit(); - return maxTotalRate.toNumber(); + return await this.contract.read.maxTotalRateLimit(); } /** @@ -145,8 +119,7 @@ export class RLNBaseContract { * @returns Promise The current total rate limit usage in messages per epoch */ public async getCurrentTotalRateLimit(): Promise { - const currentTotal = await this.contract.currentTotalRateLimit(); - return currentTotal.toNumber(); + return Number(await this.contract.read.currentTotalRateLimit()); } /** @@ -154,11 +127,10 @@ export class RLNBaseContract { * @returns Promise The remaining rate limit that can be allocated */ public async getRemainingTotalRateLimit(): Promise { - const [maxTotal, currentTotal] = await Promise.all([ - this.contract.maxTotalRateLimit(), - this.contract.currentTotalRateLimit() - ]); - return Number(maxTotal) - Number(currentTotal); + return ( + (await this.contract.read.maxTotalRateLimit()) - + Number(await this.contract.read.currentTotalRateLimit()) + ); } /** @@ -170,233 +142,35 @@ export class RLNBaseContract { this.rateLimit = newRateLimit; } - public get members(): Member[] { - const sortedMembers = Array.from(this._members.values()).sort( - (left, right) => left.index.toNumber() - right.index.toNumber() - ); - return sortedMembers; - } - - public async fetchMembers(options: FetchMembersOptions = {}): Promise { - const registeredMemberEvents = await RLNBaseContract.queryFilter( - this.contract, - { - fromBlock: this.deployBlock, - ...options, - membersFilter: this.membersFilter - } - ); - const removedMemberEvents = await RLNBaseContract.queryFilter( - this.contract, - { - fromBlock: this.deployBlock, - ...options, - membersFilter: this.membershipErasedFilter - } - ); - const expiredMemberEvents = await RLNBaseContract.queryFilter( - this.contract, - { - fromBlock: this.deployBlock, - ...options, - membersFilter: this.membersExpiredFilter - } - ); - - const events = [ - ...registeredMemberEvents, - ...removedMemberEvents, - ...expiredMemberEvents - ]; - this.processEvents(events); - } - - public static async queryFilter( - contract: ethers.Contract, - options: CustomQueryOptions - ): Promise { - const FETCH_CHUNK = 5; - const BLOCK_RANGE = 3000; - - const { - fromBlock, - membersFilter, - fetchRange = BLOCK_RANGE, - fetchChunks = FETCH_CHUNK - } = options; - - if (fromBlock === undefined) { - return contract.queryFilter(membersFilter); - } - - if (!contract.provider) { - throw Error("No provider found on the contract."); - } - - const toBlock = await contract.provider.getBlockNumber(); - - if (toBlock - fromBlock < fetchRange) { - return contract.queryFilter(membersFilter, fromBlock, toBlock); - } - - const events: ethers.Event[][] = []; - const chunks = RLNBaseContract.splitToChunks( - fromBlock, - toBlock, - fetchRange - ); - - for (const portion of RLNBaseContract.takeN<[number, number]>( - chunks, - fetchChunks - )) { - const promises = portion.map(([left, right]) => - RLNBaseContract.ignoreErrors( - contract.queryFilter(membersFilter, left, right), - [] - ) - ); - const fetchedEvents = await Promise.all(promises); - events.push(fetchedEvents.flatMap((v) => v)); - } - - return events.flatMap((v) => v); - } - - public processEvents(events: ethers.Event[]): void { - const toRemoveTable = new Map(); - const toInsertTable = new Map(); - - events.forEach((evt) => { - if (!evt.args) { - return; - } - - if ( - evt.event === "MembershipErased" || - evt.event === "MembershipExpired" - ) { - let index = evt.args.index; - - if (!index) { - return; - } - - if (typeof index === "number" || typeof index === "string") { - index = ethers.BigNumber.from(index); - } - - const toRemoveVal = toRemoveTable.get(evt.blockNumber); - if (toRemoveVal != undefined) { - toRemoveVal.push(index.toNumber()); - toRemoveTable.set(evt.blockNumber, toRemoveVal); - } else { - toRemoveTable.set(evt.blockNumber, [index.toNumber()]); - } - } else if (evt.event === "MembershipRegistered") { - let eventsPerBlock = toInsertTable.get(evt.blockNumber); - if (eventsPerBlock == undefined) { - eventsPerBlock = []; - } - - eventsPerBlock.push(evt); - toInsertTable.set(evt.blockNumber, eventsPerBlock); - } - }); - } - - public static splitToChunks( - from: number, - to: number, - step: number - ): Array<[number, number]> { - const chunks: Array<[number, number]> = []; - - let left = from; - while (left < to) { - const right = left + step < to ? left + step : to; - - chunks.push([left, right] as [number, number]); - - left = right; - } - - return chunks; - } - - public static *takeN(array: T[], size: number): Iterable { - let start = 0; - - while (start < array.length) { - const portion = array.slice(start, start + size); - - yield portion; - - start += size; - } - } - - public static async ignoreErrors( - promise: Promise, - defaultValue: T - ): Promise { - try { - return await promise; - } catch (err: unknown) { - if (err instanceof Error) { - log.info(`Ignoring an error during query: ${err.message}`); - } else { - log.info(`Ignoring an unknown error during query`); - } - return defaultValue; - } + /** + * Gets the Merkle tree root for RLN proof verification + * @returns Promise The Merkle tree root + * + */ + public async getMerkleRoot(): Promise { + return this.contract.read.root(); } - public subscribeToMembers(): void { - this.contract.on( - this.membersFilter, - ( - _idCommitment: bigint, - _membershipRateLimit: ethers.BigNumber, - _index: ethers.BigNumber, - event: ethers.Event - ) => { - this.processEvents([event]); - } - ); - - this.contract.on( - this.membershipErasedFilter, - ( - _idCommitment: bigint, - _membershipRateLimit: ethers.BigNumber, - _index: ethers.BigNumber, - event: ethers.Event - ) => { - this.processEvents([event]); - } - ); - - this.contract.on( - this.membersExpiredFilter, - ( - _idCommitment: bigint, - _membershipRateLimit: ethers.BigNumber, - _index: ethers.BigNumber, - event: ethers.Event - ) => { - this.processEvents([event]); - } - ); + /** + * Gets the Merkle proof for a member at a given index + * @param index The index of the member in the membership set + * @returns Promise Array of 20 Merkle proof elements + * + */ + public async getMerkleProof(index: number): Promise { + return await this.contract.read.getMerkleProof([index]); } public async getMembershipInfo( idCommitmentBigInt: bigint ): Promise { try { - const membershipData = - await this.contract.memberships(idCommitmentBigInt); - const currentBlock = await this.contract.provider.getBlockNumber(); + const membershipData = await this.contract.read.memberships([ + idCommitmentBigInt + ]); + + const currentBlock = await this.rpcClient.getBlockNumber(); + const [ depositAmount, activeDuration, @@ -408,12 +182,13 @@ export class RLNBaseContract { token ] = membershipData; - const gracePeriodEnd = gracePeriodStartTimestamp.add(gracePeriodDuration); + const gracePeriodEnd = + Number(gracePeriodStartTimestamp) + Number(gracePeriodDuration); let state: MembershipState; - if (currentBlock < gracePeriodStartTimestamp.toNumber()) { + if (currentBlock < Number(gracePeriodStartTimestamp)) { state = MembershipState.Active; - } else if (currentBlock < gracePeriodEnd.toNumber()) { + } else if (currentBlock < gracePeriodEnd) { state = MembershipState.GracePeriod; } else { state = MembershipState.Expired; @@ -422,9 +197,9 @@ export class RLNBaseContract { return { index, idCommitment: idCommitmentBigInt.toString(), - rateLimit: Number(rateLimit), - startBlock: gracePeriodStartTimestamp.toNumber(), - endBlock: gracePeriodEnd.toNumber(), + rateLimit: rateLimit, + startBlock: Number(gracePeriodStartTimestamp), + endBlock: gracePeriodEnd, state, depositAmount, activeDuration, @@ -438,43 +213,75 @@ export class RLNBaseContract { } } - public async extendMembership( - idCommitmentBigInt: bigint - ): Promise { - const tx = await this.contract.extendMemberships([idCommitmentBigInt]); - await tx.wait(); - return tx; + public async extendMembership(idCommitmentBigInt: bigint): Promise { + if (!this.rpcClient.account) { + throw new Error( + "Failed to extendMembership: no account set in wallet client" + ); + } + try { + await this.contract.simulate.extendMemberships([[idCommitmentBigInt]], { + chain: this.rpcClient.chain, + account: this.rpcClient.account.address + }); + } catch (err) { + throw new Error("Simulating extending membership failed: " + err); + } + const hash = await this.contract.write.extendMemberships( + [[idCommitmentBigInt]], + { + account: this.rpcClient.account, + chain: this.rpcClient.chain + } + ); + + await this.rpcClient.waitForTransactionReceipt({ hash }); + return hash; } public async eraseMembership( idCommitmentBigInt: bigint, eraseFromMembershipSet: boolean = true - ): Promise { + ): Promise { if ( !(await this.isExpired(idCommitmentBigInt)) || !(await this.isInGracePeriod(idCommitmentBigInt)) ) { throw new Error("Membership is not expired or in grace period"); } + if (!this.rpcClient.account) { + throw new Error( + "Failed to eraseMembership: no account set in wallet client" + ); + } - const estimatedGas = await this.contract.estimateGas[ - "eraseMemberships(uint256[],bool)" - ]([idCommitmentBigInt], eraseFromMembershipSet); - const gasLimit = estimatedGas.add(10000); + try { + await this.contract.simulate.eraseMemberships( + [[idCommitmentBigInt], eraseFromMembershipSet], + { + chain: this.rpcClient.chain, + account: this.rpcClient.account.address + } + ); + } catch (err) { + throw new Error("Error simulating eraseMemberships: " + err); + } - const tx = await this.contract["eraseMemberships(uint256[],bool)"]( - [idCommitmentBigInt], - eraseFromMembershipSet, - { gasLimit } + const hash = await this.contract.write.eraseMemberships( + [[idCommitmentBigInt], eraseFromMembershipSet], + { + chain: this.rpcClient.chain, + account: this.rpcClient.account + } ); - await tx.wait(); - return tx; + await this.rpcClient.waitForTransactionReceipt({ hash }); + return hash; } public async registerMembership( idCommitmentBigInt: bigint, rateLimit: number = DEFAULT_RATE_LIMIT - ): Promise { + ): Promise { if ( rateLimit < RATE_LIMIT_PARAMS.MIN_RATE || rateLimit > RATE_LIMIT_PARAMS.MAX_RATE @@ -483,21 +290,72 @@ export class RLNBaseContract { `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}` ); } - return this.contract.register(idCommitmentBigInt, rateLimit, []); + if (!this.rpcClient.account) { + throw new Error( + "Failed to registerMembership: no account set in wallet client" + ); + } + try { + await this.contract.simulate.register( + [idCommitmentBigInt, rateLimit, []], + { + chain: this.rpcClient.chain, + account: this.rpcClient.account.address + } + ); + } catch (err) { + throw new Error("Failed to simulate register membership: " + err); + } + + const hash = await this.contract.write.register( + [idCommitmentBigInt, rateLimit, []], + { + chain: this.rpcClient.chain, + account: this.rpcClient.account + } + ); + await this.rpcClient.waitForTransactionReceipt({ hash }); + return hash; } - public async withdraw(token: string, walletAddress: string): Promise { + /** + * Withdraw deposited tokens after membership is erased. + * The smart contract validates that the sender is the holder of the membership, + * and will only send tokens to that address. + * @param token - Token address to withdraw + */ + public async withdraw(token: string): Promise { + if (!this.rpcClient.account) { + throw new Error("Failed to withdraw: no account set in wallet client"); + } + try { - const tx = await this.contract.withdraw(token, walletAddress); - await tx.wait(); - } catch (error) { - log.error(`Error in withdraw: ${(error as Error).message}`); + await this.contract.simulate.withdraw([token as Address], { + chain: this.rpcClient.chain, + account: this.rpcClient.account.address + }); + } catch (err) { + throw new Error("Error simulating withdraw: " + err); } + + const hash = await this.contract.write.withdraw([token as Address], { + chain: this.rpcClient.chain, + account: this.rpcClient.account + }); + + await this.rpcClient.waitForTransactionReceipt({ hash }); + return hash; } public async registerWithIdentity( identity: IdentityCredential ): Promise { try { + if (!this.rpcClient.account) { + throw new Error( + "Failed to registerWithIdentity: no account set in wallet client" + ); + } + log.info( `Registering identity with rate limit: ${this.rateLimit} messages/epoch` ); @@ -520,62 +378,71 @@ export class RLNBaseContract { ); } - const estimatedGas = await this.contract.estimateGas.register( - identity.IDCommitmentBigInt, - this.rateLimit, - [] + await this.contract.simulate.register( + [identity.IDCommitmentBigInt, this.rateLimit, []], + { + chain: this.rpcClient.chain, + account: this.rpcClient.account.address + } + ); + + const hash: Hash = await this.contract.write.register( + [identity.IDCommitmentBigInt, this.rateLimit, []], + { + chain: this.rpcClient.chain, + account: this.rpcClient.account + } ); - const gasLimit = estimatedGas.add(10000); - - const txRegisterResponse: ethers.ContractTransaction = - await this.contract.register( - identity.IDCommitmentBigInt, - this.rateLimit, - [], - { - gasLimit - } - ); - const txRegisterReceipt = await txRegisterResponse.wait(); + const txRegisterReceipt = await this.rpcClient.waitForTransactionReceipt({ + hash + }); - if (txRegisterReceipt.status === 0) { + if (txRegisterReceipt.status === "reverted") { throw new Error("Transaction failed on-chain"); } - const memberRegistered = txRegisterReceipt.events?.find( - (event: ethers.Event) => event.event === "MembershipRegistered" - ); + // Parse MembershipRegistered event from logs + const memberRegisteredLog = txRegisterReceipt.logs.find((log) => { + try { + const decoded = decodeEventLog({ + abi: wakuRlnV2Abi, + data: log.data, + topics: log.topics + }); + return decoded.eventName === "MembershipRegistered"; + } catch { + return false; + } + }); - if (!memberRegistered || !memberRegistered.args) { + if (!memberRegisteredLog) { log.error( "Failed to register membership: No MembershipRegistered event found" ); return undefined; } - const decodedData: MembershipRegisteredEvent = { - idCommitment: memberRegistered.args.idCommitment, - membershipRateLimit: memberRegistered.args.membershipRateLimit, - index: memberRegistered.args.index - }; + // Decode the event + const decoded = decodeEventLog({ + abi: wakuRlnV2Abi, + data: memberRegisteredLog.data, + topics: memberRegisteredLog.topics, + eventName: "MembershipRegistered" + }); log.info( - `Successfully registered membership with index ${decodedData.index} ` + - `and rate limit ${decodedData.membershipRateLimit}` + `Successfully registered membership with index ${decoded.args.index} ` + + `and rate limit ${decoded.args.membershipRateLimit}` ); - const network = await this.contract.provider.getNetwork(); - const address = this.contract.address; - const membershipId = Number(decodedData.index); - return { identity, membership: { - address, - treeIndex: membershipId, - chainId: network.chainId.toString(), - rateLimit: decodedData.membershipRateLimit.toNumber() + address: this.contract.address, + treeIndex: decoded.args.index, + chainId: String(RLN_CONTRACT.chainId), + rateLimit: Number(decoded.args.membershipRateLimit) } }; } catch (error) { @@ -608,78 +475,6 @@ export class RLNBaseContract { } } - public async registerWithPermitAndErase( - identity: IdentityCredential, - permit: { - owner: string; - deadline: number; - v: number; - r: string; - s: string; - }, - idCommitmentsToErase: string[] - ): Promise { - try { - log.info( - `Registering identity with permit and rate limit: ${this.rateLimit} messages/epoch` - ); - - const txRegisterResponse: ethers.ContractTransaction = - await this.contract.registerWithPermit( - permit.owner, - permit.deadline, - permit.v, - permit.r, - permit.s, - identity.IDCommitmentBigInt, - this.rateLimit, - idCommitmentsToErase.map((id) => ethers.BigNumber.from(id)) - ); - const txRegisterReceipt = await txRegisterResponse.wait(); - - const memberRegistered = txRegisterReceipt.events?.find( - (event: ethers.Event) => event.event === "MembershipRegistered" - ); - - if (!memberRegistered || !memberRegistered.args) { - log.error( - "Failed to register membership with permit: No MembershipRegistered event found" - ); - return undefined; - } - - const decodedData: MembershipRegisteredEvent = { - idCommitment: memberRegistered.args.idCommitment, - membershipRateLimit: memberRegistered.args.membershipRateLimit, - index: memberRegistered.args.index - }; - - log.info( - `Successfully registered membership with permit. Index: ${decodedData.index}, ` + - `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments` - ); - - const network = await this.contract.provider.getNetwork(); - const address = this.contract.address; - const membershipId = Number(decodedData.index); - - return { - identity, - membership: { - address, - treeIndex: membershipId, - chainId: network.chainId.toString(), - rateLimit: decodedData.membershipRateLimit.toNumber() - } - }; - } catch (error) { - log.error( - `Error in registerWithPermitAndErase: ${(error as Error).message}` - ); - return undefined; - } - } - /** * Validates that the rate limit is within the allowed range (sync) * @throws Error if the rate limit is outside the allowed range @@ -695,50 +490,17 @@ export class RLNBaseContract { } } - private get membersFilter(): ethers.EventFilter { - if (!this._membersFilter) { - throw Error("Members filter was not initialized."); - } - return this._membersFilter; - } - - private get membershipErasedFilter(): ethers.EventFilter { - if (!this._membershipErasedFilter) { - throw Error("MembershipErased filter was not initialized."); - } - return this._membershipErasedFilter; - } - - private get membersExpiredFilter(): ethers.EventFilter { - if (!this._membersExpiredFilter) { - throw Error("MembersExpired filter was not initialized."); - } - return this._membersExpiredFilter; - } - - private async getMemberIndex( - idCommitmentBigInt: bigint - ): Promise { - try { - const events = await this.contract.queryFilter( - this.contract.filters.MembershipRegistered(idCommitmentBigInt) - ); - if (events.length === 0) return undefined; - - // Get the most recent registration event - const event = events[events.length - 1]; - return event.args?.index; - } catch (error) { - return undefined; - } + private async getMemberIndex(idCommitmentBigInt: bigint): Promise { + // Current version of the contract has the index at position 5 in the membership struct + return (await this.contract.read.memberships([idCommitmentBigInt]))[5]; } public async getMembershipStatus( idCommitment: bigint ): Promise<"expired" | "grace" | "active"> { const [isExpired, isInGrace] = await Promise.all([ - this.contract.isExpired(idCommitment), - this.contract.isInGracePeriod(idCommitment) + this.contract.read.isExpired([idCommitment]), + this.contract.read.isInGracePeriod([idCommitment]) ]); if (isExpired) return "expired"; @@ -753,7 +515,7 @@ export class RLNBaseContract { */ public async isExpired(idCommitmentBigInt: bigint): Promise { try { - return await this.contract.isExpired(idCommitmentBigInt); + return await this.contract.read.isExpired([idCommitmentBigInt]); } catch (error) { log.error("Error in isExpired:", error); return false; @@ -767,7 +529,7 @@ export class RLNBaseContract { */ public async isInGracePeriod(idCommitmentBigInt: bigint): Promise { try { - return await this.contract.isInGracePeriod(idCommitmentBigInt); + return await this.contract.read.isInGracePeriod([idCommitmentBigInt]); } catch (error) { log.error("Error in isInGracePeriod:", error); return false; @@ -779,21 +541,18 @@ export class RLNBaseContract { * @param rateLimit The rate limit to calculate the price for * @param contractFactory Optional factory for creating the contract (for testing) */ - public async getPriceForRateLimit( - rateLimit: number, - contractFactory?: typeof import("ethers").Contract - ): Promise<{ + public async getPriceForRateLimit(rateLimit: number): Promise<{ token: string | null; - price: import("ethers").BigNumber | null; + price: bigint | null; }> { - const provider = this.contract.provider; - const ContractCtor = contractFactory || ethers.Contract; - const priceCalculator = new ContractCtor( - PRICE_CALCULATOR_CONTRACT.address, - PRICE_CALCULATOR_CONTRACT.abi, - provider - ); - const [token, price] = await priceCalculator.calculate(rateLimit); + const address = await this.contract.read.priceCalculator(); + const [token, price] = await this.rpcClient.readContract({ + address, + abi: iPriceCalculatorAbi, + functionName: "calculate", + args: [rateLimit] + }); + // Defensive: if token or price is null/undefined, return nulls if (!token || !price) { return { token: null, price: null }; diff --git a/packages/rln/src/contract/token.ts b/packages/rln/src/contract/token.ts new file mode 100644 index 0000000000..7c28fe7f4a --- /dev/null +++ b/packages/rln/src/contract/token.ts @@ -0,0 +1,98 @@ +import { type Address, formatUnits, PublicClient, WalletClient } from "viem"; + +/** + * Minimal ERC20 ABI containing only the functions we need + */ +export const erc20Abi = [ + { + type: "function", + inputs: [{ name: "account", internalType: "address", type: "address" }], + name: "balanceOf", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "decimals", + outputs: [{ name: "", internalType: "uint8", type: "uint8" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "symbol", + outputs: [{ name: "", internalType: "string", type: "string" }], + stateMutability: "view" + } +] as const; + +export interface TokenBalance { + /** Raw balance as bigint */ + raw: bigint; + /** Formatted balance as string (e.g., "100.5") */ + formatted: string; + /** Token decimals */ + decimals: number; + /** Token symbol (if available) */ + symbol?: string; + /** User's wallet address */ + userAddress: Address; +} + +/** + * Gets the token balance for the connected wallet + * @param walletClient The viem wallet client with a connected account + * @param publicClient The viem public client for reading contract state + * @param tokenAddress The ERC20 token contract address + * @returns Promise The token balance information + * @throws Error if no account is connected to the wallet client + * + * @example + * ```typescript + * const { walletClient, publicClient } = await createViemClientsFromWindow(); + * const balance = await getTokenBalance(walletClient, publicClient, "0x..."); + * console.log(`Balance: ${balance.formatted} ${balance.symbol}`); + * ``` + */ +export async function getTokenBalance( + walletClient: WalletClient, + publicClient: PublicClient, + tokenAddress: Address +): Promise { + if (!walletClient.account) { + throw new Error("No account connected to wallet client"); + } + + const userAddress = walletClient.account.address; + + // Read balance, decimals, and symbol in parallel + const [balance, decimals, symbol] = await Promise.all([ + publicClient.readContract({ + address: tokenAddress, + abi: erc20Abi, + functionName: "balanceOf", + args: [userAddress] + }), + publicClient.readContract({ + address: tokenAddress, + abi: erc20Abi, + functionName: "decimals" + }), + publicClient + .readContract({ + address: tokenAddress, + abi: erc20Abi, + functionName: "symbol" + }) + .catch(() => undefined) // Symbol is optional, some tokens don't have it + ]); + + return { + raw: balance, + formatted: formatUnits(balance, decimals), + decimals, + symbol, + userAddress + }; +} diff --git a/packages/rln/src/contract/types.ts b/packages/rln/src/contract/types.ts index bd0247bb6a..479ed412bb 100644 --- a/packages/rln/src/contract/types.ts +++ b/packages/rln/src/contract/types.ts @@ -1,28 +1,22 @@ -import { ethers } from "ethers"; +import { Address } from "viem"; -export interface CustomQueryOptions extends FetchMembersOptions { - membersFilter: ethers.EventFilter; -} +import { RpcClient } from "../utils/index.js"; export type Member = { idCommitment: string; - index: ethers.BigNumber; + index: bigint; }; export interface RLNContractOptions { - signer: ethers.Signer; - address: string; + rpcClient: RpcClient; + address: Address; rateLimit?: number; } -export interface RLNContractInitOptions extends RLNContractOptions { - contract?: ethers.Contract; -} - export interface MembershipRegisteredEvent { idCommitment: string; - membershipRateLimit: ethers.BigNumber; - index: ethers.BigNumber; + membershipRateLimit: bigint; + index: bigint; } export type FetchMembersOptions = { @@ -32,13 +26,13 @@ export type FetchMembersOptions = { }; export interface MembershipInfo { - index: ethers.BigNumber; + index: number; idCommitment: string; rateLimit: number; startBlock: number; endBlock: number; state: MembershipState; - depositAmount: ethers.BigNumber; + depositAmount: bigint; activeDuration: number; gracePeriodDuration: number; holder: string; diff --git a/packages/rln/src/contract/wagmi/generated.ts b/packages/rln/src/contract/wagmi/generated.ts new file mode 100644 index 0000000000..f13fa0c2ce --- /dev/null +++ b/packages/rln/src/contract/wagmi/generated.ts @@ -0,0 +1,977 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IPriceCalculator +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const iPriceCalculatorAbi = [ + { + type: "function", + inputs: [{ name: "_rateLimit", internalType: "uint32", type: "uint32" }], + name: "calculate", + outputs: [ + { name: "", internalType: "address", type: "address" }, + { name: "", internalType: "uint256", type: "uint256" } + ], + stateMutability: "view" + } +] as const; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// LinearPriceCalculator +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const linearPriceCalculatorAbi = [ + { + type: "constructor", + inputs: [ + { name: "_token", internalType: "address", type: "address" }, + { + name: "_pricePerMessagePerEpoch", + internalType: "uint256", + type: "uint256" + } + ], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [{ name: "_rateLimit", internalType: "uint32", type: "uint32" }], + name: "calculate", + outputs: [ + { name: "", internalType: "address", type: "address" }, + { name: "", internalType: "uint256", type: "uint256" } + ], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "owner", + outputs: [{ name: "", internalType: "address", type: "address" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "pricePerMessagePerEpoch", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "_token", internalType: "address", type: "address" }, + { + name: "_pricePerMessagePerEpoch", + internalType: "uint256", + type: "uint256" + } + ], + name: "setTokenAndPrice", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [], + name: "token", + outputs: [{ name: "", internalType: "address", type: "address" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [{ name: "newOwner", internalType: "address", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "previousOwner", + internalType: "address", + type: "address", + indexed: true + }, + { + name: "newOwner", + internalType: "address", + type: "address", + indexed: true + } + ], + name: "OwnershipTransferred" + }, + { type: "error", inputs: [], name: "OnlyTokensAllowed" } +] as const; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MembershipUpgradeable +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const membershipUpgradeableAbi = [ + { + type: "function", + inputs: [], + name: "activeDurationForNewMemberships", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "currentTotalRateLimit", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "holder", internalType: "address", type: "address" }, + { name: "token", internalType: "address", type: "address" } + ], + name: "depositsToWithdraw", + outputs: [{ name: "balance", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "gracePeriodDurationForNewMemberships", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [{ name: "", internalType: "uint256", type: "uint256" }], + name: "indicesOfLazilyErasedMemberships", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "_idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "isExpired", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "_idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "isInGracePeriod", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [{ name: "rateLimit", internalType: "uint32", type: "uint32" }], + name: "isValidMembershipRateLimit", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "maxMembershipRateLimit", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "maxTotalRateLimit", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "_idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "membershipExpirationTimestamp", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "memberships", + outputs: [ + { name: "depositAmount", internalType: "uint256", type: "uint256" }, + { name: "activeDuration", internalType: "uint32", type: "uint32" }, + { + name: "gracePeriodStartTimestamp", + internalType: "uint256", + type: "uint256" + }, + { name: "gracePeriodDuration", internalType: "uint32", type: "uint32" }, + { name: "rateLimit", internalType: "uint32", type: "uint32" }, + { name: "index", internalType: "uint32", type: "uint32" }, + { name: "holder", internalType: "address", type: "address" }, + { name: "token", internalType: "address", type: "address" } + ], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "minMembershipRateLimit", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "nextFreeIndex", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "priceCalculator", + outputs: [ + { name: "", internalType: "contract IPriceCalculator", type: "address" } + ], + stateMutability: "view" + }, + { + type: "event", + anonymous: false, + inputs: [ + { name: "version", internalType: "uint8", type: "uint8", indexed: false } + ], + name: "Initialized" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint32", + type: "uint32", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false } + ], + name: "MembershipErased" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint32", + type: "uint32", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false } + ], + name: "MembershipExpired" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint32", + type: "uint32", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false }, + { + name: "newGracePeriodStartTimestamp", + internalType: "uint256", + type: "uint256", + indexed: false + } + ], + name: "MembershipExtended" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false } + ], + name: "MembershipRegistered" + } +] as const; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WakuRlnV2 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const wakuRlnV2Abi = [ + { type: "constructor", inputs: [], stateMutability: "nonpayable" }, + { + type: "function", + inputs: [], + name: "MAX_MEMBERSHIP_SET_SIZE", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "MERKLE_TREE_DEPTH", + outputs: [{ name: "", internalType: "uint8", type: "uint8" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "Q", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "activeDurationForNewMemberships", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "currentTotalRateLimit", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "deployedBlockNumber", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "holder", internalType: "address", type: "address" }, + { name: "token", internalType: "address", type: "address" } + ], + name: "depositsToWithdraw", + outputs: [{ name: "balance", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "idCommitments", internalType: "uint256[]", type: "uint256[]" } + ], + name: "eraseMemberships", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "idCommitments", internalType: "uint256[]", type: "uint256[]" }, + { name: "eraseFromMembershipSet", internalType: "bool", type: "bool" } + ], + name: "eraseMemberships", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "idCommitments", internalType: "uint256[]", type: "uint256[]" } + ], + name: "extendMemberships", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "getMembershipInfo", + outputs: [ + { name: "", internalType: "uint32", type: "uint32" }, + { name: "", internalType: "uint32", type: "uint32" }, + { name: "", internalType: "uint256", type: "uint256" } + ], + stateMutability: "view" + }, + { + type: "function", + inputs: [{ name: "index", internalType: "uint40", type: "uint40" }], + name: "getMerkleProof", + outputs: [{ name: "", internalType: "uint256[20]", type: "uint256[20]" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "startIndex", internalType: "uint32", type: "uint32" }, + { name: "endIndex", internalType: "uint32", type: "uint32" } + ], + name: "getRateCommitmentsInRangeBoundsInclusive", + outputs: [{ name: "", internalType: "uint256[]", type: "uint256[]" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "gracePeriodDurationForNewMemberships", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [{ name: "", internalType: "uint256", type: "uint256" }], + name: "indicesOfLazilyErasedMemberships", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "_priceCalculator", internalType: "address", type: "address" }, + { name: "_maxTotalRateLimit", internalType: "uint32", type: "uint32" }, + { + name: "_minMembershipRateLimit", + internalType: "uint32", + type: "uint32" + }, + { + name: "_maxMembershipRateLimit", + internalType: "uint32", + type: "uint32" + }, + { name: "_activeDuration", internalType: "uint32", type: "uint32" }, + { name: "_gracePeriod", internalType: "uint32", type: "uint32" } + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "_idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "isExpired", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "_idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "isInGracePeriod", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "isInMembershipSet", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "isValidIdCommitment", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "pure" + }, + { + type: "function", + inputs: [{ name: "rateLimit", internalType: "uint32", type: "uint32" }], + name: "isValidMembershipRateLimit", + outputs: [{ name: "", internalType: "bool", type: "bool" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "maxMembershipRateLimit", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "maxTotalRateLimit", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "_idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "membershipExpirationTimestamp", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "memberships", + outputs: [ + { name: "depositAmount", internalType: "uint256", type: "uint256" }, + { name: "activeDuration", internalType: "uint32", type: "uint32" }, + { + name: "gracePeriodStartTimestamp", + internalType: "uint256", + type: "uint256" + }, + { name: "gracePeriodDuration", internalType: "uint32", type: "uint32" }, + { name: "rateLimit", internalType: "uint32", type: "uint32" }, + { name: "index", internalType: "uint32", type: "uint32" }, + { name: "holder", internalType: "address", type: "address" }, + { name: "token", internalType: "address", type: "address" } + ], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "merkleTree", + outputs: [ + { name: "maxIndex", internalType: "uint40", type: "uint40" }, + { name: "numberOfLeaves", internalType: "uint40", type: "uint40" } + ], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "minMembershipRateLimit", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "nextFreeIndex", + outputs: [{ name: "", internalType: "uint32", type: "uint32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "owner", + outputs: [{ name: "", internalType: "address", type: "address" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "priceCalculator", + outputs: [ + { name: "", internalType: "contract IPriceCalculator", type: "address" } + ], + stateMutability: "view" + }, + { + type: "function", + inputs: [], + name: "proxiableUUID", + outputs: [{ name: "", internalType: "bytes32", type: "bytes32" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" }, + { name: "rateLimit", internalType: "uint32", type: "uint32" }, + { + name: "idCommitmentsToErase", + internalType: "uint256[]", + type: "uint256[]" + } + ], + name: "register", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [], + name: "root", + outputs: [{ name: "", internalType: "uint256", type: "uint256" }], + stateMutability: "view" + }, + { + type: "function", + inputs: [ + { + name: "_activeDurationForNewMembership", + internalType: "uint32", + type: "uint32" + } + ], + name: "setActiveDuration", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { + name: "_gracePeriodDurationForNewMembership", + internalType: "uint32", + type: "uint32" + } + ], + name: "setGracePeriodDuration", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { + name: "_maxMembershipRateLimit", + internalType: "uint32", + type: "uint32" + } + ], + name: "setMaxMembershipRateLimit", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "_maxTotalRateLimit", internalType: "uint32", type: "uint32" } + ], + name: "setMaxTotalRateLimit", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { + name: "_minMembershipRateLimit", + internalType: "uint32", + type: "uint32" + } + ], + name: "setMinMembershipRateLimit", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "_priceCalculator", internalType: "address", type: "address" } + ], + name: "setPriceCalculator", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [{ name: "newOwner", internalType: "address", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "newImplementation", internalType: "address", type: "address" } + ], + name: "upgradeTo", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + inputs: [ + { name: "newImplementation", internalType: "address", type: "address" }, + { name: "data", internalType: "bytes", type: "bytes" } + ], + name: "upgradeToAndCall", + outputs: [], + stateMutability: "payable" + }, + { + type: "function", + inputs: [{ name: "token", internalType: "address", type: "address" }], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "previousAdmin", + internalType: "address", + type: "address", + indexed: false + }, + { + name: "newAdmin", + internalType: "address", + type: "address", + indexed: false + } + ], + name: "AdminChanged" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "beacon", + internalType: "address", + type: "address", + indexed: true + } + ], + name: "BeaconUpgraded" + }, + { + type: "event", + anonymous: false, + inputs: [ + { name: "version", internalType: "uint8", type: "uint8", indexed: false } + ], + name: "Initialized" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint32", + type: "uint32", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false } + ], + name: "MembershipErased" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint32", + type: "uint32", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false } + ], + name: "MembershipExpired" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint32", + type: "uint32", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false }, + { + name: "newGracePeriodStartTimestamp", + internalType: "uint256", + type: "uint256", + indexed: false + } + ], + name: "MembershipExtended" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "idCommitment", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { + name: "membershipRateLimit", + internalType: "uint256", + type: "uint256", + indexed: false + }, + { name: "index", internalType: "uint32", type: "uint32", indexed: false } + ], + name: "MembershipRegistered" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "previousOwner", + internalType: "address", + type: "address", + indexed: true + }, + { + name: "newOwner", + internalType: "address", + type: "address", + indexed: true + } + ], + name: "OwnershipTransferred" + }, + { + type: "event", + anonymous: false, + inputs: [ + { + name: "implementation", + internalType: "address", + type: "address", + indexed: true + } + ], + name: "Upgraded" + }, + { + type: "error", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "CannotEraseActiveMembership" + }, + { type: "error", inputs: [], name: "CannotExceedMaxTotalRateLimit" }, + { + type: "error", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "CannotExtendNonGracePeriodMembership" + }, + { + type: "error", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "InvalidIdCommitment" + }, + { type: "error", inputs: [], name: "InvalidMembershipRateLimit" }, + { + type: "error", + inputs: [ + { name: "startIndex", internalType: "uint256", type: "uint256" }, + { name: "endIndex", internalType: "uint256", type: "uint256" } + ], + name: "InvalidPaginationQuery" + }, + { + type: "error", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "MembershipDoesNotExist" + }, + { + type: "error", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "NonHolderCannotEraseGracePeriodMembership" + }, + { + type: "error", + inputs: [ + { name: "idCommitment", internalType: "uint256", type: "uint256" } + ], + name: "NonHolderCannotExtend" + } +] as const; diff --git a/packages/rln/src/credentials_manager.ts b/packages/rln/src/credentials_manager.ts index b82d9421f1..25a61898f2 100644 --- a/packages/rln/src/credentials_manager.ts +++ b/packages/rln/src/credentials_manager.ts @@ -1,5 +1,5 @@ import { Logger } from "@waku/utils"; -import { ethers } from "ethers"; +import { publicActions } from "viem"; import { RLN_CONTRACT } from "./contract/constants.js"; import { RLNBaseContract } from "./contract/rln_base_contract.js"; @@ -10,7 +10,7 @@ import type { } from "./keystore/index.js"; import { KeystoreEntity, Password } from "./keystore/types.js"; import { RegisterMembershipOptions, StartRLNOptions } from "./types.js"; -import { extractMetaMaskSigner } from "./utils/index.js"; +import { createViemClientFromWindow, RpcClient } from "./utils/index.js"; import { Zerokit } from "./zerokit.js"; const log = new Logger("rln:credentials"); @@ -24,7 +24,7 @@ export class RLNCredentialsManager { protected starting = false; public contract: undefined | RLNBaseContract; - public signer: undefined | ethers.Signer; + public rpcClient: undefined | RpcClient; protected keystore = Keystore.create(); public credentials: undefined | DecryptedCredentials; @@ -36,10 +36,6 @@ export class RLNCredentialsManager { this.zerokit = zerokit; } - public get provider(): undefined | ethers.providers.Provider { - return this.contract?.provider; - } - public async start(options: StartRLNOptions = {}): Promise { if (this.started || this.starting) { log.info("RLNCredentialsManager already started or starting"); @@ -59,10 +55,8 @@ export class RLNCredentialsManager { log.info("Credentials successfully decrypted"); } - const { signer, address, rateLimit } = await this.determineStartOptions( - options, - credentials - ); + const { rpcClient, address, rateLimit } = + await this.determineStartOptions(options, credentials); log.info(`Using contract address: ${address}`); @@ -72,10 +66,10 @@ export class RLNCredentialsManager { } this.credentials = credentials; - this.signer = signer!; + this.rpcClient = rpcClient!; this.contract = await RLNBaseContract.create({ - address: address!, - signer: signer!, + address: address! as `0x${string}`, + rpcClient: this.rpcClient, rateLimit: rateLimit ?? this.zerokit.rateLimit }); @@ -134,7 +128,7 @@ export class RLNCredentialsManager { protected async determineStartOptions( options: StartRLNOptions, credentials: KeystoreEntity | undefined - ): Promise { + ): Promise { let chainId = credentials?.membership.chainId; const address = credentials?.membership.address || @@ -146,11 +140,14 @@ export class RLNCredentialsManager { log.info(`Using Linea contract with chainId: ${chainId}`); } - const signer = options.signer || (await extractMetaMaskSigner()); - const currentChainId = await signer.getChainId(); + const rpcClient: RpcClient = options.walletClient + ? options.walletClient.extend(publicActions) + : await createViemClientFromWindow(); + + const currentChainId = rpcClient.chain?.id; log.info(`Current chain ID: ${currentChainId}`); - if (chainId && chainId !== currentChainId.toString()) { + if (chainId && chainId !== currentChainId?.toString()) { log.error( `Chain ID mismatch: contract=${chainId}, current=${currentChainId}` ); @@ -160,7 +157,7 @@ export class RLNCredentialsManager { } return { - signer, + rpcClient, address }; } @@ -206,9 +203,9 @@ export class RLNCredentialsManager { protected async verifyCredentialsAgainstContract( credentials: KeystoreEntity ): Promise { - if (!this.contract) { + if (!this.contract || !this.rpcClient) { throw Error( - "Failed to verify chain coordinates: no contract initialized." + "Failed to verify chain coordinates: no contract or viem client initialized." ); } @@ -221,8 +218,7 @@ export class RLNCredentialsManager { } const chainId = credentials.membership.chainId; - const network = await this.contract.provider.getNetwork(); - const currentChainId = network.chainId; + const currentChainId = await this.rpcClient.getChainId(); if (chainId !== currentChainId.toString()) { throw Error( `Failed to verify chain coordinates: credentials chainID=${chainId} is not equal to registryContract chainID=${currentChainId}` diff --git a/packages/rln/src/index.ts b/packages/rln/src/index.ts index 3348e370f7..7aa80a3d64 100644 --- a/packages/rln/src/index.ts +++ b/packages/rln/src/index.ts @@ -1,11 +1,10 @@ -import { RLN_ABI } from "./contract/abi/rln.js"; import { RLN_CONTRACT } from "./contract/index.js"; import { RLNBaseContract } from "./contract/rln_base_contract.js"; import { createRLN } from "./create.js"; import { IdentityCredential } from "./identity.js"; import { Keystore } from "./keystore/index.js"; import { RLNInstance } from "./rln.js"; -import { extractMetaMaskSigner } from "./utils/index.js"; +import { createViemClientFromWindow } from "./utils/index.js"; export { RLNBaseContract, @@ -14,10 +13,20 @@ export { RLNInstance, IdentityCredential, RLN_CONTRACT, - extractMetaMaskSigner, - RLN_ABI + createViemClientFromWindow }; +export { + wakuRlnV2Abi, + linearPriceCalculatorAbi, + iPriceCalculatorAbi, + membershipUpgradeableAbi +} from "./contract/wagmi/generated.js"; + +// Export token utilities +export { getTokenBalance, erc20Abi } from "./contract/token.js"; +export type { TokenBalance } from "./contract/token.js"; + export type { DecryptedCredentials, EncryptedCredentials, diff --git a/packages/rln/src/keystore/keystore.ts b/packages/rln/src/keystore/keystore.ts index f001f2938d..c2623b2fa0 100644 --- a/packages/rln/src/keystore/keystore.ts +++ b/packages/rln/src/keystore/keystore.ts @@ -160,7 +160,15 @@ export class Keystore { } public toString(): string { - return JSON.stringify(this.data); + // Custom replacer function to handle BigInt serialization + const bigIntReplacer = (_key: string, value: unknown): unknown => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }; + + return JSON.stringify(this.data, bigIntReplacer); } public toObject(): NwakuKeystore { @@ -327,21 +335,32 @@ export class Keystore { const { IDCommitment, IDNullifier, IDSecretHash, IDTrapdoor } = options.identity; + // Custom replacer function to handle BigInt serialization + const bigIntReplacer = (_key: string, value: unknown): unknown => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }; + return utf8ToBytes( - JSON.stringify({ - treeIndex: options.membership.treeIndex, - identityCredential: { - idCommitment: Array.from(IDCommitment), - idNullifier: Array.from(IDNullifier), - idSecretHash: Array.from(IDSecretHash), - idTrapdoor: Array.from(IDTrapdoor) - }, - membershipContract: { - chainId: options.membership.chainId, - address: options.membership.address + JSON.stringify( + { + treeIndex: options.membership.treeIndex, + identityCredential: { + idCommitment: Array.from(IDCommitment), + idNullifier: Array.from(IDNullifier), + idSecretHash: Array.from(IDSecretHash), + idTrapdoor: Array.from(IDTrapdoor) + }, + membershipContract: { + chainId: options.membership.chainId, + address: options.membership.address + }, + userMessageLimit: options.membership.rateLimit }, - userMessageLimit: options.membership.rateLimit - }) + bigIntReplacer + ) ); } } diff --git a/packages/rln/src/proof.spec.ts b/packages/rln/src/proof.spec.ts new file mode 100644 index 0000000000..5d15798283 --- /dev/null +++ b/packages/rln/src/proof.spec.ts @@ -0,0 +1,101 @@ +import { expect } from "chai"; + +import { Keystore } from "./keystore/index.js"; +import { RLNInstance } from "./rln.js"; +import { BytesUtils } from "./utils/index.js"; +import { + calculateRateCommitment, + extractPathDirectionsFromProof, + MERKLE_TREE_DEPTH, + reconstructMerkleRoot +} from "./utils/merkle.js"; +import { TEST_KEYSTORE_DATA } from "./utils/test_keystore.js"; + +describe("RLN Proof Integration Tests", function () { + this.timeout(30000); + + it("validate stored merkle proof data", function () { + // Convert stored merkle proof strings to bigints + const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p)); + + expect(merkleProof).to.be.an("array"); + expect(merkleProof).to.have.lengthOf(MERKLE_TREE_DEPTH); // RLN uses fixed depth merkle tree + + merkleProof.forEach((element, i) => { + expect(element).to.be.a( + "bigint", + `Proof element ${i} should be a bigint` + ); + expect(element).to.not.equal(0n, `Proof element ${i} should not be zero`); + }); + }); + + it("should generate a valid RLN proof", async function () { + const rlnInstance = await RLNInstance.create(); + // Load credential from test keystore + const keystore = Keystore.fromString(TEST_KEYSTORE_DATA.keystoreJson); + if (!keystore) { + throw new Error("Failed to load test keystore"); + } + const credentialHash = TEST_KEYSTORE_DATA.credentialHash; + const password = TEST_KEYSTORE_DATA.password; + const credential = await keystore.readCredential(credentialHash, password); + if (!credential) { + throw new Error("Failed to unlock credential with provided password"); + } + + const idCommitment = credential.identity.IDCommitmentBigInt; + + const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p)); + const merkleRoot = BigInt(TEST_KEYSTORE_DATA.merkleRoot); + const membershipIndex = BigInt(TEST_KEYSTORE_DATA.membershipIndex); + const rateLimit = BigInt(TEST_KEYSTORE_DATA.rateLimit); + + const rateCommitment = calculateRateCommitment(idCommitment, rateLimit); + + const proofElementIndexes = extractPathDirectionsFromProof( + merkleProof, + rateCommitment, + merkleRoot + ); + if (!proofElementIndexes) { + throw new Error("Failed to extract proof element indexes"); + } + + expect(proofElementIndexes).to.have.lengthOf(MERKLE_TREE_DEPTH); + + const reconstructedRoot = reconstructMerkleRoot( + merkleProof, + membershipIndex, + rateCommitment + ); + + expect(reconstructedRoot).to.equal( + merkleRoot, + "Reconstructed root should match stored root" + ); + + const testMessage = new TextEncoder().encode("test"); + + const proof = await rlnInstance.zerokit.generateRLNProof( + testMessage, + Number(membershipIndex), + new Date(), + credential.identity.IDSecretHash, + merkleProof.map((proof) => BytesUtils.fromBigInt(proof, 32, "little")), + proofElementIndexes.map((index) => + BytesUtils.writeUIntLE(new Uint8Array(1), index, 0, 1) + ), + Number(rateLimit), + 0 + ); + + const isValid = rlnInstance.zerokit.verifyRLNProof( + BytesUtils.writeUIntLE(new Uint8Array(8), testMessage.length, 0, 8), + testMessage, + proof, + [BytesUtils.fromBigInt(merkleRoot, 32, "little")] + ); + expect(isValid).to.be.true; + }); +}); diff --git a/packages/rln/src/rln.ts b/packages/rln/src/rln.ts index 491fd14d44..739dcefd3a 100644 --- a/packages/rln/src/rln.ts +++ b/packages/rln/src/rln.ts @@ -1,5 +1,6 @@ import { Logger } from "@waku/utils"; import init, * as zerokitRLN from "@waku/zerokit-rln-wasm"; +import initUtils from "@waku/zerokit-rln-wasm-utils"; import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; import { RLNCredentialsManager } from "./credentials_manager.js"; @@ -16,6 +17,7 @@ export class RLNInstance extends RLNCredentialsManager { */ public static async create(): Promise { try { + await initUtils(); await init(); zerokitRLN.initPanicHook(); diff --git a/packages/rln/src/types.ts b/packages/rln/src/types.ts index 7980c8aea9..55497de0a4 100644 --- a/packages/rln/src/types.ts +++ b/packages/rln/src/types.ts @@ -1,4 +1,4 @@ -import { ethers } from "ethers"; +import { WalletClient } from "viem"; import { IdentityCredential } from "./identity.js"; import { @@ -8,9 +8,9 @@ import { export type StartRLNOptions = { /** - * If not set - will extract MetaMask account and get signer from it. + * If not set - will attempt to create from provider injected in window. */ - signer?: ethers.Signer; + walletClient?: WalletClient; /** * If not set - will use default SEPOLIA_CONTRACT address. */ diff --git a/packages/rln/src/utils/bytes.ts b/packages/rln/src/utils/bytes.ts index 4df17bd380..296ced4dd8 100644 --- a/packages/rln/src/utils/bytes.ts +++ b/packages/rln/src/utils/bytes.ts @@ -49,6 +49,43 @@ export class BytesUtils { return result; } + /** + * Convert a BigInt to a Uint8Array with configurable output endianness + * @param value - The BigInt to convert + * @param byteLength - The desired byte length of the output (optional, auto-calculated if not provided) + * @param outputEndianness - Endianness of the output bytes ('big' or 'little') + * @returns Uint8Array representation of the BigInt + */ + public static fromBigInt( + value: bigint, + byteLength: number, + outputEndianness: "big" | "little" = "little" + ): Uint8Array { + if (value < 0n) { + throw new Error("Cannot convert negative BigInt to bytes"); + } + + if (value === 0n) { + return new Uint8Array(byteLength); + } + + const result = new Uint8Array(byteLength); + let workingValue = value; + + // Extract bytes in big-endian order + for (let i = byteLength - 1; i >= 0; i--) { + result[i] = Number(workingValue & 0xffn); + workingValue = workingValue >> 8n; + } + + // If we need little-endian output, reverse the array + if (outputEndianness === "little") { + result.reverse(); + } + + return result; + } + /** * Writes an unsigned integer to a buffer in little-endian format */ diff --git a/packages/rln/src/utils/epoch.ts b/packages/rln/src/utils/epoch.ts index 19b2f81108..bf89d40aa5 100644 --- a/packages/rln/src/utils/epoch.ts +++ b/packages/rln/src/utils/epoch.ts @@ -1,5 +1,7 @@ import { Logger } from "@waku/utils"; +import { BytesUtils } from "./bytes.js"; + const DefaultEpochUnitSeconds = 10; // the rln-relay epoch length in seconds const log = new Logger("rln:epoch"); @@ -15,11 +17,7 @@ export function dateToEpoch( } export function epochIntToBytes(epoch: number): Uint8Array { - const bytes = new Uint8Array(32); - const db = new DataView(bytes.buffer); - db.setUint32(0, epoch, true); - log.info("encoded epoch", epoch, bytes); - return bytes; + return BytesUtils.writeUIntLE(new Uint8Array(32), epoch, 0, 32); } export function epochBytesToInt(bytes: Uint8Array): number { diff --git a/packages/rln/src/utils/hash.ts b/packages/rln/src/utils/hash.ts index 6aa8e29277..43908ce45d 100644 --- a/packages/rln/src/utils/hash.ts +++ b/packages/rln/src/utils/hash.ts @@ -1,4 +1,4 @@ -import * as zerokitRLN from "@waku/zerokit-rln-wasm"; +import { hash, poseidonHash as poseidon } from "@waku/zerokit-rln-wasm-utils"; import { BytesUtils } from "./bytes.js"; @@ -10,16 +10,9 @@ export function poseidonHash(...input: Array): Uint8Array { 8 ); const lenPrefixedData = BytesUtils.concatenate(inputLen, ...input); - return zerokitRLN.poseidonHash(lenPrefixedData); + return poseidon(lenPrefixedData, true); } export function sha256(input: Uint8Array): Uint8Array { - const inputLen = BytesUtils.writeUIntLE( - new Uint8Array(8), - input.length, - 0, - 8 - ); - const lenPrefixedData = BytesUtils.concatenate(inputLen, input); - return zerokitRLN.hash(lenPrefixedData); + return hash(input, true); } diff --git a/packages/rln/src/utils/index.ts b/packages/rln/src/utils/index.ts index dddc0b8285..a5cf32937e 100644 --- a/packages/rln/src/utils/index.ts +++ b/packages/rln/src/utils/index.ts @@ -1,4 +1,4 @@ -export { extractMetaMaskSigner } from "./metamask.js"; +export { createViemClientFromWindow, RpcClient } from "./rpcClient.js"; export { BytesUtils } from "./bytes.js"; export { sha256, poseidonHash } from "./hash.js"; export { dateToEpoch, epochIntToBytes, epochBytesToInt } from "./epoch.js"; diff --git a/packages/rln/src/utils/merkle.ts b/packages/rln/src/utils/merkle.ts new file mode 100644 index 0000000000..2080c74439 --- /dev/null +++ b/packages/rln/src/utils/merkle.ts @@ -0,0 +1,179 @@ +import { BytesUtils } from "./bytes.js"; +import { poseidonHash } from "./hash.js"; + +/** + * The fixed depth of the Merkle tree used in the RLN contract + * This is a constant that will never change for the on-chain implementation + */ +export const MERKLE_TREE_DEPTH = 20; + +/** + * Reconstructs a Merkle tree root from a proof and leaf information + * + * @param proof - Array of MERKLE_TREE_DEPTH bigint elements representing the Merkle proof + * @param leafIndex - The index of the leaf in the tree (used to determine left/right positioning) + * @param leafValue - The value of the leaf (typically the rate commitment) + * @returns The reconstructed root as a bigint + */ +export function reconstructMerkleRoot( + proof: readonly bigint[], + leafIndex: bigint, + leafValue: bigint +): bigint { + if (proof.length !== MERKLE_TREE_DEPTH) { + throw new Error( + `Expected proof of length ${MERKLE_TREE_DEPTH}, got ${proof.length}` + ); + } + + let currentValue = leafValue; + + // Process each level of the tree (0 to MERKLE_TREE_DEPTH-1) + for (let level = 0; level < MERKLE_TREE_DEPTH; level++) { + // Check if bit `level` is set in the leaf index + const bit = (leafIndex >> BigInt(level)) & 1n; + + // Convert bigints to Uint8Array for hashing + const currentBytes = bigIntToBytes32(currentValue); + const proofBytes = bigIntToBytes32(proof[level]); + + let hashResult: Uint8Array; + + if (bit === 0n) { + // Current node is a left child: hash(current, proof[level]) + hashResult = poseidonHash(currentBytes, proofBytes); + } else { + // Current node is a right child: hash(proof[level], current) + hashResult = poseidonHash(proofBytes, currentBytes); + } + + // Convert hash result back to bigint for next iteration + currentValue = BytesUtils.toBigInt(hashResult, "little"); + } + + return currentValue; +} + +/** + * Extracts index information from a Merkle proof by attempting to reconstruct + * the root with different possible indices and comparing against the expected root + * + * @param proof - Array of MERKLE_TREE_DEPTH bigint elements representing the Merkle proof + * @param leafValue - The value of the leaf (typically the rate commitment) + * @param expectedRoot - The expected root to match against + * @param maxIndex - Maximum index to try (default: 2^MERKLE_TREE_DEPTH - 1) + * @returns The index that produces the expected root, or null if not found + */ +function extractIndexFromProof( + proof: readonly bigint[], + leafValue: bigint, + expectedRoot: bigint, + maxIndex: bigint = (1n << BigInt(MERKLE_TREE_DEPTH)) - 1n +): bigint | null { + // Try different indices to see which one produces the expected root + for (let index = 0n; index <= maxIndex; index++) { + try { + const reconstructedRoot = reconstructMerkleRoot(proof, index, leafValue); + if (reconstructedRoot === expectedRoot) { + return index; + } + } catch (error) { + // Continue trying other indices if reconstruction fails + continue; + } + } + + return null; +} + +/** + * Calculates the rate commitment from an ID commitment and rate limit + * This matches the contract's calculation: PoseidonT3.hash([idCommitment, rateLimit]) + * + * @param idCommitment - The identity commitment as a bigint + * @param rateLimit - The rate limit as a bigint + * @returns The rate commitment as a bigint + */ +export function calculateRateCommitment( + idCommitment: bigint, + rateLimit: bigint +): bigint { + const idBytes = bigIntToBytes32(idCommitment); + const rateLimitBytes = bigIntToBytes32(rateLimit); + + const hashResult = poseidonHash(idBytes, rateLimitBytes); + return BytesUtils.toBigInt(hashResult, "little"); +} + +/** + * Converts a bigint to a 32-byte Uint8Array in little-endian format + * + * @param value - The bigint value to convert + * @returns 32-byte Uint8Array representation + */ +function bigIntToBytes32(value: bigint): Uint8Array { + const bytes = new Uint8Array(32); + let temp = value; + + for (let i = 0; i < 32; i++) { + bytes[i] = Number(temp & 0xffn); + temp >>= 8n; + } + + return bytes; +} + +/** + * Extracts the path direction bits from a Merkle proof by finding the leaf index + * that produces the expected root, then converting that index to path directions + * + * @param proof - Array of MERKLE_TREE_DEPTH bigint elements representing the Merkle proof + * @param leafValue - The value of the leaf (typically the rate commitment) + * @param expectedRoot - The expected root to match against + * @param maxIndex - Maximum index to try (default: 2^MERKLE_TREE_DEPTH - 1) + * @returns Array of MERKLE_TREE_DEPTH numbers (0 or 1) representing path directions, or null if no valid path found + * - 0 means the node is a left child (hash order: current, sibling) + * - 1 means the node is a right child (hash order: sibling, current) + */ +export function extractPathDirectionsFromProof( + proof: readonly bigint[], + leafValue: bigint, + expectedRoot: bigint, + maxIndex: bigint = (1n << BigInt(MERKLE_TREE_DEPTH)) - 1n +): number[] | null { + // First, find the leaf index that produces the expected root + const leafIndex = extractIndexFromProof( + proof, + leafValue, + expectedRoot, + maxIndex + ); + + if (leafIndex === null) { + return null; + } + + // Convert the leaf index to path directions + return getPathDirectionsFromIndex(leafIndex); +} + +/** + * Converts a leaf index to an array of path direction bits + * + * @param leafIndex - The index of the leaf in the tree + * @returns Array of MERKLE_TREE_DEPTH numbers (0 or 1) representing path directions + * - 0 means the node is a left child (hash order: current, sibling) + * - 1 means the node is a right child (hash order: sibling, current) + */ +function getPathDirectionsFromIndex(leafIndex: bigint): number[] { + const pathDirections: number[] = []; + + // For each level (0 to MERKLE_TREE_DEPTH-1), extract the bit that determines left/right + for (let level = 0; level < MERKLE_TREE_DEPTH; level++) { + // Check if bit `level` is set in the leaf index + const bit = (leafIndex >> BigInt(level)) & 1n; + pathDirections.push(Number(bit)); + } + + return pathDirections; +} diff --git a/packages/rln/src/utils/metamask.ts b/packages/rln/src/utils/metamask.ts deleted file mode 100644 index 83c7066106..0000000000 --- a/packages/rln/src/utils/metamask.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ethers } from "ethers"; - -export const extractMetaMaskSigner = async (): Promise => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const ethereum = (window as any).ethereum; - - if (!ethereum) { - throw Error( - "Missing or invalid Ethereum provider. Please install a Web3 wallet such as MetaMask." - ); - } - - await ethereum.request({ method: "eth_requestAccounts" }); - const provider = new ethers.providers.Web3Provider(ethereum, "any"); - - return provider.getSigner(); -}; diff --git a/packages/rln/src/utils/rpcClient.ts b/packages/rln/src/utils/rpcClient.ts new file mode 100644 index 0000000000..585adf1a9d --- /dev/null +++ b/packages/rln/src/utils/rpcClient.ts @@ -0,0 +1,57 @@ +import "viem/window"; +import { + type Address, + createWalletClient, + custom, + PublicActions, + publicActions, + WalletClient +} from "viem"; +import { lineaSepolia } from "viem/chains"; + +export type RpcClient = WalletClient & PublicActions; + +/** + * Checks window for injected Ethereum provider, requests user to connect, and creates an RPC client object + * capable of performing both read and write operations on the blockchain. + * + * If the wallet is not connected to the Linea Sepolia network, it will attempt to switch to it. + * If the wallet does not have the Linea Sepolia network added, it will attempt to add it. + */ +export const createViemClientFromWindow = async (): Promise => { + const ethereum = window.ethereum; + + if (!ethereum) { + throw Error( + "Missing or invalid Ethereum provider. Please install a Web3 wallet such as MetaMask." + ); + } + + const [account] = await ethereum.request({ method: "eth_requestAccounts" }); + + const rpcClient: RpcClient = createWalletClient({ + account: account as Address, + chain: lineaSepolia, + transport: custom(window.ethereum!) + }).extend(publicActions); + + // Ensure wallet is connected to Linea Sepolia + try { + await rpcClient.switchChain({ id: lineaSepolia.id }); + } catch (error: unknown) { + // This error code indicates that the chain has not been added to the wallet + if ( + typeof error === "object" && + error !== null && + "code" in error && + error.code === 4902 + ) { + await rpcClient.addChain({ chain: lineaSepolia }); + await rpcClient.switchChain({ id: lineaSepolia.id }); + } else { + throw error; + } + } + + return rpcClient; +}; diff --git a/packages/rln/src/utils/test_keystore.ts b/packages/rln/src/utils/test_keystore.ts new file mode 100644 index 0000000000..707ec8f451 --- /dev/null +++ b/packages/rln/src/utils/test_keystore.ts @@ -0,0 +1,33 @@ +export const TEST_KEYSTORE_DATA = { + keystoreJson: + '{"application":"waku-rln-relay","appIdentifier":"01234567890abcdef","version":"0.2","credentials":{"E0A8AC077B95F64C1B2C4B116468B22EFA3B1CFF250069AE07422F645BAA555E":{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"96aff104d7bb23cefb57a4c5e816a3b9"},"ciphertext":"1ae2c7a47274d12d6a4b439da48abfa89be29e4ba3308d153e2e808d3e120cc85da472ab1e0278c945231092162d31d753ecb48484ac0c3a7efe6380d08f5dedecc9cda26bd156a30d232b9da4313c5ec92b21cd3dc3ca03cff68afde94a063799b658cc3e4a5c648e620d584a8a184d2d473e3e94c897e21e0de7580639dcf40c0133f36896ac5bee2dd5fe8810a5441e31e1938ecc4b195db57c1b6d320a374508406dfb7a4879081b70100140515b4c6c551f25f9b4c9a7214ac2dc222410bf74666407343dfd4af477c85cf2f316bb7a512a88948d88f5474374563d51d02c13eede6b6cf64fab7991e529157d7de39033099d26f323d9710159b47d2511695b4fb428e3b02c760e1470a3ece712c6a03692d067e0e17930bc25ce7dc4ad2634e07ef51fa7369de6b4d495c7ae1d8ad8dccdd2fa12802db4203c527887adf5eb42e2551e120b8a455892d0ac9369faf708465a983c03c7c8f77c268f85cacc7b718a1e9e2800b160ca1f7a78f2c160cbc97396f5dfe0e0f3b35addb4f8d667021c79eec5248122d8c983075b9e8ca20679e90a12bdbeefb33df21523b4e1ea7ab57ddc706b43bf4827fbc3530d20cb906468af5c5c31ac08815f3ed1d00341be7e287a3fb7ef67aecf2e56f694c51ba6db8641ac873e26659c92a8527c42df2d5ac15ff6201bdfa8a5ee34b6a90ff864fba89370a8c51efcb4ed1b69f3ed0e37ee97c66eb84763f107e1214e088e3149b2433a8da595293343b2290b0a84b7f796b70005d1672446d98d45da7c89c3eb8d91ece94ee41099f9f43c6810ce71d9f75ac3dffe1de0c79e40baad486ecaefbd0cc0e89aed7e0a16ea271a371d3f5927a1c7b813608de5715692e58322260a4bcd4ccba4b2376df01f58645c16a7b37c8473b94c7577ae774e5c72132ed15507ab2027ddabf137aa417b134b653eda247314","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"5f2081f089e9e277873bf1f538c60d714749a2bb910d8f1ed119d8d403235a8c"},"mac":"8d0667893b7d3b5f0b37c43edef616a8d295dc58292c98655eec8b5fe2ad69c3"}}}}', + credentialHash: + "E0A8AC077B95F64C1B2C4B116468B22EFA3B1CFF250069AE07422F645BAA555E", + password: "12345678", + merkleProof: [ + "21837427992620339064281119305700224965155897361776876451171527491637273262703", + "2849928341676773476316761863425436901389023422598778907382563142042850204484", + "21699429914184421678079077958020273488709892845081201722564329942861605328226", + "8522396354694062508299995669286882048091268903835874022564768254605186873188", + "4967828252976847302563643214799688359334626491919847999565033460501719790119", + "985039452502497454598906195897243897432778848314526706136284672198477696437", + "3565679202982155915846059790230166166058846233389836779083891288518797717794", + "1241870589869015758600129850815671823696180350556207862318506998039540071293", + "21551820661461729022865262380882070649935529853313286572328683688269863701601", + "16870197621778677478951480138572599814910741341994641594346262317677658226992", + "12413880268183407374852357075976609371175688755676981206018884971008854919922", + "14271763308400718165336499097156975241954733520325982997864342600795471836726", + "20066985985293572387227381049700832219069292839614107140851619262827735677018", + "9394776414966240069580838672673694685292165040808226440647796406499139370960", + "11331146992410411304059858900317123658895005918277453009197229807340014528524", + "15819538789928229930262697811477882737253464456578333862691129291651619515538", + "19217088683336594659449020493828377907203207941212636669271704950158751593251", + "21035245323335827719745544373081896983162834604456827698288649288827293579666", + "6939770416153240137322503476966641397417391950902474480970945462551409848591", + "10941962436777715901943463195175331263348098796018438960955633645115732864202" + ], + merkleRoot: + "3281768056038133311055294993138164819435524453040629949691729675724822631973", + membershipIndex: "703", + rateLimit: "300" +}; diff --git a/packages/rln/src/zerokit.spec.ts b/packages/rln/src/zerokit.browser.spec.ts similarity index 100% rename from packages/rln/src/zerokit.spec.ts rename to packages/rln/src/zerokit.browser.spec.ts diff --git a/packages/rln/src/zerokit.ts b/packages/rln/src/zerokit.ts index 47df182f00..ca743fec86 100644 --- a/packages/rln/src/zerokit.ts +++ b/packages/rln/src/zerokit.ts @@ -1,14 +1,21 @@ import * as zerokitRLN from "@waku/zerokit-rln-wasm"; +import { generateSeededExtendedMembershipKey } from "@waku/zerokit-rln-wasm-utils"; -import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; +import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./contract/constants.js"; import { IdentityCredential } from "./identity.js"; import { WitnessCalculator } from "./resources/witness_calculator"; +import { BytesUtils } from "./utils/bytes.js"; +import { dateToEpoch, epochIntToBytes } from "./utils/epoch.js"; +import { poseidonHash, sha256 } from "./utils/hash.js"; export class Zerokit { public constructor( private readonly zkRLN: number, private readonly witnessCalculator: WitnessCalculator, - private readonly _rateLimit: number = DEFAULT_RATE_LIMIT + private readonly _rateLimit: number = DEFAULT_RATE_LIMIT, + private readonly rlnIdentifier: Uint8Array = new TextEncoder().encode( + "rln/waku-rln-relay/v2.0.0" + ) ) {} public get getZkRLN(): number { @@ -26,10 +33,117 @@ export class Zerokit { public generateSeededIdentityCredential(seed: string): IdentityCredential { const stringEncoder = new TextEncoder(); const seedBytes = stringEncoder.encode(seed); - const memKeys = zerokitRLN.generateSeededExtendedMembershipKey( + const memKeys = generateSeededExtendedMembershipKey(seedBytes, true); + return IdentityCredential.fromBytes(memKeys); + } + + public async serializeWitness( + idSecretHash: Uint8Array, + pathElements: Uint8Array[], + identityPathIndex: Uint8Array[], + x: Uint8Array, + epoch: Uint8Array, + rateLimit: number, + messageId: number // number of message sent by the user in this epoch + ): Promise { + const externalNullifier = poseidonHash( + sha256(epoch), + sha256(this.rlnIdentifier) + ); + const pathElementsBytes = new Uint8Array(8 + pathElements.length * 32); + BytesUtils.writeUIntLE(pathElementsBytes, pathElements.length, 0, 8); + for (let i = 0; i < pathElements.length; i++) { + // We assume that the path elements are already in little-endian format + pathElementsBytes.set(pathElements[i], 8 + i * 32); + } + const identityPathIndexBytes = new Uint8Array( + 8 + identityPathIndex.length * 1 + ); + BytesUtils.writeUIntLE( + identityPathIndexBytes, + identityPathIndex.length, + 0, + 8 + ); + for (let i = 0; i < identityPathIndex.length; i++) { + // We assume that each identity path index is already in little-endian format + identityPathIndexBytes.set(identityPathIndex[i], 8 + i * 1); + } + return BytesUtils.concatenate( + idSecretHash, + BytesUtils.writeUIntLE(new Uint8Array(32), rateLimit, 0, 32), + BytesUtils.writeUIntLE(new Uint8Array(32), messageId, 0, 32), + pathElementsBytes, + identityPathIndexBytes, + x, + externalNullifier + ); + } + + public async generateRLNProof( + msg: Uint8Array, + index: number, // index of the leaf in the merkle tree + epoch: Uint8Array | Date | undefined, + idSecretHash: Uint8Array, + pathElements: Uint8Array[], + identityPathIndex: Uint8Array[], + rateLimit: number, + messageId: number // number of message sent by the user in this epoch + ): Promise { + if (epoch === undefined) { + epoch = epochIntToBytes(dateToEpoch(new Date())); + } else if (epoch instanceof Date) { + epoch = epochIntToBytes(dateToEpoch(epoch)); + } + + if (epoch.length !== 32) throw new Error("invalid epoch"); + if (idSecretHash.length !== 32) throw new Error("invalid id secret hash"); + if (index < 0) throw new Error("index must be >= 0"); + if ( + rateLimit < RATE_LIMIT_PARAMS.MIN_RATE || + rateLimit > RATE_LIMIT_PARAMS.MAX_RATE + ) { + throw new Error( + `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}` + ); + } + + const x = sha256(msg); + + const serializedWitness = await this.serializeWitness( + idSecretHash, + pathElements, + identityPathIndex, + x, + epoch, + rateLimit, + messageId + ); + const witnessJson: Record = zerokitRLN.rlnWitnessToJson( this.zkRLN, - seedBytes + serializedWitness + ) as Record; + const calculatedWitness: bigint[] = + await this.witnessCalculator.calculateWitness(witnessJson); + return zerokitRLN.generateRLNProofWithWitness( + this.zkRLN, + calculatedWitness, + serializedWitness + ); + } + + public verifyRLNProof( + signalLength: Uint8Array, + signal: Uint8Array, + proof: Uint8Array, + roots: Uint8Array[] + ): boolean { + if (signalLength.length !== 8) + throw new Error("signalLength must be 8 bytes"); + return zerokitRLN.verifyWithRoots( + this.zkRLN, + BytesUtils.concatenate(proof, signalLength, signal), + BytesUtils.concatenate(...roots) ); - return IdentityCredential.fromBytes(memKeys); } } diff --git a/packages/rln/tsconfig.dev.json b/packages/rln/tsconfig.dev.json index 96c99dc461..d53a621c4f 100644 --- a/packages/rln/tsconfig.dev.json +++ b/packages/rln/tsconfig.dev.json @@ -1,3 +1,4 @@ { - "extends": "../../tsconfig.dev" -} \ No newline at end of file + "extends": "../../tsconfig.dev", + "exclude": ["wagmi.config.ts"] +} diff --git a/packages/rln/tsconfig.json b/packages/rln/tsconfig.json index 6f8756934b..f31944719d 100644 --- a/packages/rln/tsconfig.json +++ b/packages/rln/tsconfig.json @@ -6,5 +6,5 @@ "tsBuildInfoFile": "dist/.tsbuildinfo" }, "include": ["src"], - "exclude": ["src/**/*.spec.ts", "src/test_utils"] -} \ No newline at end of file + "exclude": ["wagmi.config.ts", "src/**/*.spec.ts", "src/test_utils"] +} diff --git a/packages/rln/wagmi.config.ts b/packages/rln/wagmi.config.ts new file mode 100644 index 0000000000..bffe3f0576 --- /dev/null +++ b/packages/rln/wagmi.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "@wagmi/cli"; +import { foundry } from "@wagmi/cli/plugins"; + +export default defineConfig({ + out: "src/contract/wagmi/generated.ts", + plugins: [ + foundry({ + project: "./waku-rlnv2-contract", + artifacts: "out", + include: [ + "WakuRlnV2.sol/**", + "Membership.sol/**", + "LinearPriceCalculator.sol/**", + "IPriceCalculator.sol/**" + ] + }) + ] +});