From 88417964ad9e3b047f4db39674eed44c8b9b4a99 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:49:52 +0000 Subject: [PATCH 1/2] refactor: sdk functions for poll and verification --- packages/cli/package.json | 1 + packages/cli/ts/index.ts | 34 +++- packages/sdk/.eslintrc.js | 20 ++ packages/sdk/.gitignore | 6 + packages/sdk/.npmignore | 4 + packages/sdk/README.md | 12 ++ packages/sdk/package.json | 77 ++++++++ packages/sdk/ts/index.ts | 16 ++ packages/sdk/ts/poll.ts | 89 +++++++++ packages/sdk/ts/tallyCommitments.ts | 55 ++++++ packages/sdk/ts/utils/contracts.ts | 24 +++ packages/sdk/ts/utils/index.ts | 12 ++ packages/sdk/ts/utils/interfaces.ts | 291 ++++++++++++++++++++++++++++ packages/sdk/ts/utils/trees.ts | 29 +++ packages/sdk/ts/utils/verifiers.ts | 92 +++++++++ packages/sdk/ts/verify.ts | 111 +++++++++++ packages/sdk/tsconfig.build.json | 8 + packages/sdk/tsconfig.json | 8 + packages/sdk/typedoc.json | 5 + pnpm-lock.yaml | 113 ++++++++--- 20 files changed, 978 insertions(+), 29 deletions(-) create mode 100644 packages/sdk/.eslintrc.js create mode 100644 packages/sdk/.gitignore create mode 100644 packages/sdk/.npmignore create mode 100644 packages/sdk/README.md create mode 100644 packages/sdk/package.json create mode 100644 packages/sdk/ts/index.ts create mode 100644 packages/sdk/ts/poll.ts create mode 100644 packages/sdk/ts/tallyCommitments.ts create mode 100644 packages/sdk/ts/utils/contracts.ts create mode 100644 packages/sdk/ts/utils/index.ts create mode 100644 packages/sdk/ts/utils/interfaces.ts create mode 100644 packages/sdk/ts/utils/trees.ts create mode 100644 packages/sdk/ts/utils/verifiers.ts create mode 100644 packages/sdk/ts/verify.ts create mode 100644 packages/sdk/tsconfig.build.json create mode 100644 packages/sdk/tsconfig.json create mode 100644 packages/sdk/typedoc.json diff --git a/packages/cli/package.json b/packages/cli/package.json index b7d3a0b088..b805287b28 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -54,6 +54,7 @@ "maci-core": "^2.5.0", "maci-crypto": "^2.5.0", "maci-domainobjs": "^2.5.0", + "maci-sdk": "^0.0.1", "prompt": "^1.3.0" }, "devDependencies": { diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index 1c428054d4..ca0a6a3054 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import { Command } from "@commander-js/extra-typings"; +import { generateTallyCommitments, getPollParams, verify, getPoll } from "maci-sdk"; import fs from "fs"; import path from "path"; @@ -15,14 +16,12 @@ import { deploy, showContracts, deployPoll, - getPoll, publish, setVerifyingKeys, mergeSignups, timeTravel, signup, isRegisteredUser, - verify, genProofs, fundWallet, proveOnChain, @@ -32,7 +31,7 @@ import { joinPoll, isJoinedUser, } from "./commands"; -import { TallyData, logError, promptSensitiveValue, readContractAddress } from "./utils"; +import { TallyData, banner, logError, logGreen, promptSensitiveValue, readContractAddress, success } from "./utils"; // set the description version and name of the cli tool const { description, version, name } = JSON.parse( @@ -535,7 +534,6 @@ program .description("Get deployed poll from MACI contract") .option("-p, --poll ", "the poll id") .option("-x, --maci-address ", "the MACI contract address") - .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .action(async (cmdObj) => { try { const signer = await getSigner(); @@ -543,12 +541,25 @@ program const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); - await getPoll({ + const details = await getPoll({ pollId: cmdObj.poll, maciAddress, signer, - quiet: cmdObj.quiet, }); + + logGreen( + true, + success( + [ + `ID: ${details.id}`, + `Deploy time: ${new Date(Number(details.deployTime) * 1000).toString()}`, + `End time: ${new Date(Number(details.deployTime) + Number(details.duration) * 1000).toString()}`, + `Number of signups ${details.numSignups}`, + `State tree merged: ${details.isMerged}`, + `Mode: ${details.mode === 0n ? "Quadratic Voting" : "Non-Quadratic Voting"}`, + ].join("\n"), + ), + ); } catch (error) { program.error((error as Error).message, { exitCode: 1 }); } @@ -582,6 +593,7 @@ program .option("-r, --rpc-provider ", "the rpc provider URL") .action(async (cmdObj) => { try { + banner(cmdObj.quiet); const signer = await getSigner(); const network = await signer.provider?.getNetwork(); @@ -595,12 +607,20 @@ program const maciAddress = tallyData.maci || cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); + const pollParams = await getPollParams({ pollId: cmdObj.pollId, maciContractAddress: maciAddress, signer }); + const tallyCommitments = generateTallyCommitments({ + tallyData, + voteOptionTreeDepth: pollParams.voteOptionTreeDepth, + }); + await verify({ tallyData, pollId: cmdObj.pollId, maciAddress, - quiet: cmdObj.quiet, signer, + tallyCommitments, + numVoteOptions: pollParams.numVoteOptions, + voteOptionTreeDepth: pollParams.voteOptionTreeDepth, }); } catch (error) { program.error((error as Error).message, { exitCode: 1 }); diff --git a/packages/sdk/.eslintrc.js b/packages/sdk/.eslintrc.js new file mode 100644 index 0000000000..fc75c956db --- /dev/null +++ b/packages/sdk/.eslintrc.js @@ -0,0 +1,20 @@ +const path = require("path"); + +module.exports = { + root: true, + extends: ["../../.eslintrc.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: path.resolve(__dirname, "./tsconfig.json"), + sourceType: "module", + typescript: true, + ecmaVersion: 2022, + experimentalDecorators: true, + requireConfigFile: false, + ecmaFeatures: { + classes: true, + impliedStrict: true, + }, + warnOnUnsupportedTypeScriptVersion: true, + }, +}; diff --git a/packages/sdk/.gitignore b/packages/sdk/.gitignore new file mode 100644 index 0000000000..d195e77ec3 --- /dev/null +++ b/packages/sdk/.gitignore @@ -0,0 +1,6 @@ +contractAddresses.json +contractAddresses.old.json +contractAddress.old +contractAddress.txt +localState.json +zkeys diff --git a/packages/sdk/.npmignore b/packages/sdk/.npmignore new file mode 100644 index 0000000000..069ee7f788 --- /dev/null +++ b/packages/sdk/.npmignore @@ -0,0 +1,4 @@ +tests +build/tests +.etherlime-store +.env diff --git a/packages/sdk/README.md b/packages/sdk/README.md new file mode 100644 index 0000000000..38d0842e65 --- /dev/null +++ b/packages/sdk/README.md @@ -0,0 +1,12 @@ +# maci-sdk + +[![NPM Package][cli-npm-badge]][cli-npm-link] +[![Actions Status][cli-actions-badge]][cli-actions-link] + +Please refer to the [documentation for the +CLI](https://maci.pse.dev/docs/developers-references/typescript-code/cli). + +[cli-npm-badge]: https://img.shields.io/npm/v/maci-sdk.svg +[cli-actions-badge]: https://github.com/privacy-scaling-explorations/maci/actions/workflows/e2e.yml/badge.svg +[cli-npm-link]: https://www.npmjs.com/package/maci-sdk +[cli-actions-link]: https://github.com/privacy-scaling-explorations/maci/actions?query=workflow%3ACI diff --git a/packages/sdk/package.json b/packages/sdk/package.json new file mode 100644 index 0000000000..0b8e6e6641 --- /dev/null +++ b/packages/sdk/package.json @@ -0,0 +1,77 @@ +{ + "name": "maci-sdk", + "version": "0.0.1", + "description": "MACI's SDK", + "main": "build/ts/index.js", + "exports": { + ".": { + "types": "./build/ts/index.d.ts", + "default": "./build/ts/index.js" + }, + "./sdk": { + "types": "./build/ts/sdk/index.d.ts", + "default": "./build/ts/sdk/index.js" + } + }, + "bin": { + "maci-sdk": "./build/ts/index.js" + }, + "files": [ + "build", + "CHANGELOG.md", + "README.md" + ], + "scripts": { + "watch": "tsc --watch", + "build": "tsc -p tsconfig.build.json", + "postbuild": "cp package.json ./build && mkdir -p ./zkeys", + "types": "tsc -p tsconfig.json --noEmit", + "test": "nyc ts-mocha --exit tests/unit/*.test.ts", + "docs": "typedoc --plugin typedoc-plugin-markdown --options ./typedoc.json" + }, + "dependencies": { + "@commander-js/extra-typings": "^12.1.0", + "@nomicfoundation/hardhat-toolbox": "^5.0.0", + "commander": "^12.1.0", + "dotenv": "^16.4.5", + "ethers": "^6.13.4", + "hardhat": "^2.22.8", + "maci-contracts": "^2.5.0", + "maci-crypto": "^2.5.0" + }, + "devDependencies": { + "@types/chai": "^4.3.9", + "@types/chai-as-promised": "^7.1.8", + "@types/mocha": "^10.0.8", + "@types/node": "^22.9.0", + "@types/snarkjs": "^0.7.8", + "chai": "^4.3.10", + "chai-as-promised": "^7.1.2", + "mocha": "^10.7.3", + "nyc": "^17.1.0", + "snarkjs": "^0.7.5", + "ts-mocha": "^10.0.0", + "typescript": "^5.6.3" + }, + "nyc": { + "reporter": [ + "text", + "lcov" + ], + "extensions": [ + ".ts" + ], + "all": true, + "exclude": [ + "**/*.js", + "**/*.d.ts", + "hardhat.config.ts", + "tests/**/*.ts", + "ts/index.ts" + ], + "branches": ">50%", + "lines": ">50%", + "functions": ">50%", + "statements": ">50%" + } +} diff --git a/packages/sdk/ts/index.ts b/packages/sdk/ts/index.ts new file mode 100644 index 0000000000..d1f342a2ed --- /dev/null +++ b/packages/sdk/ts/index.ts @@ -0,0 +1,16 @@ +export { getPoll, getPollParams } from "./poll"; +export { verify } from "./verify"; +export { generateTallyCommitments } from "./tallyCommitments"; + +export { + linkPoseidonLibraries, + Deployment, + ContractStorage, + EContracts, + EMode, + type IVerifyingKeyStruct, +} from "maci-contracts"; + +export * from "maci-contracts/typechain-types"; + +export type { TallyData, VerifyArgs, IGetPollArgs, IGetPollData } from "./utils"; diff --git a/packages/sdk/ts/poll.ts b/packages/sdk/ts/poll.ts new file mode 100644 index 0000000000..5c4d8ed1bd --- /dev/null +++ b/packages/sdk/ts/poll.ts @@ -0,0 +1,89 @@ +import { ZeroAddress } from "ethers"; +import { + MACI__factory as MACIFactory, + Poll__factory as PollFactory, + Tally__factory as TallyFactory, +} from "maci-contracts/typechain-types"; + +import type { IGetPollArgs, IGetPollData, IGetPollParamsArgs, IPollParams } from "./utils/interfaces"; + +/** + * Get deployed poll from MACI contract + * @param {IGetPollArgs} args - The arguments for the get poll command + * @returns {IGetPollData} poll data + */ +export const getPoll = async ({ maciAddress, signer, provider, pollId }: IGetPollArgs): Promise => { + if (!signer && !provider) { + throw new Error("No signer and provider are provided"); + } + + const maciContract = MACIFactory.connect(maciAddress, signer ?? provider); + const id = + pollId === undefined ? await maciContract.nextPollId().then((nextPollId) => nextPollId - 1n) : BigInt(pollId); + + if (id < 0n) { + throw new Error(`Invalid poll id ${id}`); + } + + const pollContracts = await maciContract.polls(id); + + if (pollContracts.poll === ZeroAddress) { + throw new Error(`MACI contract doesn't have any deployed poll ${id}`); + } + + const pollContract = PollFactory.connect(pollContracts.poll, signer ?? provider); + + const [[deployTime, duration], mergedStateRoot] = await Promise.all([ + pollContract.getDeployTimeAndDuration(), + pollContract.mergedStateRoot(), + ]); + const isMerged = mergedStateRoot !== BigInt(0); + const numSignups = await (isMerged ? pollContract.numSignups() : maciContract.numSignUps()); + + // get the poll mode + const tallyContract = TallyFactory.connect(pollContracts.tally, signer ?? provider); + const mode = await tallyContract.mode(); + + return { + id, + address: pollContracts.poll, + deployTime, + duration, + numSignups, + isMerged, + mode, + }; +}; + +/** + * Get the parameters for the poll + * @param {IGetPollParamsArgs} args - The arguments for the get poll command + * @returns {IPollParams} poll parameters + */ +export const getPollParams = async ({ + pollId, + signer, + maciContractAddress, +}: IGetPollParamsArgs): Promise => { + // get the contract objects + const maciContract = MACIFactory.connect(maciContractAddress, signer); + const pollContracts = await maciContract.polls(pollId); + const pollContract = PollFactory.connect(pollContracts.poll, signer); + + const treeDepths = await pollContract.treeDepths(); + const voteOptionTreeDepth = Number(treeDepths.voteOptionTreeDepth); + const numVoteOptions = 5 ** voteOptionTreeDepth; + + const messageBatchSize = Number.parseInt((await pollContract.messageBatchSize()).toString(), 10); + + const intStateTreeDepth = Number(treeDepths.intStateTreeDepth); + const tallyBatchSize = 5 ** intStateTreeDepth; + + return { + messageBatchSize, + numVoteOptions, + tallyBatchSize, + voteOptionTreeDepth, + intStateTreeDepth, + }; +}; diff --git a/packages/sdk/ts/tallyCommitments.ts b/packages/sdk/ts/tallyCommitments.ts new file mode 100644 index 0000000000..35bb18180d --- /dev/null +++ b/packages/sdk/ts/tallyCommitments.ts @@ -0,0 +1,55 @@ +import { genTreeCommitment, hash2, hash3, hashLeftRight } from "maci-crypto"; + +import { IGenerateTallyCommitmentsArgs, ITallyCommitments } from "./utils/interfaces"; + +/** + * Generate the tally commitments for this current batch of proving + * @param tallyData - The tally data + * @param voteOptionTreeDepth - The vote option tree depth + * @returns The commitments to the Tally data + */ +export const generateTallyCommitments = ({ + tallyData, + voteOptionTreeDepth, +}: IGenerateTallyCommitmentsArgs): ITallyCommitments => { + // compute newResultsCommitment + const newResultsCommitment = genTreeCommitment( + tallyData.results.tally.map((x) => BigInt(x)), + BigInt(tallyData.results.salt), + voteOptionTreeDepth, + ); + + // compute newSpentVoiceCreditsCommitment + const newSpentVoiceCreditsCommitment = hash2([ + BigInt(tallyData.totalSpentVoiceCredits.spent), + BigInt(tallyData.totalSpentVoiceCredits.salt), + ]); + + let newTallyCommitment: bigint; + let newPerVOSpentVoiceCreditsCommitment: bigint | undefined; + + if (tallyData.isQuadratic) { + // compute newPerVOSpentVoiceCreditsCommitment + newPerVOSpentVoiceCreditsCommitment = genTreeCommitment( + tallyData.perVOSpentVoiceCredits!.tally.map((x) => BigInt(x)), + BigInt(tallyData.perVOSpentVoiceCredits!.salt), + voteOptionTreeDepth, + ); + + // compute newTallyCommitment + newTallyCommitment = hash3([ + newResultsCommitment, + newSpentVoiceCreditsCommitment, + newPerVOSpentVoiceCreditsCommitment, + ]); + } else { + newTallyCommitment = hashLeftRight(newResultsCommitment, newSpentVoiceCreditsCommitment); + } + + return { + newTallyCommitment, + newSpentVoiceCreditsCommitment, + newPerVOSpentVoiceCreditsCommitment, + newResultsCommitment, + }; +}; diff --git a/packages/sdk/ts/utils/contracts.ts b/packages/sdk/ts/utils/contracts.ts new file mode 100644 index 0000000000..72ea0d6f6a --- /dev/null +++ b/packages/sdk/ts/utils/contracts.ts @@ -0,0 +1,24 @@ +import type { Provider } from "ethers"; + +/** + * Small utility function to check whether a contract exists at a given address + * @param provider - the provider to use to interact with the chain + * @param address - the address of the contract to check + * @returns a boolean indicating whether the contract exists + */ +export const contractExists = async (provider: Provider, address: string): Promise => { + const code = await provider.getCode(address); + return code.length > 2; +}; + +/** + * Small utility to retrieve the current block timestamp from the blockchain + * @param provider the provider to use to interact with the chain + * @returns the current block timestamp + */ +export const currentBlockTimestamp = async (provider: Provider): Promise => { + const blockNum = await provider.getBlockNumber(); + const block = await provider.getBlock(blockNum); + + return Number(block?.timestamp); +}; diff --git a/packages/sdk/ts/utils/index.ts b/packages/sdk/ts/utils/index.ts new file mode 100644 index 0000000000..fb750cd782 --- /dev/null +++ b/packages/sdk/ts/utils/index.ts @@ -0,0 +1,12 @@ +export { contractExists, currentBlockTimestamp } from "./contracts"; +export type { + TallyData, + VerifyArgs, + IGetPollArgs, + IGetPollData, + IGenerateTallyCommitmentsArgs, + IGetPollParamsArgs, + ITallyCommitments, + IPollParams, +} from "./interfaces"; +export { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "./verifiers"; diff --git a/packages/sdk/ts/utils/interfaces.ts b/packages/sdk/ts/utils/interfaces.ts new file mode 100644 index 0000000000..be043ea593 --- /dev/null +++ b/packages/sdk/ts/utils/interfaces.ts @@ -0,0 +1,291 @@ +import type { Provider, Signer } from "ethers"; + +/** + * Interface for the tally file data. + */ +export interface TallyData { + /** + * The MACI address. + */ + maci: string; + + /** + * The ID of the poll. + */ + pollId: string; + + /** + * The name of the network for which these proofs + * are valid for + */ + network?: string; + + /** + * The chain ID for which these proofs are valid for + */ + chainId?: string; + + /** + * Whether the poll is using quadratic voting or not. + */ + isQuadratic: boolean; + + /** + * The address of the Tally contract. + */ + tallyAddress: string; + + /** + * The new tally commitment. + */ + newTallyCommitment: string; + + /** + * The results of the poll. + */ + results: { + /** + * The tally of the results. + */ + tally: string[]; + + /** + * The salt of the results. + */ + salt: string; + + /** + * The commitment of the results. + */ + commitment: string; + }; + + /** + * The total spent voice credits. + */ + totalSpentVoiceCredits: { + /** + * The spent voice credits. + */ + spent: string; + + /** + * The salt of the spent voice credits. + */ + salt: string; + + /** + * The commitment of the spent voice credits. + */ + commitment: string; + }; + + /** + * The per VO spent voice credits. + */ + perVOSpentVoiceCredits?: { + /** + * The tally of the per VO spent voice credits. + */ + tally: string[]; + + /** + * The salt of the per VO spent voice credits. + */ + salt: string; + + /** + * The commitment of the per VO spent voice credits. + */ + commitment: string; + }; +} + +export type BigNumberish = number | string | bigint; +/** + * Interface for the arguments to the get poll command + */ +export interface IGetPollArgs { + /** + * A signer object + */ + signer?: Signer; + + /** + * A provider fallback object + */ + provider?: Provider; + + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * The poll id. If not specified, latest poll id will be used + */ + pollId?: BigNumberish; +} + +/** + * Interface for the return data to the get poll command + */ +export interface IGetPollData { + /** + * The poll id + */ + id: BigNumberish; + + /** + * The poll address + */ + address: string; + + /** + * The poll deployment time + */ + deployTime: BigNumberish; + + /** + * The poll duration + */ + duration: BigNumberish; + + /** + * The poll number of signups + */ + numSignups: BigNumberish; + + /** + * Whether the MACI contract's state root has been merged + */ + isMerged: boolean; + + /** + * Mode of the poll + */ + mode: BigNumberish; +} + +/** + * Interface for the arguments to the verifyProof command + */ +export interface VerifyArgs { + /** + * The id of the poll + */ + pollId: bigint; + + /** + * A signer object + */ + signer: Signer; + + /** + * The tally data + */ + tallyData: TallyData; + + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * The tally commitments + */ + tallyCommitments: ITallyCommitments; + + /** + * The number of vote options + */ + numVoteOptions: number; + + /** + * The vote option tree depth + */ + voteOptionTreeDepth: number; +} + +/** + * Arguments for the get poll params command + */ +export interface IGetPollParamsArgs { + /** + * The poll id + */ + pollId: bigint; + /** + * The signer + */ + signer: Signer; + /** + * The MACI contract address + */ + maciContractAddress: string; +} + +/** + * Poll parameters + */ +export interface IPollParams { + /** + * The message batch size + */ + messageBatchSize: number; + /** + * The number of vote options + */ + numVoteOptions: number; + + /** + * Tally Batch Size + */ + tallyBatchSize: number; + + /** + * The vote option tree depth + */ + voteOptionTreeDepth: number; + + /** + * The depth of the tree holding the user ballots + */ + intStateTreeDepth: number; +} + +/** + * Arguments for the generateTallyCommitments function + */ +export interface IGenerateTallyCommitmentsArgs { + /** + * The tally data + */ + tallyData: TallyData; + /** + * The vote option tree depth + */ + voteOptionTreeDepth: number; +} + +/** + * Interface for the tally commitments + */ +export interface ITallyCommitments { + /** + * The new tally commitment + */ + newTallyCommitment: bigint; + /** + * The new spent voice credits commitment + */ + newSpentVoiceCreditsCommitment: bigint; + /** + * The new per vote option spent voice credits commitment + */ + newPerVOSpentVoiceCreditsCommitment?: bigint; + /** + * The commitment to the results tree root + */ + newResultsCommitment: bigint; +} diff --git a/packages/sdk/ts/utils/trees.ts b/packages/sdk/ts/utils/trees.ts new file mode 100644 index 0000000000..87dd7186fd --- /dev/null +++ b/packages/sdk/ts/utils/trees.ts @@ -0,0 +1,29 @@ +/** + * Utility to calculate the depth of a binary tree + * @param maxLeaves - the number of leaves in the tree + * @returns the depth of the tree + */ +export const calcBinaryTreeDepthFromMaxLeaves = (maxLeaves: number): number => { + let result = 0; + + while (2 ** result < maxLeaves) { + result += 1; + } + + return result; +}; + +/** + * Utility to calculate the depth of a quin tree + * @param maxLeaves the number of leaves in the tree + * @returns the depth of the tree + */ +export const calcQuinTreeDepthFromMaxLeaves = (maxLeaves: number): number => { + let result = 0; + + while (5 ** result < maxLeaves) { + result += 1; + } + + return result; +}; diff --git a/packages/sdk/ts/utils/verifiers.ts b/packages/sdk/ts/utils/verifiers.ts new file mode 100644 index 0000000000..ef47542f73 --- /dev/null +++ b/packages/sdk/ts/utils/verifiers.ts @@ -0,0 +1,92 @@ +import { genTreeProof } from "maci-crypto"; + +import type { TallyData } from "./interfaces"; +import type { Tally } from "maci-contracts"; + +/** + * Loop through each per vote option spent voice credits and verify it on-chain + * + * @param tallyContract The tally contract + * @param tallyData The tally.json file data + * @param voteOptionTreeDepth The vote option tree depth + * @param newSpentVoiceCreditsCommitment The total spent voice credits commitment + * @param newResultsCommitment The tally result commitment + * @returns list of the indexes of the tally result that failed on-chain verification + */ +export const verifyPerVOSpentVoiceCredits = async ( + tallyContract: Tally, + tallyData: TallyData, + voteOptionTreeDepth: number, + newSpentVoiceCreditsCommitment: bigint, + newResultsCommitment: bigint, +): Promise => { + const failedIndices: number[] = []; + + for (let i = 0; i < tallyData.perVOSpentVoiceCredits!.tally.length; i += 1) { + const proof = genTreeProof( + i, + tallyData.perVOSpentVoiceCredits!.tally.map((x) => BigInt(x)), + voteOptionTreeDepth, + ); + + // eslint-disable-next-line no-await-in-loop + const isValid = await tallyContract.verifyPerVOSpentVoiceCredits( + i, + tallyData.perVOSpentVoiceCredits!.tally[i], + proof, + tallyData.perVOSpentVoiceCredits!.salt, + voteOptionTreeDepth, + newSpentVoiceCreditsCommitment, + newResultsCommitment, + ); + if (!isValid) { + failedIndices.push(i); + } + } + + return failedIndices; +}; + +/** + * Loop through each tally result and verify it on-chain + * @param tallyContract The tally contract + * @param tallyData The tally.json file data + * @param voteOptionTreeDepth The vote option tree depth + * @param newSpentVoiceCreditsCommitment The total spent voice credits commitment + * @param newPerVOSpentVoiceCreditsCommitment The per vote option voice credits commitment + * @returns list of the indexes of the tally result that failed on-chain verification + */ +export const verifyTallyResults = async ( + tallyContract: Tally, + tallyData: TallyData, + voteOptionTreeDepth: number, + newSpentVoiceCreditsCommitment: bigint, + newPerVOSpentVoiceCreditsCommitment?: bigint, +): Promise => { + const failedIndices: number[] = []; + + for (let i = 0; i < tallyData.results.tally.length; i += 1) { + const proof = genTreeProof( + i, + tallyData.results.tally.map((x) => BigInt(x)), + voteOptionTreeDepth, + ); + + // eslint-disable-next-line no-await-in-loop + const isValid = await tallyContract.verifyTallyResult( + i, + tallyData.results.tally[i], + proof, + tallyData.results.salt, + voteOptionTreeDepth, + newSpentVoiceCreditsCommitment, + newPerVOSpentVoiceCreditsCommitment ?? 0n, + ); + + if (!isValid) { + failedIndices.push(i); + } + } + + return failedIndices; +}; diff --git a/packages/sdk/ts/verify.ts b/packages/sdk/ts/verify.ts new file mode 100644 index 0000000000..b15422281c --- /dev/null +++ b/packages/sdk/ts/verify.ts @@ -0,0 +1,111 @@ +import { Tally__factory as TallyFactory, MACI__factory as MACIFactory } from "maci-contracts/typechain-types"; + +import type { VerifyArgs } from "./utils/interfaces"; + +import { contractExists } from "./utils/contracts"; +import { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "./utils/verifiers"; + +/** + * Verify the results of a poll on-chain + * @param VerifyArgs - The arguments for the verify command + */ +export const verify = async ({ + pollId, + tallyData, + signer, + tallyCommitments, + numVoteOptions, + voteOptionTreeDepth, +}: VerifyArgs): Promise => { + const useQv = tallyData.isQuadratic; + const maciContractAddress = tallyData.maci; + + if (!(await contractExists(signer.provider!, maciContractAddress))) { + throw new Error(`There is no MACI contract deployed at ${maciContractAddress}.`); + } + + // get the contract objects + const maciContract = MACIFactory.connect(maciContractAddress, signer); + const pollContracts = await maciContract.polls(pollId); + const tallyContract = TallyFactory.connect(pollContracts.tally, signer); + + // get the on-chain tally commitment\ + const onChainTallyCommitment = BigInt(await tallyContract.tallyCommitment()); + + // check the results commitment + const validResultsCommitment = tallyData.newTallyCommitment.match(/0x[a-fA-F0-9]+/); + + if (!validResultsCommitment) { + throw new Error("Invalid results commitment format"); + } + + if (tallyData.results.tally.length !== numVoteOptions) { + throw new Error("Wrong number of vote options."); + } + + // destructure the tally commitments + const { + newTallyCommitment, + newSpentVoiceCreditsCommitment, + newPerVOSpentVoiceCreditsCommitment, + newResultsCommitment, + } = tallyCommitments; + + // verify that the results commitment matches the output of genTreeCommitment() + if (onChainTallyCommitment !== newTallyCommitment) { + throw new Error("The on-chain tally commitment does not match."); + } + + // verify total spent voice credits on-chain + if ( + !(await tallyContract.verifySpentVoiceCredits( + tallyData.totalSpentVoiceCredits.spent, + tallyData.totalSpentVoiceCredits.salt, + newResultsCommitment, + newPerVOSpentVoiceCreditsCommitment ?? 0n, + )) + ) { + throw new Error("The on-chain verification of total spent voice credits failed."); + } + + // verify tally result on-chain for each vote option + const failedPerVOSpentCredits = await verifyTallyResults( + tallyContract, + tallyData, + voteOptionTreeDepth, + newSpentVoiceCreditsCommitment, + newPerVOSpentVoiceCreditsCommitment, + ); + + if (failedPerVOSpentCredits.length > 0) { + throw new Error( + `At least one spent voice credits entry in the tally results failed the on-chain verification. Please check your tally results at these indexes: ${failedPerVOSpentCredits.join( + ", ", + )}`, + ); + } + + if (useQv) { + if (tallyData.perVOSpentVoiceCredits?.tally.length !== numVoteOptions) { + throw new Error("Wrong number of vote options."); + } + // verify per vote option voice credits on-chain + const failedSpentCredits = await verifyPerVOSpentVoiceCredits( + tallyContract, + tallyData, + voteOptionTreeDepth, + newSpentVoiceCreditsCommitment, + newResultsCommitment, + ); + + if (failedSpentCredits.length > 0) { + throw new Error( + `At least one tally result failed the on-chain verification. Please check your Tally data at these indexes: ${failedSpentCredits.join( + ", ", + )}`, + ); + } + } + + return true; +}; diff --git a/packages/sdk/tsconfig.build.json b/packages/sdk/tsconfig.build.json new file mode 100644 index 0000000000..aba0b4ab7b --- /dev/null +++ b/packages/sdk/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["./ts"], + "files": [] +} diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json new file mode 100644 index 0000000000..85e5791098 --- /dev/null +++ b/packages/sdk/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["./ts", "./tests"], + "files": [] +} diff --git a/packages/sdk/typedoc.json b/packages/sdk/typedoc.json new file mode 100644 index 0000000000..ce67283bfd --- /dev/null +++ b/packages/sdk/typedoc.json @@ -0,0 +1,5 @@ +{ + "extends": ["../../typedoc.base.json"], + "entryPoints": ["ts/index.ts"], + "out": "../../apps/website/typedoc/sdk" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f279b93db..13b865b682 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -429,6 +429,9 @@ importers: maci-domainobjs: specifier: ^2.5.0 version: link:../domainobjs + maci-sdk: + specifier: ^0.0.1 + version: link:../sdk prompt: specifier: ^1.3.0 version: 1.3.0 @@ -754,6 +757,70 @@ importers: specifier: ^5.7.3 version: 5.7.3 + packages/sdk: + dependencies: + '@commander-js/extra-typings': + specifier: ^12.1.0 + version: 12.1.0(commander@12.1.0) + '@nomicfoundation/hardhat-toolbox': + specifier: ^5.0.0 + version: 5.0.0(xxl2qhkxzbwobweo6ovit3fcve) + commander: + specifier: ^12.1.0 + version: 12.1.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + ethers: + specifier: ^6.13.4 + version: 6.13.5 + hardhat: + specifier: ^2.22.8 + version: 2.22.18(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))(typescript@5.7.3) + maci-contracts: + specifier: ^2.5.0 + version: link:../contracts + maci-crypto: + specifier: ^2.5.0 + version: link:../crypto + devDependencies: + '@types/chai': + specifier: ^4.3.9 + version: 4.3.16 + '@types/chai-as-promised': + specifier: ^7.1.8 + version: 7.1.8 + '@types/mocha': + specifier: ^10.0.8 + version: 10.0.10 + '@types/node': + specifier: ^22.9.0 + version: 22.10.7 + '@types/snarkjs': + specifier: ^0.7.8 + version: 0.7.9 + chai: + specifier: ^4.3.10 + version: 4.4.1 + chai-as-promised: + specifier: ^7.1.2 + version: 7.1.2(chai@4.4.1) + mocha: + specifier: ^10.7.3 + version: 10.8.2 + nyc: + specifier: ^17.1.0 + version: 17.1.0 + snarkjs: + specifier: ^0.7.5 + version: 0.7.5 + ts-mocha: + specifier: ^10.0.0 + version: 10.0.0(mocha@10.8.2) + typescript: + specifier: ^5.6.3 + version: 5.7.3 + packages: '@achingbrain/nat-port-mapper@4.0.1': @@ -2095,10 +2162,6 @@ packages: resolution: {integrity: sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.24.7': - resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.26.0': resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} @@ -2152,6 +2215,11 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@commander-js/extra-typings@12.1.0': + resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==} + peerDependencies: + commander: ~12.1.0 + '@commander-js/extra-typings@13.0.0': resolution: {integrity: sha512-4or44L3saI49QRBvdSzfCtzqONlFg0/qy0Cfl+LRynDaxlGs7r2KRTLCELaAoKh4oQguICxRwQfm77/Up+0wTw==} peerDependencies: @@ -5748,10 +5816,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chalk@5.4.1: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -16396,10 +16460,6 @@ snapshots: core-js-pure: 3.37.1 regenerator-runtime: 0.14.1 - '@babel/runtime@7.24.7': - dependencies: - regenerator-runtime: 0.14.1 - '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 @@ -16498,6 +16558,10 @@ snapshots: '@colors/colors@1.5.0': {} + '@commander-js/extra-typings@12.1.0(commander@12.1.0)': + dependencies: + commander: 12.1.0 + '@commander-js/extra-typings@13.0.0(commander@13.0.0)': dependencies: commander: 13.0.0 @@ -16646,7 +16710,7 @@ snapshots: '@commitlint/types@19.5.0': dependencies: '@types/conventional-commits-parser': 5.0.0 - chalk: 5.3.0 + chalk: 5.4.1 '@cspotcode/source-map-support@0.8.1': dependencies: @@ -22437,8 +22501,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.3.0: {} - chalk@5.4.1: {} char-regex@1.0.2: {} @@ -26808,7 +26870,7 @@ snapshots: jest-diff@29.7.0: dependencies: - chalk: 4.1.0 + chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 @@ -29027,7 +29089,7 @@ snapshots: '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 axios: 1.7.2 - chalk: 4.1.0 + chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 cliui: 8.0.1 @@ -30334,7 +30396,7 @@ snapshots: react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.97.1): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)' webpack: 5.97.1 @@ -30407,13 +30469,13 @@ snapshots: react-router-config@5.1.1(react-router@5.3.4(react@19.0.0))(react@19.0.0): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 react: 19.0.0 react-router: 5.3.4(react@19.0.0) react-router-dom@5.3.4(react@19.0.0): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -30424,7 +30486,7 @@ snapshots: react-router@5.3.4(react@19.0.0): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 history: 4.10.1 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 @@ -32082,6 +32144,13 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.24.7) + ts-mocha@10.0.0(mocha@10.8.2): + dependencies: + mocha: 10.8.2 + ts-node: 7.0.1 + optionalDependencies: + tsconfig-paths: 3.15.0 + ts-mocha@10.0.0(mocha@11.0.1): dependencies: mocha: 11.0.1 @@ -32466,7 +32535,7 @@ snapshots: update-notifier@6.0.2: dependencies: boxen: 7.1.1 - chalk: 5.3.0 + chalk: 5.4.1 configstore: 6.0.0 has-yarn: 3.0.0 import-lazy: 4.0.0 From 319487d8cb74f54f605e65724dcaee4600c67c81 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:53:52 +0000 Subject: [PATCH 2/2] refactor: user functions --- packages/cli/ts/index.ts | 51 +++++-- packages/sdk/package.json | 3 +- packages/sdk/ts/index.ts | 3 +- packages/sdk/ts/user.ts | 121 +++++++++++++++ packages/sdk/ts/utils/constants.ts | 1 + packages/sdk/ts/utils/index.ts | 10 ++ packages/sdk/ts/utils/interfaces.ts | 180 +++++++++++++++++++++++ packages/sdk/ts/utils/user.ts | 74 ++++++++++ pnpm-lock.yaml | 218 +++------------------------- 9 files changed, 454 insertions(+), 207 deletions(-) create mode 100644 packages/sdk/ts/user.ts create mode 100644 packages/sdk/ts/utils/constants.ts create mode 100644 packages/sdk/ts/utils/user.ts diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index ca0a6a3054..5e881e4593 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node import { Command } from "@commander-js/extra-typings"; -import { generateTallyCommitments, getPollParams, verify, getPoll } from "maci-sdk"; +import { generateTallyCommitments, getPollParams, verify, getPoll, isUserRegistered, signup } from "maci-sdk"; import fs from "fs"; import path from "path"; @@ -20,8 +20,6 @@ import { setVerifyingKeys, mergeSignups, timeTravel, - signup, - isRegisteredUser, genProofs, fundWallet, proveOnChain, @@ -31,7 +29,17 @@ import { joinPoll, isJoinedUser, } from "./commands"; -import { TallyData, banner, logError, logGreen, promptSensitiveValue, readContractAddress, success } from "./utils"; +import { + DEFAULT_SG_DATA, + TallyData, + banner, + logError, + logGreen, + logRed, + promptSensitiveValue, + readContractAddress, + success, +} from "./utils"; // set the description version and name of the cli tool const { description, version, name } = JSON.parse( @@ -465,13 +473,17 @@ program const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); - await signup({ + const data = await signup({ maciPubKey: cmdObj.pubkey, maciAddress, - sgDataArg: cmdObj.sgData, - quiet: cmdObj.quiet, + sgData: cmdObj.sgData ?? DEFAULT_SG_DATA, signer, }); + + logGreen( + cmdObj.quiet, + success(`State index: ${data.stateIndex.toString()}\n Transaction hash: ${data.transactionHash}`), + ); } catch (error) { program.error((error as Error).message, { exitCode: 1 }); } @@ -489,12 +501,17 @@ program const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); - await isRegisteredUser({ + const data = await isUserRegistered({ maciPubKey: cmdObj.pubkey, maciAddress, signer, - quiet: cmdObj.quiet, }); + + if (data.isRegistered) { + logGreen(cmdObj.quiet, success(`State index: ${data.stateIndex?.toString()}`)); + } else { + logRed(cmdObj.quiet, "User is not registered"); + } } catch (error) { program.error((error as Error).message, { exitCode: 1 }); } @@ -516,7 +533,7 @@ program const maciAddress = cmdObj.maciAddress || (await readContractAddress("MACI", network?.name)); - await isJoinedUser({ + const data = await isJoinedUser({ pollPubKey: cmdObj.pubkey, startBlock: cmdObj.startBlock!, maciAddress, @@ -524,6 +541,20 @@ program signer, quiet: cmdObj.quiet, }); + + if (data.isJoined) { + logGreen( + cmdObj.quiet, + success( + [ + `Poll state index: ${data.pollStateIndex?.toString()}, registered: ${data.isJoined}`, + `Voice credits: ${data.voiceCredits?.toString()}`, + ].join("\n"), + ), + ); + } else { + logRed(cmdObj.quiet, "User has not joined the poll"); + } } catch (error) { program.error((error as Error).message, { exitCode: 1 }); } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 0b8e6e6641..8060d5d4b6 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -37,7 +37,8 @@ "ethers": "^6.13.4", "hardhat": "^2.22.8", "maci-contracts": "^2.5.0", - "maci-crypto": "^2.5.0" + "maci-crypto": "^2.5.0", + "maci-domainobjs": "^2.5.0" }, "devDependencies": { "@types/chai": "^4.3.9", diff --git a/packages/sdk/ts/index.ts b/packages/sdk/ts/index.ts index d1f342a2ed..41e63320b9 100644 --- a/packages/sdk/ts/index.ts +++ b/packages/sdk/ts/index.ts @@ -1,6 +1,7 @@ export { getPoll, getPollParams } from "./poll"; export { verify } from "./verify"; export { generateTallyCommitments } from "./tallyCommitments"; +export { isUserRegistered, isJoinedUser, signup } from "./user"; export { linkPoseidonLibraries, @@ -13,4 +14,4 @@ export { export * from "maci-contracts/typechain-types"; -export type { TallyData, VerifyArgs, IGetPollArgs, IGetPollData } from "./utils"; +export type { TallyData, VerifyArgs, IGetPollArgs, IGetPollData, IIsRegisteredUser, IIsJoinedUser } from "./utils"; diff --git a/packages/sdk/ts/user.ts b/packages/sdk/ts/user.ts new file mode 100644 index 0000000000..df4acfa9d5 --- /dev/null +++ b/packages/sdk/ts/user.ts @@ -0,0 +1,121 @@ +import { ContractTransactionReceipt, isBytesLike } from "ethers"; +import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts/typechain-types"; +import { PubKey } from "maci-domainobjs"; + +import { contractExists, IRegisteredUserArgs, ISignupArgs, parsePollJoinEvents, parseSignupEvents } from "./utils"; +import { IIsRegisteredUser, IJoinedUserArgs, ISignupData } from "./utils/interfaces"; + +/** + * Checks if user is registered with a given public key + * @param IRegisteredArgs - The arguments for the check register command + * @returns whether the user is registered or not and their state index + */ +export const isUserRegistered = async ({ + maciAddress, + maciPubKey, + signer, + startBlock, +}: IRegisteredUserArgs): Promise => { + const maciContract = MACIFactory.connect(maciAddress, signer); + const publicKey = PubKey.deserialize(maciPubKey); + const startBlockNumber = startBlock || 0; + const currentBlock = await signer.provider!.getBlockNumber(); + + const { stateIndex } = await parseSignupEvents({ + maciContract, + startBlock: startBlockNumber, + currentBlock, + publicKey, + }); + + return { + isRegistered: stateIndex !== undefined, + stateIndex, + }; +}; + +/** + * Signup a user to the MACI contract + * @param {SignupArgs} args - The arguments for the signup command + * @returns {ISignupData} The state index of the user and transaction hash + */ +export const signup = async ({ maciPubKey, maciAddress, sgData, signer }: ISignupArgs): Promise => { + // validate user key + if (!PubKey.isValidSerializedPubKey(maciPubKey)) { + throw new Error("Invalid MACI public key"); + } + + const userMaciPubKey = PubKey.deserialize(maciPubKey); + + if (!(await contractExists(signer.provider!, maciAddress))) { + throw new Error("There is no contract deployed at the specified address"); + } + + // we validate that the signup data and voice credit data is valid + if (!isBytesLike(sgData)) { + throw new Error("invalid signup gateway data"); + } + + const maciContract = MACIFactory.connect(maciAddress, signer); + + let stateIndex = ""; + let receipt: ContractTransactionReceipt | null = null; + + try { + // sign up to the MACI contract + const tx = await maciContract.signUp(userMaciPubKey.asContractParam(), sgData); + receipt = await tx.wait(); + + if (receipt?.status !== 1) { + throw new Error("The transaction failed"); + } + + const iface = maciContract.interface; + + // get state index from the event + const [log] = receipt.logs; + const { args } = iface.parseLog(log as unknown as { topics: string[]; data: string }) || { args: [] }; + [stateIndex, ,] = args; + } catch (error) { + throw new Error((error as Error).message); + } + + return { + stateIndex: stateIndex ? stateIndex.toString() : "", + transactionHash: receipt.hash, + }; +}; + +/** + * Checks if user is joined to a poll with the public key + * @param {IJoinedUserArgs} - The arguments for the join check command + * @returns user joined or not and poll state index, voice credit balance + */ +export const isJoinedUser = async ({ + maciAddress, + pollId, + pollPubKey, + signer, + startBlock, +}: IJoinedUserArgs): Promise<{ isJoined: boolean; pollStateIndex?: string; voiceCredits?: string }> => { + const maciContract = MACIFactory.connect(maciAddress, signer); + const pollContracts = await maciContract.getPoll(pollId); + const pollContract = PollFactory.connect(pollContracts.poll, signer); + + const pollPublicKey = PubKey.deserialize(pollPubKey); + const startBlockNumber = startBlock || 0; + const currentBlock = await signer.provider!.getBlockNumber(); + + const { pollStateIndex, voiceCredits } = await parsePollJoinEvents({ + pollContract, + startBlock: startBlockNumber, + currentBlock, + pollPublicKey, + }); + + return { + isJoined: pollStateIndex !== undefined, + pollStateIndex, + voiceCredits, + }; +}; diff --git a/packages/sdk/ts/utils/constants.ts b/packages/sdk/ts/utils/constants.ts new file mode 100644 index 0000000000..636ed550f8 --- /dev/null +++ b/packages/sdk/ts/utils/constants.ts @@ -0,0 +1 @@ +export const BLOCKS_STEP = 1000; diff --git a/packages/sdk/ts/utils/index.ts b/packages/sdk/ts/utils/index.ts index fb750cd782..af4b046562 100644 --- a/packages/sdk/ts/utils/index.ts +++ b/packages/sdk/ts/utils/index.ts @@ -8,5 +8,15 @@ export type { IGetPollParamsArgs, ITallyCommitments, IPollParams, + IRegisteredUserArgs, + IParseSignupEventsArgs, + ISignupData, + ISignupArgs, + IJoinedUserArgs, + IParsePollJoinEventsArgs, + IIsRegisteredUser, + IIsJoinedUser, } from "./interfaces"; export { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "./verifiers"; +export { BLOCKS_STEP } from "./constants"; +export { parsePollJoinEvents, parseSignupEvents } from "./user"; diff --git a/packages/sdk/ts/utils/interfaces.ts b/packages/sdk/ts/utils/interfaces.ts index be043ea593..06bc416762 100644 --- a/packages/sdk/ts/utils/interfaces.ts +++ b/packages/sdk/ts/utils/interfaces.ts @@ -1,3 +1,6 @@ +import { MACI, Poll } from "maci-contracts/typechain-types"; +import { PubKey } from "maci-domainobjs"; + import type { Provider, Signer } from "ethers"; /** @@ -289,3 +292,180 @@ export interface ITallyCommitments { */ newResultsCommitment: bigint; } + +/** + * Interface for the arguments to the register check command + */ +export interface IRegisteredUserArgs { + /** + * A signer object + */ + signer: Signer; + + /** + * The public key of the user + */ + maciPubKey: string; + + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * Start block for event parsing + */ + startBlock?: number; +} + +/** + * Interface for the arguments to the parseSignupEvents function + */ +export interface IParseSignupEventsArgs { + /** + * The MACI contract + */ + maciContract: MACI; + + /** + * The start block + */ + startBlock: number; + + /** + * The current block + */ + currentBlock: number; + + /** + * The public key + */ + publicKey: PubKey; +} + +/** + * Interface for the arguments to the signup command + */ +export interface ISignupArgs { + /** + * The public key of the user + */ + maciPubKey: string; + + /** + * A signer object + */ + signer: Signer; + + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * The signup gatekeeper data + */ + sgData: string; +} + +/** + * Interface for the return data to the signup command + */ +export interface ISignupData { + /** + * The state index of the user + */ + stateIndex: string; + + /** + * The signup transaction hash + */ + transactionHash: string; +} + +/** + * Interface for the arguments to the parsePollJoinEvents function + */ +export interface IParsePollJoinEventsArgs { + /** + * The MACI contract + */ + pollContract: Poll; + + /** + * The start block + */ + startBlock: number; + + /** + * The current block + */ + currentBlock: number; + + /** + * The public key + */ + pollPublicKey: PubKey; +} + +/** + * Interface for the arguments to the isJoinedUser command + */ +export interface IJoinedUserArgs { + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * The id of the poll + */ + pollId: bigint; + + /** + * Poll public key for the poll + */ + pollPubKey: string; + + /** + * A signer object + */ + signer: Signer; + + /** + * The start block number + */ + startBlock: number; +} + +/** + * Interface for the return data to the isRegisteredUser function + */ +export interface IIsRegisteredUser { + /** + * Whether the user is registered + */ + isRegistered: boolean; + /** + * The state index of the user + */ + stateIndex?: string; +} + +/** + * Interface for the return data to the isJoinedUser function + */ +export interface IIsJoinedUser { + /** + * Whether the user joined the poll + */ + isJoined: boolean; + /** + * The state index of the user + */ + pollStateIndex?: string; + /** + * The voice credits of the user + */ + voiceCredits?: string; +} diff --git a/packages/sdk/ts/utils/user.ts b/packages/sdk/ts/utils/user.ts new file mode 100644 index 0000000000..39ee3ecabe --- /dev/null +++ b/packages/sdk/ts/utils/user.ts @@ -0,0 +1,74 @@ +import { BLOCKS_STEP } from "./constants"; +import { IParsePollJoinEventsArgs, IParseSignupEventsArgs } from "./interfaces"; + +export /** + * Parse the poll joining events from the Poll contract + */ +const parsePollJoinEvents = async ({ + pollContract, + startBlock, + currentBlock, + pollPublicKey, +}: IParsePollJoinEventsArgs): Promise<{ + pollStateIndex?: string; + voiceCredits?: string; +}> => { + // 1000 blocks at a time + for (let block = startBlock; block <= currentBlock; block += BLOCKS_STEP) { + const toBlock = Math.min(block + BLOCKS_STEP - 1, currentBlock); + const pubKey = pollPublicKey.asArray(); + // eslint-disable-next-line no-await-in-loop + const newEvents = await pollContract.queryFilter( + pollContract.filters.PollJoined(pubKey[0], pubKey[1], undefined, undefined, undefined, undefined), + block, + toBlock, + ); + + if (newEvents.length > 0) { + const [event] = newEvents; + + return { + pollStateIndex: event.args[5].toString(), + voiceCredits: event.args[2].toString(), + }; + } + } + + return { + pollStateIndex: undefined, + voiceCredits: undefined, + }; +}; + +export /** + * Parse the signup events from the MACI contract + */ +const parseSignupEvents = async ({ + maciContract, + startBlock, + currentBlock, + publicKey, +}: IParseSignupEventsArgs): Promise<{ stateIndex?: string }> => { + // 1000 blocks at a time + for (let block = startBlock; block <= currentBlock; block += 1000) { + const toBlock = Math.min(block + 999, currentBlock); + // eslint-disable-next-line no-await-in-loop + const newEvents = await maciContract.queryFilter( + maciContract.filters.SignUp(undefined, undefined, publicKey.rawPubKey[0], publicKey.rawPubKey[1]), + block, + toBlock, + ); + + if (newEvents.length > 0) { + const [event] = newEvents; + + return { + stateIndex: event.args[0].toString(), + }; + } + } + + return { + stateIndex: undefined, + }; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13b865b682..2252923195 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -261,7 +261,7 @@ importers: dependencies: '@docusaurus/core': specifier: ^3.7.0 - version: 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + version: 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/preset-classic': specifier: ^3.7.0 version: 3.7.0(@algolia/client-search@5.19.0)(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.14.0)(typescript@5.7.3) @@ -783,6 +783,9 @@ importers: maci-crypto: specifier: ^2.5.0 version: link:../crypto + maci-domainobjs: + specifier: ^2.5.0 + version: link:../domainobjs devDependencies: '@types/chai': specifier: ^4.3.9 @@ -17122,72 +17125,6 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': - dependencies: - '@docusaurus/babel': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/bundler': 3.7.0(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) - '@docusaurus/logger': 3.7.0 - '@docusaurus/mdx-loader': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@mdx-js/react': 3.1.0(@types/react@19.0.7)(react@19.0.0) - boxen: 6.2.1 - chalk: 4.1.2 - chokidar: 3.6.0 - cli-table3: 0.6.5 - combine-promises: 1.2.0 - commander: 5.1.0 - core-js: 3.37.1 - del: 6.1.1 - detect-port: 1.6.1 - escape-html: 1.0.3 - eta: 2.2.0 - eval: 0.1.8 - fs-extra: 11.2.0 - html-tags: 3.3.1 - html-webpack-plugin: 5.6.0(webpack@5.97.1) - leven: 3.1.0 - lodash: 4.17.21 - p-map: 4.0.0 - prompts: 2.4.2 - react: 19.0.0 - react-dev-utils: 12.0.1(eslint@8.57.0)(typescript@5.7.3)(webpack@5.97.1) - react-dom: 19.0.0(react@19.0.0) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)' - react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.97.1) - react-router: 5.3.4(react@19.0.0) - react-router-config: 5.1.1(react-router@5.3.4(react@19.0.0))(react@19.0.0) - react-router-dom: 5.3.4(react@19.0.0) - semver: 7.6.3 - serve-handler: 6.1.6 - shelljs: 0.8.5 - tslib: 2.8.1 - update-notifier: 6.0.2 - webpack: 5.97.1 - webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 4.15.2(webpack@5.97.1) - webpack-merge: 6.0.1 - transitivePeerDependencies: - - '@docusaurus/faster' - - '@parcel/css' - - '@rspack/core' - - '@swc/core' - - '@swc/css' - - bufferutil - - csso - - debug - - esbuild - - eslint - - lightningcss - - supports-color - - typescript - - uglify-js - - utf-8-validate - - vue-template-compiler - - webpack-cli - '@docusaurus/cssnano-preset@3.7.0': dependencies: cssnano-preset-advanced: 6.1.2(postcss@8.4.38) @@ -17255,10 +17192,10 @@ snapshots: '@docusaurus/plugin-content-blog@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/logger': 3.7.0 '@docusaurus/mdx-loader': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -17337,50 +17274,9 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': - dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) - '@docusaurus/logger': 3.7.0 - '@docusaurus/mdx-loader': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/module-type-aliases': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@types/react-router-config': 5.0.11 - combine-promises: 1.2.0 - fs-extra: 11.3.0 - js-yaml: 4.1.0 - lodash: 4.17.21 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - tslib: 2.8.1 - utility-types: 3.11.0 - webpack: 5.97.1 - transitivePeerDependencies: - - '@docusaurus/faster' - - '@mdx-js/react' - - '@parcel/css' - - '@rspack/core' - - '@swc/core' - - '@swc/css' - - bufferutil - - csso - - debug - - esbuild - - eslint - - lightningcss - - supports-color - - typescript - - uglify-js - - utf-8-validate - - vue-template-compiler - - webpack-cli - '@docusaurus/plugin-content-pages@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/mdx-loader': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -17412,7 +17308,7 @@ snapshots: '@docusaurus/plugin-debug@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.3.0 @@ -17442,7 +17338,7 @@ snapshots: '@docusaurus/plugin-google-analytics@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 @@ -17470,7 +17366,7 @@ snapshots: '@docusaurus/plugin-google-gtag@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/gtag.js': 0.0.12 @@ -17499,7 +17395,7 @@ snapshots: '@docusaurus/plugin-google-tag-manager@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 @@ -17527,7 +17423,7 @@ snapshots: '@docusaurus/plugin-sitemap@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/logger': 3.7.0 '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -17560,7 +17456,7 @@ snapshots: '@docusaurus/plugin-svgr@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/types': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-validation': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -17592,9 +17488,9 @@ snapshots: '@docusaurus/preset-classic@3.7.0(@algolia/client-search@5.19.0)(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.14.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/plugin-content-pages': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/plugin-debug': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/plugin-google-analytics': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) @@ -17638,12 +17534,12 @@ snapshots: '@docusaurus/theme-classic@3.7.0(@types/react@19.0.7)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/logger': 3.7.0 '@docusaurus/mdx-loader': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/module-type-aliases': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/plugin-content-pages': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/theme-translations': 3.7.0 @@ -17690,7 +17586,7 @@ snapshots: dependencies: '@docusaurus/mdx-loader': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/module-type-aliases': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/utils-common': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/history': 4.7.11 @@ -17713,9 +17609,9 @@ snapshots: '@docusaurus/theme-search-algolia@3.7.0(@algolia/client-search@5.19.0)(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.14.0)(typescript@5.7.3)': dependencies: '@docsearch/react': 3.8.2(@algolia/client-search@5.19.0)(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.14.0) - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/logger': 3.7.0 - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/theme-translations': 3.7.0 '@docusaurus/utils': 3.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -21883,14 +21779,6 @@ snapshots: transitivePeerDependencies: - debug - axios@1.7.2: - dependencies: - follow-redirects: 1.15.9(debug@4.3.4) - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axios@1.7.2(debug@4.4.0): dependencies: follow-redirects: 1.15.9(debug@4.4.0) @@ -21901,7 +21789,7 @@ snapshots: axios@1.7.9: dependencies: - follow-redirects: 1.15.9(debug@4.3.4) + follow-redirects: 1.15.9(debug@4.4.0) form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -23637,7 +23525,7 @@ snapshots: docusaurus-plugin-matomo@0.0.8(@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)): dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@19.0.7)(react@19.0.0))(debug@4.4.0)(eslint@8.57.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) dom-converter@0.2.0: dependencies: @@ -25869,18 +25757,6 @@ snapshots: transitivePeerDependencies: - supports-color - http-proxy-middleware@2.0.6(@types/express@4.17.21): - dependencies: - '@types/http-proxy': 1.17.14 - http-proxy: 1.18.1 - is-glob: 4.0.3 - is-plain-obj: 3.0.0 - micromatch: 4.0.8 - optionalDependencies: - '@types/express': 4.17.21 - transitivePeerDependencies: - - debug - http-proxy-middleware@2.0.6(@types/express@4.17.21)(debug@4.4.0): dependencies: '@types/http-proxy': 1.17.14 @@ -25893,14 +25769,6 @@ snapshots: transitivePeerDependencies: - debug - http-proxy@1.18.1: - dependencies: - eventemitter3: 4.0.7 - follow-redirects: 1.15.9(debug@4.3.4) - requires-port: 1.0.0 - transitivePeerDependencies: - - debug - http-proxy@1.18.1(debug@4.4.0): dependencies: eventemitter3: 4.0.7 @@ -29088,7 +28956,7 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 - axios: 1.7.2 + axios: 1.7.2(debug@4.4.0) chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -32805,46 +32673,6 @@ snapshots: - supports-color - utf-8-validate - webpack-dev-server@4.15.2(webpack@5.97.1): - dependencies: - '@types/bonjour': 3.5.13 - '@types/connect-history-api-fallback': 1.5.4 - '@types/express': 4.17.21 - '@types/serve-index': 1.9.4 - '@types/serve-static': 1.15.7 - '@types/sockjs': 0.3.36 - '@types/ws': 8.5.10 - ansi-html-community: 0.0.8 - bonjour-service: 1.2.1 - chokidar: 3.6.0 - colorette: 2.0.20 - compression: 1.7.4 - connect-history-api-fallback: 2.0.0 - default-gateway: 6.0.3 - express: 4.21.2 - graceful-fs: 4.2.11 - html-entities: 2.5.2 - http-proxy-middleware: 2.0.6(@types/express@4.17.21) - ipaddr.js: 2.2.0 - launch-editor: 2.6.1 - open: 8.4.2 - p-retry: 4.6.2 - rimraf: 3.0.2 - schema-utils: 4.2.0 - selfsigned: 2.4.1 - serve-index: 1.9.1 - sockjs: 0.3.24 - spdy: 4.0.2 - webpack-dev-middleware: 5.3.4(webpack@5.97.1) - ws: 8.18.0 - optionalDependencies: - webpack: 5.97.1 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - webpack-merge@5.10.0: dependencies: clone-deep: 4.0.1