mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-02 13:53:12 +00:00
feat(rln)!: generate contract types, migrate from ethers to viem (#2705)
* feat: use wagmi to generate contract types * feat: migrate rln from ethers to viem * fix: remove .gitmodules * fix: update readme * fix: refactor to use a single viem client object * fix: update comments, tsconfig * feat: remove membership event tracking * fix: script name in package.json and readme * fix: only allow linea sepolia * fix: consolidate viem types, typed window * fix: use viem to infer type of decoded event * fix: use js for generate abi script * feat: generate abi and build rln package as release condition * fix: check that eth_requestAccounts returns an array * fix: handle error messages * fix: use https instead of git for cloning in script * fix: add warning annotations for contract typings check * fix: install deps for rln package before building * fix: use pnpm when installing rln contracts * fix: use workspace flag to run abi script * fix: add ref to checkout action * fix: include pnpm in ci
This commit is contained in:
parent
788f7e62c5
commit
f2ad23ad43
@ -104,6 +104,7 @@
|
|||||||
"reactjs",
|
"reactjs",
|
||||||
"recid",
|
"recid",
|
||||||
"rlnrelay",
|
"rlnrelay",
|
||||||
|
"rlnv",
|
||||||
"roadmap",
|
"roadmap",
|
||||||
"sandboxed",
|
"sandboxed",
|
||||||
"scanf",
|
"scanf",
|
||||||
@ -132,7 +133,9 @@
|
|||||||
"upgrader",
|
"upgrader",
|
||||||
"vacp",
|
"vacp",
|
||||||
"varint",
|
"varint",
|
||||||
|
"viem",
|
||||||
"vkey",
|
"vkey",
|
||||||
|
"wagmi",
|
||||||
"waku",
|
"waku",
|
||||||
"wakuconnect",
|
"wakuconnect",
|
||||||
"wakunode",
|
"wakunode",
|
||||||
|
|||||||
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@ -113,12 +113,44 @@ jobs:
|
|||||||
node-version: ${{ env.NODE_JS }}
|
node-version: ${{ env.NODE_JS }}
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
if: ${{ steps.release.outputs.releases_created }}
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
- run: npm install
|
- run: npm install
|
||||||
if: ${{ steps.release.outputs.releases_created }}
|
if: ${{ steps.release.outputs.releases_created }}
|
||||||
|
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
if: ${{ steps.release.outputs.releases_created }}
|
if: ${{ steps.release.outputs.releases_created }}
|
||||||
|
|
||||||
|
- name: Setup Foundry
|
||||||
|
if: ${{ steps.release.outputs.releases_created }}
|
||||||
|
uses: foundry-rs/foundry-toolchain@v1
|
||||||
|
with:
|
||||||
|
version: nightly
|
||||||
|
|
||||||
|
- name: Generate RLN contract ABIs
|
||||||
|
id: rln-abi
|
||||||
|
if: ${{ steps.release.outputs.releases_created }}
|
||||||
|
run: |
|
||||||
|
npm run setup:contract-abi -w @waku/rln || {
|
||||||
|
echo "::warning::Failed to generate contract ABIs, marking @waku/rln as private to skip publishing"
|
||||||
|
cd packages/rln
|
||||||
|
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||||
|
echo "failed=true" >> $GITHUB_OUTPUT
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Rebuild with new ABIs
|
||||||
|
if: ${{ steps.release.outputs.releases_created && steps.rln-abi.outputs.failed != 'true' }}
|
||||||
|
run: |
|
||||||
|
npm install -w packages/rln
|
||||||
|
npm run build -w @waku/rln || {
|
||||||
|
echo "::warning::Failed to build @waku/rln, marking as private to skip publishing"
|
||||||
|
cd packages/rln
|
||||||
|
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||||
|
}
|
||||||
|
|
||||||
- run: npm run publish
|
- run: npm run publish
|
||||||
if: ${{ steps.release.outputs.releases_created }}
|
if: ${{ steps.release.outputs.releases_created }}
|
||||||
env:
|
env:
|
||||||
|
|||||||
30
.github/workflows/pre-release.yml
vendored
30
.github/workflows/pre-release.yml
vendored
@ -17,16 +17,46 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: waku-org/js-waku
|
repository: waku-org/js-waku
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_JS }}
|
node-version: ${{ env.NODE_JS }}
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
|
||||||
|
- name: Setup Foundry
|
||||||
|
uses: foundry-rs/foundry-toolchain@v1
|
||||||
|
with:
|
||||||
|
version: nightly
|
||||||
|
|
||||||
|
- name: Generate RLN contract ABIs
|
||||||
|
id: rln-abi
|
||||||
|
run: |
|
||||||
|
npm run setup:contract-abi -w @waku/rln || {
|
||||||
|
echo "::warning::Failed to generate contract ABIs, marking @waku/rln as private to skip publishing"
|
||||||
|
cd packages/rln
|
||||||
|
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||||
|
echo "failed=true" >> $GITHUB_OUTPUT
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Rebuild with new ABIs
|
||||||
|
if: steps.rln-abi.outputs.failed != 'true'
|
||||||
|
run: |
|
||||||
|
npm install -w packages/rln
|
||||||
|
npm run build -w @waku/rln || {
|
||||||
|
echo "::warning::Failed to build @waku/rln, marking as private to skip publishing"
|
||||||
|
cd packages/rln
|
||||||
|
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); pkg.private = true; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
|
||||||
|
}
|
||||||
|
|
||||||
- run: npm run publish -- --tag next
|
- run: npm run publish -- --tag next
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_JS_WAKU_PUBLISH }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_JS_WAKU_PUBLISH }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,3 +20,4 @@ packages/discovery/mock_local_storage
|
|||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
.env
|
.env
|
||||||
postgres-data/
|
postgres-data/
|
||||||
|
packages/rln/waku-rlnv2-contract/
|
||||||
|
|||||||
1373
package-lock.json
generated
1373
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,5 +3,10 @@ module.exports = {
|
|||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
project: "./tsconfig.dev.json"
|
project: "./tsconfig.dev.json"
|
||||||
},
|
},
|
||||||
ignorePatterns: ["src/resources/**/*"]
|
ignorePatterns: ["src/resources/**/*"],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["*.config.ts", "*.config.js"]
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,6 +12,18 @@ This package provides RLN functionality for the Waku protocol, enabling rate-lim
|
|||||||
npm install @waku/rln
|
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
|
## Usage
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@ -20,11 +32,6 @@ import { RLN } from '@waku/rln';
|
|||||||
// Usage examples coming soon
|
// Usage examples coming soon
|
||||||
```
|
```
|
||||||
|
|
||||||
## Constants
|
|
||||||
|
|
||||||
- Implementation contract: 0xde2260ca49300357d5af4153cda0d18f7b3ea9b3
|
|
||||||
- Proxy contract: 0xb9cd878c90e49f797b4431fbf4fb333108cb90e6
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT OR Apache-2.0
|
MIT OR Apache-2.0
|
||||||
66
packages/rln/generate_contract_abi.js
Normal file
66
packages/rln/generate_contract_abi.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { execSync } from "child_process";
|
||||||
|
import { existsSync, rmSync } from "fs";
|
||||||
|
import { dirname, join } from "path";
|
||||||
|
import process from "process";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
// Get script directory (equivalent to BASH_SOURCE in bash)
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const CONTRACT_DIR = join(__dirname, "waku-rlnv2-contract");
|
||||||
|
const REPO_URL = "https://github.com/waku-org/waku-rlnv2-contract.git";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a shell command and print output in real-time
|
||||||
|
* @param {string} command - The command to execute
|
||||||
|
* @param {object} options - Options for execSync
|
||||||
|
*/
|
||||||
|
function exec(command, options = {}) {
|
||||||
|
execSync(command, {
|
||||||
|
stdio: "inherit",
|
||||||
|
cwd: options.cwd || __dirname,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
console.log("📦 Setting up waku-rlnv2-contract...");
|
||||||
|
|
||||||
|
// Remove existing directory if it exists
|
||||||
|
if (existsSync(CONTRACT_DIR)) {
|
||||||
|
console.log("🗑️ Removing existing waku-rlnv2-contract directory...");
|
||||||
|
rmSync(CONTRACT_DIR, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone the repository
|
||||||
|
console.log("📥 Cloning waku-rlnv2-contract...");
|
||||||
|
exec(`git clone ${REPO_URL} ${CONTRACT_DIR}`);
|
||||||
|
|
||||||
|
// Install dependencies
|
||||||
|
console.log("📦 Installing dependencies...");
|
||||||
|
exec("pnpm i", { cwd: CONTRACT_DIR });
|
||||||
|
|
||||||
|
// Build contracts with Foundry
|
||||||
|
console.log("🔨 Building contracts with Foundry...");
|
||||||
|
exec("forge build", { cwd: CONTRACT_DIR });
|
||||||
|
|
||||||
|
// Generate ABIs with wagmi
|
||||||
|
console.log("⚙️ Generating ABIs with wagmi...");
|
||||||
|
exec("npx wagmi generate");
|
||||||
|
|
||||||
|
console.log("✅ Contract ABIs generated successfully!");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"❌ Error generating contract ABIs:",
|
||||||
|
error instanceof Error ? error.message : error
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@ -43,7 +43,8 @@
|
|||||||
"watch:build": "tsc -p tsconfig.json -w",
|
"watch:build": "tsc -p tsconfig.json -w",
|
||||||
"watch:test": "mocha --watch",
|
"watch:test": "mocha --watch",
|
||||||
"prepublish": "npm run build",
|
"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": "node generate_contract_abi.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
@ -54,12 +55,13 @@
|
|||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^5.0.1",
|
"@types/chai": "^5.0.1",
|
||||||
"@types/chai-spies": "^1.0.6",
|
"@types/chai-spies": "^1.0.6",
|
||||||
"@waku/interfaces": "0.0.34",
|
|
||||||
"@types/deep-equal-in-any-order": "^1.0.4",
|
"@types/deep-equal-in-any-order": "^1.0.4",
|
||||||
"@types/lodash": "^4.17.15",
|
"@types/lodash": "^4.17.15",
|
||||||
"@types/sinon": "^17.0.3",
|
"@types/sinon": "^17.0.3",
|
||||||
|
"@wagmi/cli": "^2.7.0",
|
||||||
"@waku/build-utils": "^1.0.0",
|
"@waku/build-utils": "^1.0.0",
|
||||||
"@waku/message-encryption": "^0.0.38",
|
"@waku/interfaces": "0.0.34",
|
||||||
|
"@waku/message-encryption": "^0.0.37",
|
||||||
"deep-equal-in-any-order": "^2.0.6",
|
"deep-equal-in-any-order": "^2.0.6",
|
||||||
"fast-check": "^3.23.2",
|
"fast-check": "^3.23.2",
|
||||||
"rollup-plugin-copy": "^3.5.0"
|
"rollup-plugin-copy": "^3.5.0"
|
||||||
@ -76,18 +78,19 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainsafe/bls-keystore": "3.0.0",
|
"@chainsafe/bls-keystore": "3.0.0",
|
||||||
|
"@noble/hashes": "^1.2.0",
|
||||||
|
"@wagmi/core": "^2.22.1",
|
||||||
"@waku/core": "^0.0.40",
|
"@waku/core": "^0.0.40",
|
||||||
"@waku/utils": "^0.0.27",
|
"@waku/utils": "^0.0.27",
|
||||||
"@noble/hashes": "^1.2.0",
|
|
||||||
"@waku/zerokit-rln-wasm": "^0.2.1",
|
"@waku/zerokit-rln-wasm": "^0.2.1",
|
||||||
"ethereum-cryptography": "^3.1.0",
|
|
||||||
"ethers": "^5.7.2",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"uuid": "^11.0.5",
|
|
||||||
"chai": "^5.1.2",
|
"chai": "^5.1.2",
|
||||||
"chai-as-promised": "^8.0.1",
|
"chai-as-promised": "^8.0.1",
|
||||||
"chai-spies": "^1.1.0",
|
"chai-spies": "^1.1.0",
|
||||||
"chai-subset": "^1.6.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@ -1,16 +1,15 @@
|
|||||||
import { PRICE_CALCULATOR_ABI } from "./abi/price_calculator.js";
|
import { linearPriceCalculatorAbi, wakuRlnV2Abi } from "./wagmi/generated.js";
|
||||||
import { RLN_ABI } from "./abi/rln.js";
|
|
||||||
|
|
||||||
export const RLN_CONTRACT = {
|
export const RLN_CONTRACT = {
|
||||||
chainId: 59141,
|
chainId: 59141,
|
||||||
address: "0xb9cd878c90e49f797b4431fbf4fb333108cb90e6",
|
address: "0xb9cd878c90e49f797b4431fbf4fb333108cb90e6",
|
||||||
abi: RLN_ABI
|
abi: wakuRlnV2Abi
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PRICE_CALCULATOR_CONTRACT = {
|
export const PRICE_CALCULATOR_CONTRACT = {
|
||||||
chainId: 59141,
|
chainId: 59141,
|
||||||
address: "0xBcfC0660Df69f53ab409F32bb18A3fb625fcE644",
|
address: "0xBcfC0660Df69f53ab409F32bb18A3fb625fcE644",
|
||||||
abi: PRICE_CALCULATOR_ABI
|
abi: linearPriceCalculatorAbi
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,28 +1,39 @@
|
|||||||
import { expect, use } from "chai";
|
import { expect, use } from "chai";
|
||||||
import chaiAsPromised from "chai-as-promised";
|
import chaiAsPromised from "chai-as-promised";
|
||||||
import { ethers } from "ethers";
|
|
||||||
import sinon from "sinon";
|
import sinon from "sinon";
|
||||||
|
|
||||||
import { RLNBaseContract } from "./rln_base_contract.js";
|
import { RLNBaseContract } from "./rln_base_contract.js";
|
||||||
|
|
||||||
use(chaiAsPromised);
|
use(chaiAsPromised);
|
||||||
|
|
||||||
function createMockRLNBaseContract(provider: any): RLNBaseContract {
|
function createMockRLNBaseContract(
|
||||||
|
mockContract: any,
|
||||||
|
mockRpcClient: any
|
||||||
|
): RLNBaseContract {
|
||||||
const dummy = Object.create(RLNBaseContract.prototype);
|
const dummy = Object.create(RLNBaseContract.prototype);
|
||||||
dummy.contract = { provider };
|
dummy.contract = mockContract;
|
||||||
|
dummy.rpcClient = mockRpcClient;
|
||||||
return dummy as RLNBaseContract;
|
return dummy as RLNBaseContract;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("RLNBaseContract.getPriceForRateLimit (unit)", function () {
|
describe("RLNBaseContract.getPriceForRateLimit (unit)", function () {
|
||||||
let provider: any;
|
let mockContract: any;
|
||||||
let calculateStub: sinon.SinonStub;
|
let mockRpcClient: any;
|
||||||
let mockContractFactory: any;
|
let priceCalculatorReadStub: sinon.SinonStub;
|
||||||
|
let readContractStub: sinon.SinonStub;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
provider = {};
|
priceCalculatorReadStub = sinon.stub();
|
||||||
calculateStub = sinon.stub();
|
readContractStub = sinon.stub();
|
||||||
mockContractFactory = function () {
|
|
||||||
return { calculate: calculateStub };
|
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 () => {
|
it("returns token and price for valid calculate", async () => {
|
||||||
const fakeToken = "0x1234567890abcdef1234567890abcdef12345678";
|
const fakeToken = "0x1234567890abcdef1234567890abcdef12345678";
|
||||||
const fakePrice = ethers.BigNumber.from(42);
|
const fakePrice = 42n;
|
||||||
calculateStub.resolves([fakeToken, fakePrice]);
|
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.token).to.equal(fakeToken);
|
||||||
expect(result.price).to.not.be.null;
|
expect(result.price).to.equal(fakePrice);
|
||||||
if (result.price) {
|
expect(priceCalculatorReadStub.calledOnce).to.be.true;
|
||||||
expect(result.price.eq(fakePrice)).to.be.true;
|
expect(readContractStub.calledOnce).to.be.true;
|
||||||
}
|
|
||||||
expect(calculateStub.calledOnceWith(20)).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 () => {
|
it("throws if calculate throws", async () => {
|
||||||
calculateStub.rejects(new Error("fail"));
|
const priceCalculatorAddress = "0xabcdef1234567890abcdef1234567890abcdef12";
|
||||||
|
|
||||||
const rlnBase = createMockRLNBaseContract(provider);
|
priceCalculatorReadStub.resolves(priceCalculatorAddress);
|
||||||
await expect(
|
readContractStub.rejects(new Error("fail"));
|
||||||
rlnBase.getPriceForRateLimit(20, mockContractFactory)
|
|
||||||
).to.be.rejectedWith("fail");
|
const rlnBase = createMockRLNBaseContract(mockContract, mockRpcClient);
|
||||||
expect(calculateStub.calledOnceWith(20)).to.be.true;
|
await expect(rlnBase.getPriceForRateLimit(20)).to.be.rejectedWith("fail");
|
||||||
|
|
||||||
|
expect(priceCalculatorReadStub.calledOnce).to.be.true;
|
||||||
|
expect(readContractStub.calledOnce).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws if calculate returns malformed data", async () => {
|
it("returns null values if calculate returns malformed data", async () => {
|
||||||
calculateStub.resolves([null, null]);
|
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.token).to.be.null;
|
||||||
expect(result.price).to.be.null;
|
expect(result.price).to.be.null;
|
||||||
|
expect(priceCalculatorReadStub.calledOnce).to.be.true;
|
||||||
|
expect(readContractStub.calledOnce).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,92 +1,74 @@
|
|||||||
import { Logger } from "@waku/utils";
|
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 { 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 {
|
import {
|
||||||
DEFAULT_RATE_LIMIT,
|
DEFAULT_RATE_LIMIT,
|
||||||
PRICE_CALCULATOR_CONTRACT,
|
RATE_LIMIT_PARAMS,
|
||||||
RATE_LIMIT_PARAMS
|
RLN_CONTRACT
|
||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import {
|
import {
|
||||||
CustomQueryOptions,
|
|
||||||
FetchMembersOptions,
|
|
||||||
Member,
|
|
||||||
MembershipInfo,
|
MembershipInfo,
|
||||||
MembershipRegisteredEvent,
|
|
||||||
MembershipState,
|
MembershipState,
|
||||||
RLNContractInitOptions
|
RLNContractOptions
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
import { iPriceCalculatorAbi, wakuRlnV2Abi } from "./wagmi/generated.js";
|
||||||
|
|
||||||
const log = new Logger("rln:contract:base");
|
const log = new Logger("rln:contract:base");
|
||||||
|
|
||||||
export class RLNBaseContract {
|
export class RLNBaseContract {
|
||||||
public contract: ethers.Contract;
|
public contract: GetContractReturnType<
|
||||||
private deployBlock: undefined | number;
|
typeof wakuRlnV2Abi,
|
||||||
|
PublicClient | WalletClient
|
||||||
|
>;
|
||||||
|
public rpcClient: RpcClient;
|
||||||
private rateLimit: number;
|
private rateLimit: number;
|
||||||
private minRateLimit?: number;
|
private minRateLimit?: number;
|
||||||
private maxRateLimit?: number;
|
private maxRateLimit?: number;
|
||||||
|
|
||||||
protected _members: Map<number, Member> = new Map();
|
|
||||||
private _membersFilter: ethers.EventFilter;
|
|
||||||
private _membershipErasedFilter: ethers.EventFilter;
|
|
||||||
private _membersExpiredFilter: ethers.EventFilter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor for RLNBaseContract. Use static create() instead.
|
* Private constructor for RLNBaseContract. Use static create() instead.
|
||||||
*/
|
*/
|
||||||
protected constructor(options: RLNContractInitOptions) {
|
protected constructor(options: RLNContractOptions) {
|
||||||
const {
|
const { address, rpcClient, rateLimit = DEFAULT_RATE_LIMIT } = options;
|
||||||
address,
|
|
||||||
signer,
|
|
||||||
rateLimit = DEFAULT_RATE_LIMIT,
|
|
||||||
contract
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
log.info("Initializing RLNBaseContract", { address, rateLimit });
|
log.info("Initializing RLNBaseContract", { address, rateLimit });
|
||||||
|
|
||||||
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
this.rpcClient = rpcClient;
|
||||||
this.rateLimit = rateLimit;
|
this.contract = getContract({
|
||||||
|
address,
|
||||||
try {
|
abi: wakuRlnV2Abi,
|
||||||
log.info("Setting up event filters");
|
client: this.rpcClient
|
||||||
// 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 });
|
|
||||||
});
|
});
|
||||||
|
this.rateLimit = rateLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static async factory to create and initialize RLNBaseContract
|
* Static async factory to create and initialize RLNBaseContract
|
||||||
*/
|
*/
|
||||||
public static async create(
|
public static async create(
|
||||||
options: RLNContractInitOptions
|
options: RLNContractOptions
|
||||||
): Promise<RLNBaseContract> {
|
): Promise<RLNBaseContract> {
|
||||||
const instance = new RLNBaseContract(options);
|
const instance = new RLNBaseContract(options);
|
||||||
|
|
||||||
const [min, max] = await Promise.all([
|
const [min, max] = await Promise.all([
|
||||||
instance.contract.minMembershipRateLimit(),
|
instance.contract.read.minMembershipRateLimit(),
|
||||||
instance.contract.maxMembershipRateLimit()
|
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);
|
instance.validateRateLimit(instance.rateLimit);
|
||||||
return instance;
|
return instance;
|
||||||
@ -106,13 +88,6 @@ export class RLNBaseContract {
|
|||||||
return this.contract.address;
|
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)
|
* Gets the minimum allowed rate limit (cached)
|
||||||
*/
|
*/
|
||||||
@ -136,8 +111,7 @@ export class RLNBaseContract {
|
|||||||
* @returns Promise<number> The maximum total rate limit in messages per epoch
|
* @returns Promise<number> The maximum total rate limit in messages per epoch
|
||||||
*/
|
*/
|
||||||
public async getMaxTotalRateLimit(): Promise<number> {
|
public async getMaxTotalRateLimit(): Promise<number> {
|
||||||
const maxTotalRate = await this.contract.maxTotalRateLimit();
|
return await this.contract.read.maxTotalRateLimit();
|
||||||
return maxTotalRate.toNumber();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,8 +119,7 @@ export class RLNBaseContract {
|
|||||||
* @returns Promise<number> The current total rate limit usage in messages per epoch
|
* @returns Promise<number> The current total rate limit usage in messages per epoch
|
||||||
*/
|
*/
|
||||||
public async getCurrentTotalRateLimit(): Promise<number> {
|
public async getCurrentTotalRateLimit(): Promise<number> {
|
||||||
const currentTotal = await this.contract.currentTotalRateLimit();
|
return Number(await this.contract.read.currentTotalRateLimit());
|
||||||
return currentTotal.toNumber();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,11 +127,10 @@ export class RLNBaseContract {
|
|||||||
* @returns Promise<number> The remaining rate limit that can be allocated
|
* @returns Promise<number> The remaining rate limit that can be allocated
|
||||||
*/
|
*/
|
||||||
public async getRemainingTotalRateLimit(): Promise<number> {
|
public async getRemainingTotalRateLimit(): Promise<number> {
|
||||||
const [maxTotal, currentTotal] = await Promise.all([
|
return (
|
||||||
this.contract.maxTotalRateLimit(),
|
(await this.contract.read.maxTotalRateLimit()) -
|
||||||
this.contract.currentTotalRateLimit()
|
Number(await this.contract.read.currentTotalRateLimit())
|
||||||
]);
|
);
|
||||||
return Number(maxTotal) - Number(currentTotal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,233 +142,35 @@ export class RLNBaseContract {
|
|||||||
this.rateLimit = newRateLimit;
|
this.rateLimit = newRateLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get members(): Member[] {
|
/**
|
||||||
const sortedMembers = Array.from(this._members.values()).sort(
|
* Gets the Merkle tree root for RLN proof verification
|
||||||
(left, right) => left.index.toNumber() - right.index.toNumber()
|
* @returns Promise<bigint> The Merkle tree root
|
||||||
);
|
*
|
||||||
return sortedMembers;
|
*/
|
||||||
|
public async getMerkleRoot(): Promise<bigint> {
|
||||||
|
return this.contract.read.root();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
|
/**
|
||||||
const registeredMemberEvents = await RLNBaseContract.queryFilter(
|
* Gets the Merkle proof for a member at a given index
|
||||||
this.contract,
|
* @param index The index of the member in the membership set
|
||||||
{
|
* @returns Promise<bigint[]> Array of 20 Merkle proof elements
|
||||||
fromBlock: this.deployBlock,
|
*
|
||||||
...options,
|
*/
|
||||||
membersFilter: this.membersFilter
|
public async getMerkleProof(index: number): Promise<readonly bigint[]> {
|
||||||
}
|
return await this.contract.read.getMerkleProof([index]);
|
||||||
);
|
|
||||||
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<ethers.Event[]> {
|
|
||||||
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<number, number[]>();
|
|
||||||
const toInsertTable = new Map<number, ethers.Event[]>();
|
|
||||||
|
|
||||||
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<T>(array: T[], size: number): Iterable<T[]> {
|
|
||||||
let start = 0;
|
|
||||||
|
|
||||||
while (start < array.length) {
|
|
||||||
const portion = array.slice(start, start + size);
|
|
||||||
|
|
||||||
yield portion;
|
|
||||||
|
|
||||||
start += size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async ignoreErrors<T>(
|
|
||||||
promise: Promise<T>,
|
|
||||||
defaultValue: T
|
|
||||||
): Promise<T> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMembershipInfo(
|
public async getMembershipInfo(
|
||||||
idCommitmentBigInt: bigint
|
idCommitmentBigInt: bigint
|
||||||
): Promise<MembershipInfo | undefined> {
|
): Promise<MembershipInfo | undefined> {
|
||||||
try {
|
try {
|
||||||
const membershipData =
|
const membershipData = await this.contract.read.memberships([
|
||||||
await this.contract.memberships(idCommitmentBigInt);
|
idCommitmentBigInt
|
||||||
const currentBlock = await this.contract.provider.getBlockNumber();
|
]);
|
||||||
|
|
||||||
|
const currentBlock = await this.rpcClient.getBlockNumber();
|
||||||
|
|
||||||
const [
|
const [
|
||||||
depositAmount,
|
depositAmount,
|
||||||
activeDuration,
|
activeDuration,
|
||||||
@ -408,12 +182,13 @@ export class RLNBaseContract {
|
|||||||
token
|
token
|
||||||
] = membershipData;
|
] = membershipData;
|
||||||
|
|
||||||
const gracePeriodEnd = gracePeriodStartTimestamp.add(gracePeriodDuration);
|
const gracePeriodEnd =
|
||||||
|
Number(gracePeriodStartTimestamp) + Number(gracePeriodDuration);
|
||||||
|
|
||||||
let state: MembershipState;
|
let state: MembershipState;
|
||||||
if (currentBlock < gracePeriodStartTimestamp.toNumber()) {
|
if (currentBlock < Number(gracePeriodStartTimestamp)) {
|
||||||
state = MembershipState.Active;
|
state = MembershipState.Active;
|
||||||
} else if (currentBlock < gracePeriodEnd.toNumber()) {
|
} else if (currentBlock < gracePeriodEnd) {
|
||||||
state = MembershipState.GracePeriod;
|
state = MembershipState.GracePeriod;
|
||||||
} else {
|
} else {
|
||||||
state = MembershipState.Expired;
|
state = MembershipState.Expired;
|
||||||
@ -422,9 +197,9 @@ export class RLNBaseContract {
|
|||||||
return {
|
return {
|
||||||
index,
|
index,
|
||||||
idCommitment: idCommitmentBigInt.toString(),
|
idCommitment: idCommitmentBigInt.toString(),
|
||||||
rateLimit: Number(rateLimit),
|
rateLimit: rateLimit,
|
||||||
startBlock: gracePeriodStartTimestamp.toNumber(),
|
startBlock: Number(gracePeriodStartTimestamp),
|
||||||
endBlock: gracePeriodEnd.toNumber(),
|
endBlock: gracePeriodEnd,
|
||||||
state,
|
state,
|
||||||
depositAmount,
|
depositAmount,
|
||||||
activeDuration,
|
activeDuration,
|
||||||
@ -438,43 +213,87 @@ export class RLNBaseContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async extendMembership(
|
public async extendMembership(idCommitmentBigInt: bigint): Promise<Hash> {
|
||||||
idCommitmentBigInt: bigint
|
if (!this.rpcClient.account) {
|
||||||
): Promise<ethers.ContractTransaction> {
|
throw new Error(
|
||||||
const tx = await this.contract.extendMemberships([idCommitmentBigInt]);
|
"Failed to extendMembership: no account set in wallet client"
|
||||||
await tx.wait();
|
);
|
||||||
return tx;
|
}
|
||||||
|
try {
|
||||||
|
await this.contract.simulate.extendMemberships([[idCommitmentBigInt]], {
|
||||||
|
chain: this.rpcClient.chain,
|
||||||
|
account: this.rpcClient.account.address
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
throw new Error(
|
||||||
|
"Error simulating extending membership: " + err.message
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error("Error simulating extending membership", {
|
||||||
|
cause: 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(
|
public async eraseMembership(
|
||||||
idCommitmentBigInt: bigint,
|
idCommitmentBigInt: bigint,
|
||||||
eraseFromMembershipSet: boolean = true
|
eraseFromMembershipSet: boolean = true
|
||||||
): Promise<ethers.ContractTransaction> {
|
): Promise<Hash> {
|
||||||
if (
|
if (
|
||||||
!(await this.isExpired(idCommitmentBigInt)) ||
|
!(await this.isExpired(idCommitmentBigInt)) ||
|
||||||
!(await this.isInGracePeriod(idCommitmentBigInt))
|
!(await this.isInGracePeriod(idCommitmentBigInt))
|
||||||
) {
|
) {
|
||||||
throw new Error("Membership is not expired or in grace period");
|
throw new Error("Membership is not expired or in grace period");
|
||||||
}
|
}
|
||||||
|
if (!this.rpcClient.account) {
|
||||||
const estimatedGas = await this.contract.estimateGas[
|
throw new Error(
|
||||||
"eraseMemberships(uint256[],bool)"
|
"Failed to eraseMembership: no account set in wallet client"
|
||||||
]([idCommitmentBigInt], eraseFromMembershipSet);
|
|
||||||
const gasLimit = estimatedGas.add(10000);
|
|
||||||
|
|
||||||
const tx = await this.contract["eraseMemberships(uint256[],bool)"](
|
|
||||||
[idCommitmentBigInt],
|
|
||||||
eraseFromMembershipSet,
|
|
||||||
{ gasLimit }
|
|
||||||
);
|
);
|
||||||
await tx.wait();
|
}
|
||||||
return tx;
|
|
||||||
|
try {
|
||||||
|
await this.contract.simulate.eraseMemberships(
|
||||||
|
[[idCommitmentBigInt], eraseFromMembershipSet],
|
||||||
|
{
|
||||||
|
chain: this.rpcClient.chain,
|
||||||
|
account: this.rpcClient.account.address
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
throw new Error("Error simulating eraseMemberships: " + err.message);
|
||||||
|
} else {
|
||||||
|
throw new Error("Error simulating eraseMemberships", { cause: err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = await this.contract.write.eraseMemberships(
|
||||||
|
[[idCommitmentBigInt], eraseFromMembershipSet],
|
||||||
|
{
|
||||||
|
chain: this.rpcClient.chain,
|
||||||
|
account: this.rpcClient.account
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await this.rpcClient.waitForTransactionReceipt({ hash });
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async registerMembership(
|
public async registerMembership(
|
||||||
idCommitmentBigInt: bigint,
|
idCommitmentBigInt: bigint,
|
||||||
rateLimit: number = DEFAULT_RATE_LIMIT
|
rateLimit: number = DEFAULT_RATE_LIMIT
|
||||||
): Promise<ethers.ContractTransaction> {
|
): Promise<Hash> {
|
||||||
if (
|
if (
|
||||||
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
||||||
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
||||||
@ -483,21 +302,80 @@ export class RLNBaseContract {
|
|||||||
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
`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) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
throw new Error("Error simulating register membership: " + err.message);
|
||||||
|
} else {
|
||||||
|
throw new Error("Error simulating register membership", { cause: err });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withdraw(token: string, walletAddress: string): Promise<void> {
|
const hash = await this.contract.write.register(
|
||||||
try {
|
[idCommitmentBigInt, rateLimit, []],
|
||||||
const tx = await this.contract.withdraw(token, walletAddress);
|
{
|
||||||
await tx.wait();
|
chain: this.rpcClient.chain,
|
||||||
} catch (error) {
|
account: this.rpcClient.account
|
||||||
log.error(`Error in withdraw: ${(error as Error).message}`);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
await this.rpcClient.waitForTransactionReceipt({ hash });
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<Hash> {
|
||||||
|
if (!this.rpcClient.account) {
|
||||||
|
throw new Error("Failed to withdraw: no account set in wallet client");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.contract.simulate.withdraw([token as Address], {
|
||||||
|
chain: this.rpcClient.chain,
|
||||||
|
account: this.rpcClient.account.address
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
throw new Error("Error simulating withdraw: " + err.message);
|
||||||
|
} else {
|
||||||
|
throw new Error("Error simulating withdraw", { cause: 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(
|
public async registerWithIdentity(
|
||||||
identity: IdentityCredential
|
identity: IdentityCredential
|
||||||
): Promise<DecryptedCredentials | undefined> {
|
): Promise<DecryptedCredentials | undefined> {
|
||||||
try {
|
try {
|
||||||
|
if (!this.rpcClient.account) {
|
||||||
|
throw new Error(
|
||||||
|
"Failed to registerWithIdentity: no account set in wallet client"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
`Registering identity with rate limit: ${this.rateLimit} messages/epoch`
|
`Registering identity with rate limit: ${this.rateLimit} messages/epoch`
|
||||||
);
|
);
|
||||||
@ -520,62 +398,71 @@ export class RLNBaseContract {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const estimatedGas = await this.contract.estimateGas.register(
|
await this.contract.simulate.register(
|
||||||
identity.IDCommitmentBigInt,
|
[identity.IDCommitmentBigInt, this.rateLimit, []],
|
||||||
this.rateLimit,
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const gasLimit = estimatedGas.add(10000);
|
|
||||||
|
|
||||||
const txRegisterResponse: ethers.ContractTransaction =
|
|
||||||
await this.contract.register(
|
|
||||||
identity.IDCommitmentBigInt,
|
|
||||||
this.rateLimit,
|
|
||||||
[],
|
|
||||||
{
|
{
|
||||||
gasLimit
|
chain: this.rpcClient.chain,
|
||||||
|
account: this.rpcClient.account.address
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
const hash: Hash = await this.contract.write.register(
|
||||||
|
[identity.IDCommitmentBigInt, this.rateLimit, []],
|
||||||
|
{
|
||||||
|
chain: this.rpcClient.chain,
|
||||||
|
account: this.rpcClient.account
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (txRegisterReceipt.status === 0) {
|
const txRegisterReceipt = await this.rpcClient.waitForTransactionReceipt({
|
||||||
|
hash
|
||||||
|
});
|
||||||
|
|
||||||
|
if (txRegisterReceipt.status === "reverted") {
|
||||||
throw new Error("Transaction failed on-chain");
|
throw new Error("Transaction failed on-chain");
|
||||||
}
|
}
|
||||||
|
|
||||||
const memberRegistered = txRegisterReceipt.events?.find(
|
// Parse MembershipRegistered event from logs
|
||||||
(event: ethers.Event) => event.event === "MembershipRegistered"
|
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(
|
log.error(
|
||||||
"Failed to register membership: No MembershipRegistered event found"
|
"Failed to register membership: No MembershipRegistered event found"
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decodedData: MembershipRegisteredEvent = {
|
// Decode the event
|
||||||
idCommitment: memberRegistered.args.idCommitment,
|
const decoded = decodeEventLog({
|
||||||
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
abi: wakuRlnV2Abi,
|
||||||
index: memberRegistered.args.index
|
data: memberRegisteredLog.data,
|
||||||
};
|
topics: memberRegisteredLog.topics,
|
||||||
|
eventName: "MembershipRegistered"
|
||||||
|
});
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
`Successfully registered membership with index ${decodedData.index} ` +
|
`Successfully registered membership with index ${decoded.args.index} ` +
|
||||||
`and rate limit ${decodedData.membershipRateLimit}`
|
`and rate limit ${decoded.args.membershipRateLimit}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const network = await this.contract.provider.getNetwork();
|
|
||||||
const address = this.contract.address;
|
|
||||||
const membershipId = Number(decodedData.index);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
identity,
|
identity,
|
||||||
membership: {
|
membership: {
|
||||||
address,
|
address: this.contract.address,
|
||||||
treeIndex: membershipId,
|
treeIndex: decoded.args.index,
|
||||||
chainId: network.chainId.toString(),
|
chainId: String(RLN_CONTRACT.chainId),
|
||||||
rateLimit: decodedData.membershipRateLimit.toNumber()
|
rateLimit: Number(decoded.args.membershipRateLimit)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -608,78 +495,6 @@ export class RLNBaseContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async registerWithPermitAndErase(
|
|
||||||
identity: IdentityCredential,
|
|
||||||
permit: {
|
|
||||||
owner: string;
|
|
||||||
deadline: number;
|
|
||||||
v: number;
|
|
||||||
r: string;
|
|
||||||
s: string;
|
|
||||||
},
|
|
||||||
idCommitmentsToErase: string[]
|
|
||||||
): Promise<DecryptedCredentials | undefined> {
|
|
||||||
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)
|
* Validates that the rate limit is within the allowed range (sync)
|
||||||
* @throws Error if the rate limit is outside the allowed range
|
* @throws Error if the rate limit is outside the allowed range
|
||||||
@ -695,50 +510,17 @@ export class RLNBaseContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get membersFilter(): ethers.EventFilter {
|
private async getMemberIndex(idCommitmentBigInt: bigint): Promise<number> {
|
||||||
if (!this._membersFilter) {
|
// Current version of the contract has the index at position 5 in the membership struct
|
||||||
throw Error("Members filter was not initialized.");
|
return (await this.contract.read.memberships([idCommitmentBigInt]))[5];
|
||||||
}
|
|
||||||
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<ethers.BigNumber | undefined> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMembershipStatus(
|
public async getMembershipStatus(
|
||||||
idCommitment: bigint
|
idCommitment: bigint
|
||||||
): Promise<"expired" | "grace" | "active"> {
|
): Promise<"expired" | "grace" | "active"> {
|
||||||
const [isExpired, isInGrace] = await Promise.all([
|
const [isExpired, isInGrace] = await Promise.all([
|
||||||
this.contract.isExpired(idCommitment),
|
this.contract.read.isExpired([idCommitment]),
|
||||||
this.contract.isInGracePeriod(idCommitment)
|
this.contract.read.isInGracePeriod([idCommitment])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isExpired) return "expired";
|
if (isExpired) return "expired";
|
||||||
@ -753,7 +535,7 @@ export class RLNBaseContract {
|
|||||||
*/
|
*/
|
||||||
public async isExpired(idCommitmentBigInt: bigint): Promise<boolean> {
|
public async isExpired(idCommitmentBigInt: bigint): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
return await this.contract.isExpired(idCommitmentBigInt);
|
return await this.contract.read.isExpired([idCommitmentBigInt]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Error in isExpired:", error);
|
log.error("Error in isExpired:", error);
|
||||||
return false;
|
return false;
|
||||||
@ -767,7 +549,7 @@ export class RLNBaseContract {
|
|||||||
*/
|
*/
|
||||||
public async isInGracePeriod(idCommitmentBigInt: bigint): Promise<boolean> {
|
public async isInGracePeriod(idCommitmentBigInt: bigint): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
return await this.contract.isInGracePeriod(idCommitmentBigInt);
|
return await this.contract.read.isInGracePeriod([idCommitmentBigInt]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Error in isInGracePeriod:", error);
|
log.error("Error in isInGracePeriod:", error);
|
||||||
return false;
|
return false;
|
||||||
@ -779,21 +561,18 @@ export class RLNBaseContract {
|
|||||||
* @param rateLimit The rate limit to calculate the price for
|
* @param rateLimit The rate limit to calculate the price for
|
||||||
* @param contractFactory Optional factory for creating the contract (for testing)
|
* @param contractFactory Optional factory for creating the contract (for testing)
|
||||||
*/
|
*/
|
||||||
public async getPriceForRateLimit(
|
public async getPriceForRateLimit(rateLimit: number): Promise<{
|
||||||
rateLimit: number,
|
|
||||||
contractFactory?: typeof import("ethers").Contract
|
|
||||||
): Promise<{
|
|
||||||
token: string | null;
|
token: string | null;
|
||||||
price: import("ethers").BigNumber | null;
|
price: bigint | null;
|
||||||
}> {
|
}> {
|
||||||
const provider = this.contract.provider;
|
const address = await this.contract.read.priceCalculator();
|
||||||
const ContractCtor = contractFactory || ethers.Contract;
|
const [token, price] = await this.rpcClient.readContract({
|
||||||
const priceCalculator = new ContractCtor(
|
address,
|
||||||
PRICE_CALCULATOR_CONTRACT.address,
|
abi: iPriceCalculatorAbi,
|
||||||
PRICE_CALCULATOR_CONTRACT.abi,
|
functionName: "calculate",
|
||||||
provider
|
args: [rateLimit]
|
||||||
);
|
});
|
||||||
const [token, price] = await priceCalculator.calculate(rateLimit);
|
|
||||||
// Defensive: if token or price is null/undefined, return nulls
|
// Defensive: if token or price is null/undefined, return nulls
|
||||||
if (!token || !price) {
|
if (!token || !price) {
|
||||||
return { token: null, price: null };
|
return { token: null, price: null };
|
||||||
|
|||||||
@ -1,28 +1,22 @@
|
|||||||
import { ethers } from "ethers";
|
import { Address } from "viem";
|
||||||
|
|
||||||
export interface CustomQueryOptions extends FetchMembersOptions {
|
import { RpcClient } from "../utils/index.js";
|
||||||
membersFilter: ethers.EventFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Member = {
|
export type Member = {
|
||||||
idCommitment: string;
|
idCommitment: string;
|
||||||
index: ethers.BigNumber;
|
index: bigint;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RLNContractOptions {
|
export interface RLNContractOptions {
|
||||||
signer: ethers.Signer;
|
rpcClient: RpcClient;
|
||||||
address: string;
|
address: Address;
|
||||||
rateLimit?: number;
|
rateLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RLNContractInitOptions extends RLNContractOptions {
|
|
||||||
contract?: ethers.Contract;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MembershipRegisteredEvent {
|
export interface MembershipRegisteredEvent {
|
||||||
idCommitment: string;
|
idCommitment: string;
|
||||||
membershipRateLimit: ethers.BigNumber;
|
membershipRateLimit: bigint;
|
||||||
index: ethers.BigNumber;
|
index: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FetchMembersOptions = {
|
export type FetchMembersOptions = {
|
||||||
@ -32,13 +26,13 @@ export type FetchMembersOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface MembershipInfo {
|
export interface MembershipInfo {
|
||||||
index: ethers.BigNumber;
|
index: number;
|
||||||
idCommitment: string;
|
idCommitment: string;
|
||||||
rateLimit: number;
|
rateLimit: number;
|
||||||
startBlock: number;
|
startBlock: number;
|
||||||
endBlock: number;
|
endBlock: number;
|
||||||
state: MembershipState;
|
state: MembershipState;
|
||||||
depositAmount: ethers.BigNumber;
|
depositAmount: bigint;
|
||||||
activeDuration: number;
|
activeDuration: number;
|
||||||
gracePeriodDuration: number;
|
gracePeriodDuration: number;
|
||||||
holder: string;
|
holder: string;
|
||||||
|
|||||||
1013
packages/rln/src/contract/wagmi/generated.ts
Normal file
1013
packages/rln/src/contract/wagmi/generated.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
import { Logger } from "@waku/utils";
|
import { Logger } from "@waku/utils";
|
||||||
import { ethers } from "ethers";
|
import { publicActions } from "viem";
|
||||||
|
|
||||||
import { RLN_CONTRACT } from "./contract/constants.js";
|
import { RLN_CONTRACT } from "./contract/constants.js";
|
||||||
import { RLNBaseContract } from "./contract/rln_base_contract.js";
|
import { RLNBaseContract } from "./contract/rln_base_contract.js";
|
||||||
@ -10,7 +10,7 @@ import type {
|
|||||||
} from "./keystore/index.js";
|
} from "./keystore/index.js";
|
||||||
import { KeystoreEntity, Password } from "./keystore/types.js";
|
import { KeystoreEntity, Password } from "./keystore/types.js";
|
||||||
import { RegisterMembershipOptions, StartRLNOptions } from "./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";
|
import { Zerokit } from "./zerokit.js";
|
||||||
|
|
||||||
const log = new Logger("rln:credentials");
|
const log = new Logger("rln:credentials");
|
||||||
@ -24,7 +24,7 @@ export class RLNCredentialsManager {
|
|||||||
protected starting = false;
|
protected starting = false;
|
||||||
|
|
||||||
public contract: undefined | RLNBaseContract;
|
public contract: undefined | RLNBaseContract;
|
||||||
public signer: undefined | ethers.Signer;
|
public rpcClient: undefined | RpcClient;
|
||||||
|
|
||||||
protected keystore = Keystore.create();
|
protected keystore = Keystore.create();
|
||||||
public credentials: undefined | DecryptedCredentials;
|
public credentials: undefined | DecryptedCredentials;
|
||||||
@ -36,10 +36,6 @@ export class RLNCredentialsManager {
|
|||||||
this.zerokit = zerokit;
|
this.zerokit = zerokit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get provider(): undefined | ethers.providers.Provider {
|
|
||||||
return this.contract?.provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async start(options: StartRLNOptions = {}): Promise<void> {
|
public async start(options: StartRLNOptions = {}): Promise<void> {
|
||||||
if (this.started || this.starting) {
|
if (this.started || this.starting) {
|
||||||
log.info("RLNCredentialsManager already started or starting");
|
log.info("RLNCredentialsManager already started or starting");
|
||||||
@ -59,10 +55,8 @@ export class RLNCredentialsManager {
|
|||||||
log.info("Credentials successfully decrypted");
|
log.info("Credentials successfully decrypted");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { signer, address, rateLimit } = await this.determineStartOptions(
|
const { rpcClient, address, rateLimit } =
|
||||||
options,
|
await this.determineStartOptions(options, credentials);
|
||||||
credentials
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info(`Using contract address: ${address}`);
|
log.info(`Using contract address: ${address}`);
|
||||||
|
|
||||||
@ -72,10 +66,10 @@ export class RLNCredentialsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
this.signer = signer!;
|
this.rpcClient = rpcClient!;
|
||||||
this.contract = await RLNBaseContract.create({
|
this.contract = await RLNBaseContract.create({
|
||||||
address: address!,
|
address: address! as `0x${string}`,
|
||||||
signer: signer!,
|
rpcClient: this.rpcClient,
|
||||||
rateLimit: rateLimit ?? this.zerokit.rateLimit
|
rateLimit: rateLimit ?? this.zerokit.rateLimit
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -134,7 +128,7 @@ export class RLNCredentialsManager {
|
|||||||
protected async determineStartOptions(
|
protected async determineStartOptions(
|
||||||
options: StartRLNOptions,
|
options: StartRLNOptions,
|
||||||
credentials: KeystoreEntity | undefined
|
credentials: KeystoreEntity | undefined
|
||||||
): Promise<StartRLNOptions> {
|
): Promise<StartRLNOptions & { rpcClient: RpcClient }> {
|
||||||
let chainId = credentials?.membership.chainId;
|
let chainId = credentials?.membership.chainId;
|
||||||
const address =
|
const address =
|
||||||
credentials?.membership.address ||
|
credentials?.membership.address ||
|
||||||
@ -146,11 +140,14 @@ export class RLNCredentialsManager {
|
|||||||
log.info(`Using Linea contract with chainId: ${chainId}`);
|
log.info(`Using Linea contract with chainId: ${chainId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const signer = options.signer || (await extractMetaMaskSigner());
|
const rpcClient: RpcClient = options.walletClient
|
||||||
const currentChainId = await signer.getChainId();
|
? options.walletClient.extend(publicActions)
|
||||||
|
: await createViemClientFromWindow();
|
||||||
|
|
||||||
|
const currentChainId = rpcClient.chain?.id;
|
||||||
log.info(`Current chain ID: ${currentChainId}`);
|
log.info(`Current chain ID: ${currentChainId}`);
|
||||||
|
|
||||||
if (chainId && chainId !== currentChainId.toString()) {
|
if (chainId && chainId !== currentChainId?.toString()) {
|
||||||
log.error(
|
log.error(
|
||||||
`Chain ID mismatch: contract=${chainId}, current=${currentChainId}`
|
`Chain ID mismatch: contract=${chainId}, current=${currentChainId}`
|
||||||
);
|
);
|
||||||
@ -160,7 +157,7 @@ export class RLNCredentialsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
signer,
|
rpcClient,
|
||||||
address
|
address
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -206,9 +203,9 @@ export class RLNCredentialsManager {
|
|||||||
protected async verifyCredentialsAgainstContract(
|
protected async verifyCredentialsAgainstContract(
|
||||||
credentials: KeystoreEntity
|
credentials: KeystoreEntity
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.contract) {
|
if (!this.contract || !this.rpcClient) {
|
||||||
throw Error(
|
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 chainId = credentials.membership.chainId;
|
||||||
const network = await this.contract.provider.getNetwork();
|
const currentChainId = await this.rpcClient.getChainId();
|
||||||
const currentChainId = network.chainId;
|
|
||||||
if (chainId !== currentChainId.toString()) {
|
if (chainId !== currentChainId.toString()) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`Failed to verify chain coordinates: credentials chainID=${chainId} is not equal to registryContract chainID=${currentChainId}`
|
`Failed to verify chain coordinates: credentials chainID=${chainId} is not equal to registryContract chainID=${currentChainId}`
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { RLN_ABI } from "./contract/abi/rln.js";
|
|
||||||
import { RLN_CONTRACT } from "./contract/index.js";
|
import { RLN_CONTRACT } from "./contract/index.js";
|
||||||
import { RLNBaseContract } from "./contract/rln_base_contract.js";
|
import { RLNBaseContract } from "./contract/rln_base_contract.js";
|
||||||
import { createRLN } from "./create.js";
|
import { createRLN } from "./create.js";
|
||||||
import { IdentityCredential } from "./identity.js";
|
import { IdentityCredential } from "./identity.js";
|
||||||
import { Keystore } from "./keystore/index.js";
|
import { Keystore } from "./keystore/index.js";
|
||||||
import { RLNInstance } from "./rln.js";
|
import { RLNInstance } from "./rln.js";
|
||||||
import { extractMetaMaskSigner } from "./utils/index.js";
|
import { createViemClientFromWindow } from "./utils/index.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
RLNBaseContract,
|
RLNBaseContract,
|
||||||
@ -14,10 +13,16 @@ export {
|
|||||||
RLNInstance,
|
RLNInstance,
|
||||||
IdentityCredential,
|
IdentityCredential,
|
||||||
RLN_CONTRACT,
|
RLN_CONTRACT,
|
||||||
extractMetaMaskSigner,
|
createViemClientFromWindow
|
||||||
RLN_ABI
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
wakuRlnV2Abi,
|
||||||
|
linearPriceCalculatorAbi,
|
||||||
|
iPriceCalculatorAbi,
|
||||||
|
membershipUpgradeableAbi
|
||||||
|
} from "./contract/wagmi/generated.js";
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
DecryptedCredentials,
|
DecryptedCredentials,
|
||||||
EncryptedCredentials,
|
EncryptedCredentials,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ethers } from "ethers";
|
import { WalletClient } from "viem";
|
||||||
|
|
||||||
import { IdentityCredential } from "./identity.js";
|
import { IdentityCredential } from "./identity.js";
|
||||||
import {
|
import {
|
||||||
@ -8,9 +8,9 @@ import {
|
|||||||
|
|
||||||
export type StartRLNOptions = {
|
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.
|
* If not set - will use default SEPOLIA_CONTRACT address.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export { extractMetaMaskSigner } from "./metamask.js";
|
export { createViemClientFromWindow, RpcClient } from "./rpcClient.js";
|
||||||
export { BytesUtils } from "./bytes.js";
|
export { BytesUtils } from "./bytes.js";
|
||||||
export { sha256, poseidonHash } from "./hash.js";
|
export { sha256, poseidonHash } from "./hash.js";
|
||||||
export { dateToEpoch, epochIntToBytes, epochBytesToInt } from "./epoch.js";
|
export { dateToEpoch, epochIntToBytes, epochBytesToInt } from "./epoch.js";
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { ethers } from "ethers";
|
|
||||||
|
|
||||||
export const extractMetaMaskSigner = async (): Promise<ethers.Signer> => {
|
|
||||||
// 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();
|
|
||||||
};
|
|
||||||
61
packages/rln/src/utils/rpcClient.ts
Normal file
61
packages/rln/src/utils/rpcClient.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
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<RpcClient> => {
|
||||||
|
const ethereum = window.ethereum;
|
||||||
|
|
||||||
|
if (!ethereum) {
|
||||||
|
throw Error(
|
||||||
|
"Missing or invalid Ethereum provider. Please install a Web3 wallet such as MetaMask."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = await ethereum.request({ method: "eth_requestAccounts" });
|
||||||
|
if (!Array.isArray(accounts)) {
|
||||||
|
throw Error("Failed to get accounts");
|
||||||
|
}
|
||||||
|
const account = accounts[0] as Address;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.dev"
|
"extends": "../../tsconfig.dev",
|
||||||
|
"exclude": ["wagmi.config.ts"]
|
||||||
}
|
}
|
||||||
@ -6,5 +6,5 @@
|
|||||||
"tsBuildInfoFile": "dist/.tsbuildinfo"
|
"tsBuildInfoFile": "dist/.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["src/**/*.spec.ts", "src/test_utils"]
|
"exclude": ["wagmi.config.ts", "src/**/*.spec.ts", "src/test_utils"]
|
||||||
}
|
}
|
||||||
18
packages/rln/wagmi.config.ts
Normal file
18
packages/rln/wagmi.config.ts
Normal file
@ -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/**"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user