diff --git a/.github/workflows/coordinator-build.yml b/.github/workflows/coordinator-build.yml index a3c6e421..4cedfe1a 100644 --- a/.github/workflows/coordinator-build.yml +++ b/.github/workflows/coordinator-build.yml @@ -113,3 +113,7 @@ jobs: - name: Test run: pnpm run test working-directory: packages/coordinator + + - name: Stop Hardhat + if: always() + run: kill $(lsof -t -i:8545) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 00000000..6e6f7c4b --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,72 @@ +name: Nightly + +on: + schedule: + - cron: 0 0 * * * + +env: + ZERODEV_BUNDLER_RPC: ${{ secrets.ZERODEV_BUNDLER_RPC }} + TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} + RPC_API_KEY: ${{ secrets.RPC_API_KEY }} + PIMLICO_API_KEY: ${{ secrets.PIMLICO_API_KEY }} + +jobs: + e2e-coordinator: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + with: + ref: dev + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install --yes \ + build-essential \ + libgmp-dev \ + libsodium-dev \ + nasm \ + nlohmann-json3-dev + + - name: Install + run: | + pnpm install --frozen-lockfile --prefer-offline + + - name: Build + run: | + pnpm run build + + - name: Download rapidsnark (1c137) + run: | + mkdir -p ~/rapidsnark/build + wget -qO ~/rapidsnark/build/prover https://maci-devops-zkeys.s3.ap-northeast-2.amazonaws.com/rapidsnark-linux-amd64-1c137 + chmod +x ~/rapidsnark/build/prover + + - name: Download circom Binary v2.1.6 + run: | + wget -qO ${{ github.workspace }}/circom https://github.com/iden3/circom/releases/download/v2.1.6/circom-linux-amd64 + chmod +x ${{ github.workspace }}/circom + sudo mv ${{ github.workspace }}/circom /bin/circom + + - name: Download zkeys + run: | + pnpm download-zkeys:test + + - name: Generate keypair + run: | + pnpm generate-keypair + working-directory: packages/coordinator + + - name: E2E tests + run: | + pnpm run test:e2e diff --git a/packages/coordinator/.env.example b/packages/coordinator/.env.example index 220cc331..027629d7 100644 --- a/packages/coordinator/.env.example +++ b/packages/coordinator/.env.example @@ -47,3 +47,9 @@ SUBGRAPH_FOLDER=./node_modules/maci-subgraph # API Key for Pimlico RPC Bundler PIMLICO_API_KEY="pim_" + +# A private key for e2e tests +TEST_PRIVATE_KEY="" + +# An RPC API KEY to interact with live networks (used in e2e tests) +RPC_API_KEY="" diff --git a/packages/coordinator/package.json b/packages/coordinator/package.json index 180bfb27..d6f12bfc 100644 --- a/packages/coordinator/package.json +++ b/packages/coordinator/package.json @@ -19,8 +19,9 @@ "run:node": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));'", "start": "pnpm run run:node ./ts/main.ts", "start:prod": "pnpm run run:node build/ts/main.js", - "test": "jest --forceExit", + "test": "jest --testPathIgnorePatterns=e2e.aa.test.ts --forceExit", "test:coverage": "jest --coverage --forceExit", + "test:e2e": "jest --forceExit ./tests/e2e.aa.test.ts", "types": "tsc -p tsconfig.json --noEmit", "generate-keypair": "pnpm run run:node ./scripts/generateKeypair.ts", "download-zkeys:test": "pnpm run run:node ./scripts/downloadZKeys.ts test ./zkeys", @@ -54,13 +55,13 @@ "maci-domainobjs": "^2.0.0", "maci-subgraph": "^2.3.0", "mustache": "^4.2.0", - "permissionless": "^0.1.44", + "permissionless": ">=0.1.18 <=0.1.29", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "socket.io": "^4.7.5", "tar": "^7.4.1", "ts-node": "^10.9.1", - "viem": "^2.7.15" + "viem": "^2.16.3" }, "devDependencies": { "@nestjs/cli": "^10.4.2", diff --git a/packages/coordinator/tests/e2e.aa.test.ts b/packages/coordinator/tests/e2e.aa.test.ts new file mode 100644 index 00000000..080e5923 --- /dev/null +++ b/packages/coordinator/tests/e2e.aa.test.ts @@ -0,0 +1,100 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; +import { serializePermissionAccount, toPermissionValidator } from "@zerodev/permissions"; +import { toSudoPolicy } from "@zerodev/permissions/policies"; +import { toECDSASigner } from "@zerodev/permissions/signers"; +import { addressToEmptyAccount, createKernelAccount } from "@zerodev/sdk"; +import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; +import dotenv from "dotenv"; +import { ENTRYPOINT_ADDRESS_V07 } from "permissionless"; +import { Hex } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; + +import { ErrorCodes, ESupportedNetworks } from "../ts/common"; +import { getPublicClient } from "../ts/common/accountAbstraction"; +import { CryptoService } from "../ts/crypto/crypto.service"; +import { FileService } from "../ts/file/file.service"; +import { ProofGeneratorService } from "../ts/proof/proof.service"; +import { SessionKeysService } from "../ts/sessionKeys/sessionKeys.service"; + +dotenv.config(); + +const entryPoint = ENTRYPOINT_ADDRESS_V07; +const kernelVersion = KERNEL_V3_1; + +/** + * Generate an approval for a session key + * + * @returns - the approval + */ +export const generateApproval = async (sessionKeyAddress: Hex): Promise => { + const publicClient = getPublicClient(ESupportedNetworks.OPTIMISM_SEPOLIA); + + const sessionKeySigner = privateKeyToAccount(process.env.TEST_PRIVATE_KEY! as `0x${string}`); + const ecdsaValidator = await signerToEcdsaValidator(publicClient, { + signer: sessionKeySigner, + entryPoint, + kernelVersion, + }); + + const emptyAccount = addressToEmptyAccount(sessionKeyAddress); + const emptySessionKeySigner = toECDSASigner({ signer: emptyAccount }); + + const permissionPlugin = await toPermissionValidator(publicClient, { + entryPoint, + kernelVersion, + signer: emptySessionKeySigner, + policies: [toSudoPolicy({})], + }); + + const sessionKeyAccount = await createKernelAccount(publicClient, { + entryPoint, + kernelVersion, + plugins: { + sudo: ecdsaValidator, + regular: permissionPlugin, + }, + }); + + return serializePermissionAccount(sessionKeyAccount); +}; + +describe("E2E Account Abstraction Tests", () => { + const fileService = new FileService(); + const sessionKeyService = new SessionKeysService(fileService); + const cryptoService = new CryptoService(); + const proofService = new ProofGeneratorService(cryptoService, fileService, sessionKeyService); + + // using an already deployed maci contract + const maciContract = "0xf281870519822f302B13c07252d0f6A71E8D023e"; + const pollId = 2; + + test("should return true when there are no errors", async () => { + const sessionKey = sessionKeyService.generateSessionKey().sessionKeyAddress; + const approval = await generateApproval(sessionKey); + + const merged = await proofService.merge({ + maciContractAddress: maciContract, + pollId, + sessionKeyAddress: sessionKey, + approval, + chain: ESupportedNetworks.OPTIMISM_SEPOLIA, + }); + + expect(merged).toBe(true); + }); + + test("should throw when given an invalid pollId", async () => { + const sessionKey = sessionKeyService.generateSessionKey().sessionKeyAddress; + const approval = await generateApproval(sessionKey); + + await expect( + proofService.merge({ + maciContractAddress: maciContract, + pollId: 50000, + sessionKeyAddress: sessionKey, + approval, + chain: ESupportedNetworks.OPTIMISM_SEPOLIA, + }), + ).rejects.toThrow(ErrorCodes.POLL_NOT_FOUND); + }); +}); diff --git a/packages/coordinator/ts/common/accountAbstraction.ts b/packages/coordinator/ts/common/accountAbstraction.ts index 0621c6e0..deb39128 100644 --- a/packages/coordinator/ts/common/accountAbstraction.ts +++ b/packages/coordinator/ts/common/accountAbstraction.ts @@ -1,5 +1,12 @@ +import { deserializePermissionAccount } from "@zerodev/permissions"; +import { toECDSASigner } from "@zerodev/permissions/signers"; +import { createKernelAccountClient, KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk"; +import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; import dotenv from "dotenv"; -import { type Chain, createPublicClient, http, type HttpTransport, type PublicClient } from "viem"; +import { ENTRYPOINT_ADDRESS_V07 } from "permissionless"; +import { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types"; +import { createPublicClient, http, type HttpTransport, type Transport, type Hex, type PublicClient, Chain } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; import { ErrorCodes } from "./errors"; import { ESupportedNetworks, viemChain } from "./networks"; @@ -38,6 +45,8 @@ export const genAlchemyRPCUrl = (network: ESupportedNetworks): string => { return `https://opt-sepolia.g.alchemy.com/v2/${rpcAPIKey}`; case ESupportedNetworks.ETHEREUM_SEPOLIA: return `https://eth-sepolia.g.alchemy.com/v2/${rpcAPIKey}`; + case ESupportedNetworks.LOCALHOST: + return "http://localhost:8545"; default: throw new Error(ErrorCodes.UNSUPPORTED_NETWORK); } @@ -53,3 +62,46 @@ export const getPublicClient = (chainName: ESupportedNetworks): PublicClient + > +> => { + const bundlerUrl = genPimlicoRPCUrl(chain); + const publicClient = getPublicClient(chain); + + // Using a stored private key + const sessionKeySigner = toECDSASigner({ + signer: privateKeyToAccount(sessionKey), + }); + + const sessionKeyAccount = await deserializePermissionAccount( + publicClient, + ENTRYPOINT_ADDRESS_V07, + KERNEL_V3_1, + approval, + sessionKeySigner, + ); + + const kernelClient = createKernelAccountClient({ + bundlerTransport: http(bundlerUrl), + entryPoint: ENTRYPOINT_ADDRESS_V07, + account: sessionKeyAccount, + chain: viemChain(chain), + }); + + return kernelClient; +}; diff --git a/packages/coordinator/ts/common/errors.ts b/packages/coordinator/ts/common/errors.ts index 27a5151a..2e9732c4 100644 --- a/packages/coordinator/ts/common/errors.ts +++ b/packages/coordinator/ts/common/errors.ts @@ -15,4 +15,7 @@ export enum ErrorCodes { INVALID_APPROVAL = "10", UNSUPPORTED_NETWORK = "11", RPC_API_KEY_NOT_SET = "12", + FAILED_TO_MERGE_STATE_TREE = "13", + FAILED_TO_MERGE_MESSAGE_SUBTREES = "14", + FAILED_TO_MERGE_MESSAGE_TREE = "15", } diff --git a/packages/coordinator/ts/common/index.ts b/packages/coordinator/ts/common/index.ts index 78bdf495..b3ecbda8 100644 --- a/packages/coordinator/ts/common/index.ts +++ b/packages/coordinator/ts/common/index.ts @@ -1,2 +1,3 @@ export { ErrorCodes } from "./errors"; export { ESupportedNetworks } from "./networks"; +export { transformToString } from "./utils"; diff --git a/packages/coordinator/ts/common/networks.ts b/packages/coordinator/ts/common/networks.ts index 65d77512..f1ea30c9 100644 --- a/packages/coordinator/ts/common/networks.ts +++ b/packages/coordinator/ts/common/networks.ts @@ -9,6 +9,7 @@ import { holesky, linea, lineaSepolia, + localhost, mainnet, optimism, optimismSepolia, @@ -36,6 +37,7 @@ export enum ESupportedNetworks { BASE = "base", SCROLL_SEPOLIA = "scroll-sepolia", SCROLL = "scroll", + LOCALHOST = "localhost", } /** @@ -78,6 +80,8 @@ export const viemChain = (network: ESupportedNetworks): Chain => { return optimism; case ESupportedNetworks.OPTIMISM_SEPOLIA: return optimismSepolia; + case ESupportedNetworks.LOCALHOST: + return localhost; default: throw new Error(`Unsupported network: ${network}`); } diff --git a/packages/coordinator/ts/common/utils.ts b/packages/coordinator/ts/common/utils.ts new file mode 100644 index 00000000..1add4ffa --- /dev/null +++ b/packages/coordinator/ts/common/utils.ts @@ -0,0 +1,7 @@ +/** + * Transform string to lowercase + * + * @param param0 - string to transform + * @returns lowercase string + */ +export const transformToString = ({ value }: { value: string }): string => value.toLowerCase(); diff --git a/packages/coordinator/ts/proof/__tests__/proof.controller.test.ts b/packages/coordinator/ts/proof/__tests__/proof.controller.test.ts index 04c04a18..b88f8be7 100644 --- a/packages/coordinator/ts/proof/__tests__/proof.controller.test.ts +++ b/packages/coordinator/ts/proof/__tests__/proof.controller.test.ts @@ -2,9 +2,10 @@ import { HttpException, HttpStatus } from "@nestjs/common"; import { Test } from "@nestjs/testing"; import type { IGetPublicKeyData } from "../../file/types"; -import type { IGenerateArgs, IGenerateData } from "../types"; +import type { IGenerateArgs, IGenerateData, IMergeArgs } from "../types"; import type { TallyData } from "maci-cli"; +import { ESupportedNetworks } from "../../common"; import { FileService } from "../../file/file.service"; import { ProofController } from "../proof.controller"; import { ProofGeneratorService } from "../proof.service"; @@ -21,6 +22,14 @@ describe("ProofController", () => { "siO9W/g7jNVXs9tOUv/pffrcqYdMlgdXw7nSSlqM1q1UvHGSSbhtLJpeT+nJKW7/+xrBTgI0wB866DSkg8Rgr8zD+POUMiKPrGqAO/XhrcmRDL+COURFNDRh9WGeAua6hdiNoufQYvXPl1iWyIYidSDbfmC2wR6F9vVkhg/6KDZyw8Wlr6LUh0RYT+hUHEwwGbz7MeqZJcJQSTpECPF5pnk8NTHL2W/XThaewB4n4HYqjDUbYLmBDLYWsDDMgoPo709a309rTq3uEe0YBgVF8g9aGxucTDhz+/LYYzqaeSxclUwen9Z4BGZjiDSPBZfooOEQEEwIJlViQ2kl1VeOKAmkiWEUVfItivmNbC/PNZchklmfFsGpiu4DT9UU9YVBN2OTcFYHHsslcaqrR7SuesqjluaGjG46oYEmfQlkZ4gXhavdWXw2ant+Tv6HRo4trqjoD1e3jUkN6gJMWomxOeRBTg0czBZlz/IwUtTpBHcKhi3EqGQo8OuQtWww+Ts7ySmeoONuovYUsIAppNuOubfUxvFJoTr2vKbWNAiYetw09kddkjmBe+S8A5PUiFOi262mfc7g5wJwPPP7wpTBY0Fya+2BCPzXqRLMOtNI+1tW3/UQLZYvEY8J0TxmhoAGZaRn8FKaosatRxDZTQS6QUNmKxpmUspkRKzTXN5lznM=", }; + const defaultMergeArgs: IMergeArgs = { + maciContractAddress: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + pollId: 0, + sessionKeyAddress: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + approval: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + chain: ESupportedNetworks.LOCALHOST, + }; + const defaultProofGeneratorData: IGenerateData = { tallyProofs: [], processProofs: [], @@ -33,6 +42,7 @@ describe("ProofController", () => { const mockGeneratorService = { generate: jest.fn(), + merge: jest.fn(), }; const mockFileService = { @@ -46,6 +56,7 @@ describe("ProofController", () => { .useMocker((token) => { if (token === ProofGeneratorService) { mockGeneratorService.generate.mockResolvedValue(defaultProofGeneratorData); + mockGeneratorService.merge.mockResolvedValue(true); return mockGeneratorService; } @@ -83,6 +94,13 @@ describe("ProofController", () => { }); }); + describe("v1/proof/merge", () => { + test("should return true when there are no errors", async () => { + const data = await proofController.merge(defaultMergeArgs); + expect(data).toBe(true); + }); + }); + describe("v1/proof/publicKey", () => { test("should return public key properly", async () => { const data = await proofController.getPublicKey(); diff --git a/packages/coordinator/ts/proof/__tests__/proof.service.test.ts b/packages/coordinator/ts/proof/__tests__/proof.service.test.ts index 9547153f..55c860a5 100644 --- a/packages/coordinator/ts/proof/__tests__/proof.service.test.ts +++ b/packages/coordinator/ts/proof/__tests__/proof.service.test.ts @@ -1,13 +1,14 @@ import dotenv from "dotenv"; -import { ZeroAddress } from "ethers"; import { Deployment, ProofGenerator } from "maci-contracts"; import { Keypair, PrivKey } from "maci-domainobjs"; +import { zeroAddress } from "viem"; import type { IGenerateArgs } from "../types"; import { ErrorCodes } from "../../common"; import { CryptoService } from "../../crypto/crypto.service"; import { FileService } from "../../file/file.service"; +import { SessionKeysService } from "../../sessionKeys/sessionKeys.service"; import { ProofGeneratorService } from "../proof.service"; dotenv.config(); @@ -36,10 +37,10 @@ jest.mock("../../crypto/crypto.service", (): unknown => ({ })); describe("ProofGeneratorService", () => { - const defaultArgs: IGenerateArgs = { + const defaultProofArgs: IGenerateArgs = { poll: 1, - maciContractAddress: ZeroAddress, - tallyContractAddress: ZeroAddress, + maciContractAddress: zeroAddress, + tallyContractAddress: zeroAddress, useQuadraticVoting: false, encryptedCoordinatorPrivateKey: "siO9W/g7jNVXs9tOUv/pffrcqYdMlgdXw7nSSlqM1q1UvHGSSbhtLJpeT+nJKW7/+xrBTgI0wB866DSkg8Rgr8zD+POUMiKPrGqAO/XhrcmRDL+COURFNDRh9WGeAua6hdiNoufQYvXPl1iWyIYidSDbfmC2wR6F9vVkhg/6KDZyw8Wlr6LUh0RYT+hUHEwwGbz7MeqZJcJQSTpECPF5pnk8NTHL2W/XThaewB4n4HYqjDUbYLmBDLYWsDDMgoPo709a309rTq3uEe0YBgVF8g9aGxucTDhz+/LYYzqaeSxclUwen9Z4BGZjiDSPBZfooOEQEEwIJlViQ2kl1VeOKAmkiWEUVfItivmNbC/PNZchklmfFsGpiu4DT9UU9YVBN2OTcFYHHsslcaqrR7SuesqjluaGjG46oYEmfQlkZ4gXhavdWXw2ant+Tv6HRo4trqjoD1e3jUkN6gJMWomxOeRBTg0czBZlz/IwUtTpBHcKhi3EqGQo8OuQtWww+Ts7ySmeoONuovYUsIAppNuOubfUxvFJoTr2vKbWNAiYetw09kddkjmBe+S8A5PUiFOi262mfc7g5wJwPPP7wpTBY0Fya+2BCPzXqRLMOtNI+1tW3/UQLZYvEY8J0TxmhoAGZaRn8FKaosatRxDZTQS6QUNmKxpmUspkRKzTXN5lznM=", @@ -57,6 +58,7 @@ describe("ProofGeneratorService", () => { let defaultProofGenerator = { generateMpProofs: jest.fn(), generateTallyProofs: jest.fn(), + merge: jest.fn(), }; const defaultCryptoService = { @@ -70,15 +72,16 @@ describe("ProofGeneratorService", () => { }; const fileService = new FileService(); + const sessionKeysService = new SessionKeysService(fileService); beforeEach(() => { mockContract = { polls: jest.fn(() => - Promise.resolve({ poll: ZeroAddress.replace("0x0", "0x1"), messageProcessor: ZeroAddress, tally: ZeroAddress }), + Promise.resolve({ poll: zeroAddress.replace("0x0", "0x1"), messageProcessor: zeroAddress, tally: zeroAddress }), ), getMainRoot: jest.fn(() => Promise.resolve(1n)), treeDepths: jest.fn(() => Promise.resolve([1, 2, 3])), - extContracts: jest.fn(() => Promise.resolve({ messageAq: ZeroAddress })), + extContracts: jest.fn(() => Promise.resolve({ messageAq: zeroAddress })), stateMerged: jest.fn(() => Promise.resolve(true)), coordinatorPubKey: jest.fn(() => Promise.resolve({ @@ -91,6 +94,7 @@ describe("ProofGeneratorService", () => { defaultProofGenerator = { generateMpProofs: jest.fn(() => Promise.resolve([1])), generateTallyProofs: jest.fn(() => Promise.resolve({ proofs: [1], tallyData: {} })), + merge: jest.fn(() => Promise.resolve(true)), }; (defaultCryptoService.decrypt as jest.Mock) = jest.fn( @@ -117,64 +121,64 @@ describe("ProofGeneratorService", () => { test("should throw error if state is not merged yet", async () => { mockContract.stateMerged.mockResolvedValue(false); - const service = new ProofGeneratorService(defaultCryptoService, fileService); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - await expect(service.generate(defaultArgs)).rejects.toThrow(ErrorCodes.NOT_MERGED_STATE_TREE); + await expect(service.generate(defaultProofArgs)).rejects.toThrow(ErrorCodes.NOT_MERGED_STATE_TREE); }); test("should throw error if private key is wrong", async () => { const keypair = new Keypair(new PrivKey(0n)); mockContract.coordinatorPubKey.mockResolvedValue(keypair.pubKey.asContractParam()); - const service = new ProofGeneratorService(defaultCryptoService, fileService); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - await expect(service.generate(defaultArgs)).rejects.toThrow(ErrorCodes.PRIVATE_KEY_MISMATCH); + await expect(service.generate(defaultProofArgs)).rejects.toThrow(ErrorCodes.PRIVATE_KEY_MISMATCH); }); test("should throw error if there is no any poll", async () => { mockContract.getMainRoot.mockResolvedValue(0n); - const service = new ProofGeneratorService(defaultCryptoService, fileService); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - await expect(service.generate(defaultArgs)).rejects.toThrow(ErrorCodes.NOT_MERGED_MESSAGE_TREE); + await expect(service.generate(defaultProofArgs)).rejects.toThrow(ErrorCodes.NOT_MERGED_MESSAGE_TREE); }); test("should throw error if poll is not found", async () => { - const service = new ProofGeneratorService(defaultCryptoService, fileService); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - await expect(service.generate({ ...defaultArgs, poll: 2 })).rejects.toThrow(ErrorCodes.POLL_NOT_FOUND); + await expect(service.generate({ ...defaultProofArgs, poll: 2 })).rejects.toThrow(ErrorCodes.POLL_NOT_FOUND); }); test("should throw error if poll is not found in maci contract", async () => { - mockContract.polls.mockResolvedValue({ poll: ZeroAddress }); - const service = new ProofGeneratorService(defaultCryptoService, fileService); + mockContract.polls.mockResolvedValue({ poll: zeroAddress }); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - await expect(service.generate({ ...defaultArgs, poll: 2 })).rejects.toThrow(ErrorCodes.POLL_NOT_FOUND); + await expect(service.generate({ ...defaultProofArgs, poll: 2 })).rejects.toThrow(ErrorCodes.POLL_NOT_FOUND); }); test("should throw error if coordinator key cannot be decrypted", async () => { (defaultCryptoService.decrypt as jest.Mock).mockReturnValue("unknown"); - const service = new ProofGeneratorService(defaultCryptoService, fileService); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - await expect(service.generate({ ...defaultArgs, encryptedCoordinatorPrivateKey: "unknown" })).rejects.toThrow( + await expect(service.generate({ ...defaultProofArgs, encryptedCoordinatorPrivateKey: "unknown" })).rejects.toThrow( "Cannot convert 0x to a BigInt", ); }); test("should generate proofs properly for NonQv", async () => { - const service = new ProofGeneratorService(defaultCryptoService, fileService); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - const data = await service.generate(defaultArgs); + const data = await service.generate(defaultProofArgs); expect(data.processProofs).toHaveLength(1); expect(data.tallyProofs).toHaveLength(1); }); test("should generate proofs properly for Qv", async () => { - const service = new ProofGeneratorService(defaultCryptoService, fileService); + const service = new ProofGeneratorService(defaultCryptoService, fileService, sessionKeysService); - const data = await service.generate({ ...defaultArgs, useQuadraticVoting: true }); + const data = await service.generate({ ...defaultProofArgs, useQuadraticVoting: true }); expect(data.processProofs).toHaveLength(1); expect(data.tallyProofs).toHaveLength(1); diff --git a/packages/coordinator/ts/proof/dto.ts b/packages/coordinator/ts/proof/dto.ts index d51c99a7..5dcd48b1 100644 --- a/packages/coordinator/ts/proof/dto.ts +++ b/packages/coordinator/ts/proof/dto.ts @@ -101,3 +101,41 @@ export class GenerateProofDto { @IsOptional() blocksPerBatch?: number; } + +/** + * Data transfer object for merge trees + */ +export class MergeTreesDto { + /** + * Poll id + */ + @ApiProperty({ + description: "Poll id", + minimum: 0, + type: Number, + }) + @IsInt() + @Min(0) + poll!: number; + + @ApiProperty({ + description: "MACI contract address", + type: String, + }) + @IsEthereumAddress() + maciContractAddress!: string; + + @ApiProperty({ + description: "Session key address", + type: String, + }) + @IsEthereumAddress() + sessionKeyAddress!: string; + + @ApiProperty({ + description: "Approval", + type: String, + }) + @IsString() + approval!: string; +} diff --git a/packages/coordinator/ts/proof/proof.controller.ts b/packages/coordinator/ts/proof/proof.controller.ts index 8fcba0f0..7dcc80d5 100644 --- a/packages/coordinator/ts/proof/proof.controller.ts +++ b/packages/coordinator/ts/proof/proof.controller.ts @@ -2,13 +2,13 @@ import { Body, Controller, Get, HttpException, HttpStatus, Logger, Post, UseGuards } from "@nestjs/common"; import { ApiBearerAuth, ApiBody, ApiResponse, ApiTags } from "@nestjs/swagger"; -import type { IGenerateData } from "./types"; +import type { IGenerateData, IMergeArgs } from "./types"; import type { IGetPublicKeyData } from "../file/types"; import { AccountSignatureGuard, Public } from "../auth/AccountSignatureGuard.service"; import { FileService } from "../file/file.service"; -import { GenerateProofDto } from "./dto"; +import { GenerateProofDto, MergeTreesDto } from "./dto"; import { ProofGeneratorService } from "./proof.service"; @ApiTags("v1/proof") @@ -50,6 +50,24 @@ export class ProofController { }); } + /** + * Merge trees api method + * + * @param args - merge args + * @returns whether the trees were successfully merged + */ + @ApiBody({ type: MergeTreesDto }) + @ApiResponse({ status: HttpStatus.CREATED, description: "The proofs have been successfully merged" }) + @ApiResponse({ status: HttpStatus.FORBIDDEN, description: "Forbidden" }) + @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: "BadRequest" }) + @Post("merge") + async merge(@Body() args: IMergeArgs): Promise { + return this.proofGeneratorService.merge(args).catch((error: Error) => { + this.logger.error(`Error:`, error); + throw new HttpException(error.message, HttpStatus.BAD_REQUEST); + }); + } + /** * Get RSA public key for authorization setup * diff --git a/packages/coordinator/ts/proof/proof.module.ts b/packages/coordinator/ts/proof/proof.module.ts index 4f770850..e6fafe4b 100644 --- a/packages/coordinator/ts/proof/proof.module.ts +++ b/packages/coordinator/ts/proof/proof.module.ts @@ -2,13 +2,14 @@ import { Module } from "@nestjs/common"; import { CryptoModule } from "../crypto/crypto.module"; import { FileModule } from "../file/file.module"; +import { SessionKeysModule } from "../sessionKeys/sessionKeys.module"; import { ProofController } from "./proof.controller"; import { ProofGateway } from "./proof.gateway"; import { ProofGeneratorService } from "./proof.service"; @Module({ - imports: [FileModule, CryptoModule], + imports: [FileModule, CryptoModule, SessionKeysModule], controllers: [ProofController], providers: [ProofGeneratorService, ProofGateway], }) diff --git a/packages/coordinator/ts/proof/proof.service.ts b/packages/coordinator/ts/proof/proof.service.ts index 010c47ec..d0fd95c7 100644 --- a/packages/coordinator/ts/proof/proof.service.ts +++ b/packages/coordinator/ts/proof/proof.service.ts @@ -9,16 +9,22 @@ import { type MACI, type AccQueue, type IGenerateProofsOptions, + Poll__factory as PollFactory, + AccQueueQuinaryMaci__factory as AccQueueQuinaryMaciFactory, + MACI__factory as MACIFactory, } from "maci-contracts"; import { Keypair, PrivKey, PubKey } from "maci-domainobjs"; +import { Hex } from "viem"; import path from "path"; -import type { IGenerateArgs, IGenerateData } from "./types"; +import type { IGenerateArgs, IGenerateData, IMergeArgs, IMergeMessageSubTreesArgs } from "./types"; import { ErrorCodes } from "../common"; +import { getPublicClient } from "../common/accountAbstraction"; import { CryptoService } from "../crypto/crypto.service"; import { FileService } from "../file/file.service"; +import { SessionKeysService } from "../sessionKeys/sessionKeys.service"; /** * ProofGeneratorService is responsible for generating message processing and tally proofs. @@ -41,6 +47,7 @@ export class ProofGeneratorService { constructor( private readonly cryptoService: CryptoService, private readonly fileService: FileService, + private readonly sessionKeysService: SessionKeysService, ) { this.deployment = Deployment.getInstance(hre); this.deployment.setHre(hre); @@ -172,4 +179,178 @@ export class ProofGeneratorService { throw error; } } + + /** + * Merge message accumular queue sub trees + * + * @param param0 - merge message sub trees arguments + */ + async mergeMessageSubTrees({ + publicClient, + kernelClient, + pollAddress, + messageAqAddress, + }: IMergeMessageSubTreesArgs): Promise { + let subTreesMerged = false; + + while (!subTreesMerged) { + // eslint-disable-next-line no-await-in-loop + subTreesMerged = await publicClient.readContract({ + address: messageAqAddress, + abi: AccQueueQuinaryMaciFactory.abi, + functionName: "subTreesMerged", + }); + + if (subTreesMerged) { + this.logger.debug("All subtrees are merged"); + } else { + // eslint-disable-next-line no-await-in-loop + const indices = await publicClient.readContract({ + address: messageAqAddress, + abi: AccQueueQuinaryMaciFactory.abi, + functionName: "getSrIndices", + }); + + this.logger.debug(`Merging message subroots ${indices[0] + 1n} / ${indices[1] + 1n}`); + + // eslint-disable-next-line no-await-in-loop + const { request } = await publicClient.simulateContract({ + // @ts-expect-error type error between permissionless.js and viem + account: kernelClient.account, + address: pollAddress, + abi: PollFactory.abi, + functionName: "mergeMessageAqSubRoots", + args: [4n], + }); + + // eslint-disable-next-line no-await-in-loop + const txHash = await kernelClient.writeContract(request); + + // eslint-disable-next-line no-await-in-loop + const txReceipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + if (txReceipt.status !== "success") { + this.logger.error(`Error: ${ErrorCodes.FAILED_TO_MERGE_MESSAGE_SUBTREES}, message subtree merge failed`); + throw new Error(ErrorCodes.FAILED_TO_MERGE_MESSAGE_SUBTREES); + } + } + } + } + + /** + * Merge state and message trees + * @returns whether the proofs were successfully merged + */ + async merge({ maciContractAddress, pollId, approval, sessionKeyAddress, chain }: IMergeArgs): Promise { + const publicClient = getPublicClient(chain); + + const pollContracts = await publicClient.readContract({ + address: maciContractAddress as Hex, + abi: MACIFactory.abi, + functionName: "getPoll", + args: [BigInt(pollId)], + }); + + const pollAddress = pollContracts.poll; + + if (pollAddress.toLowerCase() === ZeroAddress.toLowerCase()) { + this.logger.error(`Error: ${ErrorCodes.POLL_NOT_FOUND}, Poll ${pollId} not found`); + throw new Error(ErrorCodes.POLL_NOT_FOUND); + } + + // get a kernel client + const kernelClient = await this.sessionKeysService.generateClientFromSessionKey(sessionKeyAddress, approval, chain); + + // get external contracts + const externalContracts = await publicClient.readContract({ + address: pollAddress, + abi: PollFactory.abi, + functionName: "extContracts", + }); + + // start with the state tree + const isStateMerged = await publicClient.readContract({ + address: pollAddress, + abi: PollFactory.abi, + functionName: "stateMerged", + }); + + if (isStateMerged) { + this.logger.debug("State tree is already merged"); + } else { + // merge it + const { request } = await publicClient.simulateContract({ + // @ts-expect-error type error between permissionless.js and viem + account: kernelClient.account, + address: pollAddress, + abi: PollFactory.abi, + functionName: "mergeMaciState", + }); + + const txHash = await kernelClient.writeContract(request); + const txReceipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + if (txReceipt.status !== "success") { + this.logger.error(`Error: ${ErrorCodes.FAILED_TO_MERGE_STATE_TREE}, state tree merge failed`); + throw new Error(ErrorCodes.FAILED_TO_MERGE_STATE_TREE); + } + } + + // merge the message sub trees first + await this.mergeMessageSubTrees({ + publicClient, + kernelClient, + pollAddress, + messageAqAddress: externalContracts[1], + }); + + // then merge the main root + const messageTreeDepth = await publicClient + .readContract({ + address: pollAddress, + abi: PollFactory.abi, + functionName: "treeDepths", + }) + .then((depths) => BigInt(depths[2])); + + const mainRoot = await publicClient.readContract({ + address: externalContracts[1], + abi: AccQueueQuinaryMaciFactory.abi, + functionName: "getMainRoot", + args: [messageTreeDepth], + }); + + if (mainRoot.toString() === "0") { + this.logger.debug(`Message tree is not merged yet`); + + const { request } = await publicClient.simulateContract({ + // @ts-expect-error type error between permissionless.js and viem + account: kernelClient.account, + address: pollAddress, + abi: PollFactory.abi, + functionName: "mergeMessageAq", + args: [], + }); + + const txHash = await kernelClient.writeContract(request); + const txReceipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + if (txReceipt.status === "success") { + this.logger.debug(`Message tree has been merged`); + } else { + this.logger.error(`Error: ${ErrorCodes.FAILED_TO_MERGE_MESSAGE_TREE}, message tree merge failed`); + throw new Error(ErrorCodes.FAILED_TO_MERGE_MESSAGE_TREE); + } + } else { + this.logger.debug(`Message tree has already been merged`); + } + + return true; + } } diff --git a/packages/coordinator/ts/proof/types.ts b/packages/coordinator/ts/proof/types.ts index 159433d3..34d44bdf 100644 --- a/packages/coordinator/ts/proof/types.ts +++ b/packages/coordinator/ts/proof/types.ts @@ -1,5 +1,10 @@ +import type { KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk"; import type { TallyData } from "maci-cli"; import type { Proof } from "maci-contracts"; +import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types"; +import type { Chain, Hex, HttpTransport, PublicClient, Transport } from "viem"; + +import { ESupportedNetworks } from "../common"; /** * WS events for proof generation @@ -90,3 +95,56 @@ export interface IGetZkeyFilesData { */ wasm: string; } + +/** + * Merge arguments + */ +export interface IMergeArgs { + /** + * MACI contract address + */ + maciContractAddress: string; + /** + * Poll ID + */ + pollId: number; + /** + * Approval for the session key + */ + approval: string; + /** + * Session key address + */ + sessionKeyAddress: Hex; + /** + * Chain + */ + chain: ESupportedNetworks; +} + +/** + * Merge message sub-trees + */ +export interface IMergeMessageSubTreesArgs { + /** + * Public client + */ + publicClient: PublicClient; + /** + * Kernel client + */ + kernelClient: KernelAccountClient< + ENTRYPOINT_ADDRESS_V07_TYPE, + Transport, + Chain, + KernelSmartAccount + >; + /** + * Poll address + */ + pollAddress: Hex; + /** + * Message AQ address + */ + messageAqAddress: Hex; +} diff --git a/packages/coordinator/ts/sessionKeys/__tests__/sessionKeys.controller.test.ts b/packages/coordinator/ts/sessionKeys/__tests__/sessionKeys.controller.test.ts index 1b865a7c..43698eb3 100644 --- a/packages/coordinator/ts/sessionKeys/__tests__/sessionKeys.controller.test.ts +++ b/packages/coordinator/ts/sessionKeys/__tests__/sessionKeys.controller.test.ts @@ -3,6 +3,7 @@ import { zeroAddress } from "viem"; import type { IGenerateSessionKeyReturn } from "../types"; +import { ESupportedNetworks } from "../../common"; import { SessionKeysController } from "../sessionKeys.controller"; import { SessionKeysService } from "../sessionKeys.service"; @@ -50,6 +51,7 @@ describe("SessionKeysController", () => { test("should delete a session key", () => { sessionKeysController.deactivateSessionKey({ sessionKeyAddress: zeroAddress, + chain: ESupportedNetworks.OPTIMISM_SEPOLIA, }); expect(mockSessionKeysService.deactivateSessionKey).toHaveBeenCalledWith(zeroAddress); }); diff --git a/packages/coordinator/ts/sessionKeys/dto.ts b/packages/coordinator/ts/sessionKeys/dto.ts index 56a0064a..def1c628 100644 --- a/packages/coordinator/ts/sessionKeys/dto.ts +++ b/packages/coordinator/ts/sessionKeys/dto.ts @@ -1,8 +1,11 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsEthereumAddress } from "class-validator"; +import { Transform } from "class-transformer"; +import { IsEnum, IsEthereumAddress } from "class-validator"; import type { Hex } from "viem"; +import { ESupportedNetworks, transformToString } from "../common"; + /** * Data transfer object for Deactivate session key */ @@ -16,4 +19,15 @@ export class DeactivateSessionKeyDto { }) @IsEthereumAddress() sessionKeyAddress!: Hex; + + /** + * Chain Name + */ + @ApiProperty({ + description: "Chain to which to deploy the contract(s)", + enum: ESupportedNetworks, + }) + @IsEnum(ESupportedNetworks) + @Transform(transformToString) + chain!: ESupportedNetworks; } diff --git a/packages/coordinator/ts/sessionKeys/sessionKeys.service.ts b/packages/coordinator/ts/sessionKeys/sessionKeys.service.ts index e76a725a..b3b1f51f 100644 --- a/packages/coordinator/ts/sessionKeys/sessionKeys.service.ts +++ b/packages/coordinator/ts/sessionKeys/sessionKeys.service.ts @@ -11,7 +11,7 @@ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import type { Chain, Hex, HttpTransport, Transport } from "viem"; import { ErrorCodes, ESupportedNetworks } from "../common"; -import { genPimlicoRPCUrl, getPublicClient } from "../common/accountAbstraction"; +import { getPublicClient } from "../common/accountAbstraction"; import { viemChain } from "../common/networks"; import { FileService } from "../file/file.service"; @@ -86,8 +86,7 @@ export class SessionKeysService { throw new Error(ErrorCodes.SESSION_KEY_NOT_FOUND); } - // get the bundler url and create a public client - const bundlerUrl = genPimlicoRPCUrl(chain); + // create a public client const publicClient = getPublicClient(chain); // Using a stored private key @@ -106,7 +105,7 @@ export class SessionKeysService { ); return createKernelAccountClient({ - bundlerTransport: http(bundlerUrl), + bundlerTransport: http(process.env.ZERODEV_BUNDLER_RPC), entryPoint: ENTRYPOINT_ADDRESS_V07, account: sessionKeyAccount, chain: viemChain(chain), diff --git a/packages/coordinator/ts/subgraph/__tests__/subgraph.service.test.ts b/packages/coordinator/ts/subgraph/__tests__/subgraph.service.test.ts index 3608784f..a01304f0 100644 --- a/packages/coordinator/ts/subgraph/__tests__/subgraph.service.test.ts +++ b/packages/coordinator/ts/subgraph/__tests__/subgraph.service.test.ts @@ -5,8 +5,7 @@ import fs from "fs"; import type { IDeploySubgraphArgs } from "../types"; -import { ErrorCodes, ESupportedNetworks } from "../../common"; -import { transformToString } from "../dto"; +import { ErrorCodes, ESupportedNetworks, transformToString } from "../../common"; import { SubgraphService } from "../subgraph.service"; dotenv.config(); diff --git a/packages/coordinator/ts/subgraph/dto.ts b/packages/coordinator/ts/subgraph/dto.ts index 449048b1..6befec8d 100644 --- a/packages/coordinator/ts/subgraph/dto.ts +++ b/packages/coordinator/ts/subgraph/dto.ts @@ -2,9 +2,7 @@ import { ApiProperty } from "@nestjs/swagger"; import { Transform } from "class-transformer"; import { IsEnum, IsEthereumAddress, IsInt, IsString, Matches, MaxLength, Min, MinLength } from "class-validator"; -import { ESupportedNetworks } from "../common"; - -export const transformToString = ({ value }: { value: string }): string => value.toLowerCase(); +import { ESupportedNetworks, transformToString } from "../common"; /** * Data transfer object for deploying subgraph diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f8f92ae..8b1fac88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,16 +194,16 @@ importers: version: 5.0.0(hke34ef7km4fdyz52h5gry5opq) '@zerodev/ecdsa-validator': specifier: ^5.3.1 - version: 5.3.1(@zerodev/sdk@5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + version: 5.3.1(@zerodev/sdk@5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) '@zerodev/permissions': specifier: ^5.4.3 - version: 5.4.3(@zerodev/sdk@5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(@zerodev/webauthn-key@5.3.1(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + version: 5.4.3(@zerodev/sdk@5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(@zerodev/webauthn-key@5.3.1(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) '@zerodev/sdk': specifier: ^5.3.8 - version: 5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + version: 5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) '@zerodev/session-key': specifier: ^5.4.2 - version: 5.4.2(@zerodev/sdk@5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + version: 5.4.2(@zerodev/sdk@5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -244,8 +244,8 @@ importers: specifier: ^4.2.0 version: 4.2.0 permissionless: - specifier: ^0.1.44 - version: 0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + specifier: '>=0.1.18 <=0.1.29' + version: 0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) reflect-metadata: specifier: ^0.2.0 version: 0.2.2 @@ -262,7 +262,7 @@ importers: specifier: ^10.9.1 version: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) viem: - specifier: ^2.7.15 + specifier: ^2.16.3 version: 2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) devDependencies: '@nestjs/cli': @@ -10671,10 +10671,10 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - permissionless@0.1.44: - resolution: {integrity: sha512-NQBATmG4Fp3Zqy1IjjBihfp2huV6sTzUUzZzuSQ7xBnRNABOyIm8d+q76gy2B0LnFfu47RA/aW+fNfZjnbzl4Q==} + permissionless@0.1.29: + resolution: {integrity: sha512-jKkgl3G2b4hv5DvX2U8v0OI3JfX+nEzj9a6zsrcXuJaLZdS14td4tExnlmBY93nUN6lRC6NfPZjdLbi8Dkzz7w==} peerDependencies: - viem: '>=2.14.1 <2.18.0' + viem: ^2.9.17 picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -19581,32 +19581,32 @@ snapshots: js-yaml: 3.14.1 tslib: 2.6.3 - '@zerodev/ecdsa-validator@5.3.1(@zerodev/sdk@5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': + '@zerodev/ecdsa-validator@5.3.1(@zerodev/sdk@5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': dependencies: - '@zerodev/sdk': 5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) - permissionless: 0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + '@zerodev/sdk': 5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + permissionless: 0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) viem: 2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) - '@zerodev/permissions@5.4.3(@zerodev/sdk@5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(@zerodev/webauthn-key@5.3.1(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': + '@zerodev/permissions@5.4.3(@zerodev/sdk@5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(@zerodev/webauthn-key@5.3.1(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': dependencies: '@simplewebauthn/browser': 9.0.1 - '@zerodev/sdk': 5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + '@zerodev/sdk': 5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) '@zerodev/webauthn-key': 5.3.1(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) merkletreejs: 0.3.11 - permissionless: 0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + permissionless: 0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) viem: 2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) - '@zerodev/sdk@5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': + '@zerodev/sdk@5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': dependencies: - permissionless: 0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + permissionless: 0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) semver: 7.6.3 viem: 2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) - '@zerodev/session-key@5.4.2(@zerodev/sdk@5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': + '@zerodev/session-key@5.4.2(@zerodev/sdk@5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': dependencies: - '@zerodev/sdk': 5.3.9(permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + '@zerodev/sdk': 5.3.9(permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)))(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) merkletreejs: 0.3.11 - permissionless: 0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) + permissionless: 0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)) viem: 2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) '@zerodev/webauthn-key@5.3.1(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))': @@ -27990,7 +27990,7 @@ snapshots: performance-now@2.1.0: {} - permissionless@0.1.44(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)): + permissionless@0.1.29(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)): dependencies: viem: 2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)