From 9210c619af0d5182e8d60a77813400a048459a42 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:28:17 +0200 Subject: [PATCH] feat: implement contract registries --- .../contracts/interfaces/IRegistryManager.sol | 3 + .../registryManager/RegistryManager.sol | 7 +- packages/contracts/hardhat.config.ts | 2 + packages/contracts/package.json | 8 +- .../tests/EASRegistryManager.test.ts | 10 +- .../contracts/tests/RegistryManager.test.ts | 10 + packages/contracts/ts/index.ts | 2 + packages/interface/package.json | 1 + .../src/components/AddedProjects.tsx | 11 +- .../interface/src/components/EmptyState.tsx | 2 +- packages/interface/src/contexts/Ballot.tsx | 10 +- packages/interface/src/contexts/Maci.tsx | 52 +--- packages/interface/src/contexts/types.ts | 9 +- .../components/ApplicationForm.tsx | 9 +- .../components/ApplicationHeader.tsx | 4 +- .../components/ApplicationItem.tsx | 23 +- .../components/ApplicationsToApprove.tsx | 73 ++--- .../applications/components/ReviewBar.tsx | 63 ++-- .../components/SelectAllButton.tsx | 6 +- .../hooks/useApplicationByTxHash.ts | 8 - .../applications/hooks/useApplicationId.ts | 8 + .../applications/hooks/useApplications.ts | 17 +- .../hooks/useApproveApplication.ts | 41 ++- .../hooks/useApprovedApplications.ts | 8 - .../hooks/useCreateApplication.ts | 89 ++++-- .../src/features/applications/types/index.ts | 1 + .../components/AllocationFormWrapper.tsx | 2 +- .../ballot/components/BallotConfirmation.tsx | 11 +- .../components/ProjectAvatarWithName.tsx | 10 +- .../ballot/components/SubmitBallotButton.tsx | 24 +- .../src/features/ballot/types/index.ts | 1 + .../projects/components/ProjectAvatar.tsx | 16 +- .../projects/components/ProjectBanner.tsx | 22 +- .../components/ProjectContributions.tsx | 8 +- .../projects/components/ProjectDetails.tsx | 21 +- .../projects/components/ProjectItem.tsx | 12 +- .../features/projects/components/Projects.tsx | 42 +-- .../projects/components/ProjectsResults.tsx | 2 +- .../projects/components/VotingWidget.tsx | 11 +- .../features/projects/hooks/useProjects.ts | 64 ++--- .../projects/hooks/useSelectProjects.ts | 52 ---- packages/interface/src/hooks/useMetadata.ts | 5 + packages/interface/src/hooks/useProfile.ts | 20 -- packages/interface/src/hooks/useRegistry.ts | 52 ++++ packages/interface/src/hooks/useResults.ts | 26 +- .../src/pages/applications/confirmation.tsx | 20 +- packages/interface/src/pages/ballot/index.tsx | 12 +- .../pages/projects/[projectId]/Project.tsx | 14 +- packages/interface/src/pages/stats/index.tsx | 19 +- .../src/server/api/routers/applications.ts | 43 ++- .../src/server/api/routers/projects.ts | 177 ++---------- .../src/server/api/routers/results.ts | 47 ++- .../interface/src/utils/fetchApplications.ts | 271 ++++++++++++++++++ packages/interface/src/utils/fetchMetadata.ts | 6 + packages/interface/src/utils/fetchPoll.ts | 34 ++- packages/interface/src/utils/fetchProjects.ts | 103 +++++++ packages/interface/src/utils/registry.ts | 231 +++++++++++++++ packages/interface/src/utils/types.ts | 160 ++++++++++- packages/subgraph/schemas/schema.v1.graphql | 3 +- packages/subgraph/src/poll.ts | 3 + packages/subgraph/src/registry.ts | 3 + .../subgraph/templates/subgraph.template.yaml | 8 +- pnpm-lock.yaml | 119 ++++---- 63 files changed, 1396 insertions(+), 755 deletions(-) delete mode 100644 packages/interface/src/features/applications/hooks/useApplicationByTxHash.ts create mode 100644 packages/interface/src/features/applications/hooks/useApplicationId.ts delete mode 100644 packages/interface/src/features/applications/hooks/useApprovedApplications.ts delete mode 100644 packages/interface/src/features/projects/hooks/useSelectProjects.ts delete mode 100644 packages/interface/src/hooks/useProfile.ts create mode 100644 packages/interface/src/hooks/useRegistry.ts create mode 100644 packages/interface/src/utils/fetchApplications.ts create mode 100644 packages/interface/src/utils/fetchProjects.ts create mode 100644 packages/interface/src/utils/registry.ts diff --git a/packages/contracts/contracts/interfaces/IRegistryManager.sol b/packages/contracts/contracts/interfaces/IRegistryManager.sol index 339eb107..24754f33 100644 --- a/packages/contracts/contracts/interfaces/IRegistryManager.sol +++ b/packages/contracts/contracts/interfaces/IRegistryManager.sol @@ -39,6 +39,7 @@ interface IRegistryManager { address indexed registry, RequestType indexed requestType, bytes32 indexed recipient, + uint256 recipientIndex, uint256 index, address payout, string metadataUrl @@ -47,6 +48,7 @@ interface IRegistryManager { address indexed registry, RequestType indexed requestType, bytes32 indexed recipient, + uint256 recipientIndex, uint256 index, address payout, string metadataUrl @@ -55,6 +57,7 @@ interface IRegistryManager { address indexed registry, RequestType indexed requestType, bytes32 indexed recipient, + uint256 recipientIndex, uint256 index, address payout, string metadataUrl diff --git a/packages/contracts/contracts/registryManager/RegistryManager.sol b/packages/contracts/contracts/registryManager/RegistryManager.sol index 184c1328..6d7b5780 100644 --- a/packages/contracts/contracts/registryManager/RegistryManager.sol +++ b/packages/contracts/contracts/registryManager/RegistryManager.sol @@ -57,17 +57,20 @@ contract RegistryManager is Ownable, IRegistryManager, ICommon { /// @inheritdoc IRegistryManager function process(Request memory request) public virtual override isValidRequest(request) { request.status = Status.Pending; - requests[requestCount] = request; + uint256 index = requestCount; unchecked { requestCount++; } + requests[index] = request; + emit RequestSent( request.registry, request.requestType, request.recipient.id, request.index, + index, request.recipient.recipient, request.recipient.metadataUrl ); @@ -85,6 +88,7 @@ contract RegistryManager is Ownable, IRegistryManager, ICommon { request.requestType, request.recipient.id, request.index, + index, request.recipient.recipient, request.recipient.metadataUrl ); @@ -108,6 +112,7 @@ contract RegistryManager is Ownable, IRegistryManager, ICommon { request.requestType, request.recipient.id, request.index, + index, request.recipient.recipient, request.recipient.metadataUrl ); diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 26bc11c6..abe7c514 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -6,6 +6,8 @@ import "hardhat-contract-sizer"; import "maci-contracts/tasks/deploy"; import "maci-contracts/tasks/runner/deployFull"; import "maci-contracts/tasks/runner/deployPoll"; +import "maci-contracts/tasks/runner/merge"; +import "maci-contracts/tasks/runner/prove"; import "maci-contracts/tasks/runner/verifyFull"; import "solidity-docgen"; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d2594c5c..46843b40 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -44,7 +44,13 @@ "deploy:optimism-sepolia": "pnpm run deploy --network optimism_sepolia", "deploy-poll:optimism-sepolia": "pnpm run deploy-poll --network optimism_sepolia", "initPoll:optimism-sepolia": "pnpm run initPoll --network optimism_sepolia", - "verify:optimism-sepolia": "pnpm run verify --network optimism_sepolia" + "verify:optimism-sepolia": "pnpm run verify --network optimism_sepolia", + "merge": "hardhat merge", + "merge:localhost": "pnpm run merge", + "merge:optimism-sepolia": "pnpm run merge --network optimism_sepolia", + "prove": "hardhat prove", + "prove:localhost": "pnpm run prove", + "prove:optimism-sepolia": "pnpm run prove --network optimism_sepolia" }, "dependencies": { "@nomicfoundation/hardhat-ethers": "^3.0.8", diff --git a/packages/contracts/tests/EASRegistryManager.test.ts b/packages/contracts/tests/EASRegistryManager.test.ts index 87ebedc9..234bfa03 100644 --- a/packages/contracts/tests/EASRegistryManager.test.ts +++ b/packages/contracts/tests/EASRegistryManager.test.ts @@ -64,23 +64,25 @@ describe("EASRegistryManager", () => { }); it("should allow owner to approve requests to the registry", async () => { - const addRequest = await registryManager.getRequest(0); + const requestIndex = 0; + const addRequest = await registryManager.getRequest(requestIndex); expect(addRequest.status).to.equal(ERegistryManagerRequestStatus.Pending); - await expect(registryManager.connect(owner).approve(0)) + await expect(registryManager.connect(owner).approve(requestIndex)) .to.emit(registryManager, "RequestApproved") .withArgs( addRequest.registry, addRequest.requestType, addRequest.recipient.id, addRequest.index, + requestIndex, addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); const changeRequest = { - index: 0, + index: requestIndex, registry: await mockRegistry.getAddress(), requestType: ERegistryManagerRequestType.Change, status: ERegistryManagerRequestStatus.Pending, @@ -98,6 +100,7 @@ describe("EASRegistryManager", () => { changeRequest.requestType, changeRequest.recipient.id, changeRequest.index, + 1, changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -109,6 +112,7 @@ describe("EASRegistryManager", () => { changeRequest.requestType, changeRequest.recipient.id, changeRequest.index, + 1, changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); diff --git a/packages/contracts/tests/RegistryManager.test.ts b/packages/contracts/tests/RegistryManager.test.ts index 86ff4a48..5ca49dc7 100644 --- a/packages/contracts/tests/RegistryManager.test.ts +++ b/packages/contracts/tests/RegistryManager.test.ts @@ -130,6 +130,7 @@ describe("RegistryManager", () => { addRequest.requestType, addRequest.recipient.id, addRequest.index, + addRequest.index, addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -177,6 +178,7 @@ describe("RegistryManager", () => { addRequest.requestType, addRequest.recipient.id, addRequest.index, + addRequest.index, addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -200,6 +202,7 @@ describe("RegistryManager", () => { changeRequest.requestType, changeRequest.recipient.id, changeRequest.index, + 1, changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -211,6 +214,7 @@ describe("RegistryManager", () => { changeRequest.requestType, changeRequest.recipient.id, changeRequest.index, + 1, changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -274,6 +278,7 @@ describe("RegistryManager", () => { addRequest.requestType, addRequest.recipient.id, addRequest.index, + 2, addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -285,6 +290,7 @@ describe("RegistryManager", () => { changeRequest.requestType, changeRequest.recipient.id, changeRequest.index, + 3, changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -296,6 +302,7 @@ describe("RegistryManager", () => { addRequest.requestType, addRequest.recipient.id, addRequest.index, + 2, addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -307,6 +314,7 @@ describe("RegistryManager", () => { changeRequest.requestType, changeRequest.recipient.id, changeRequest.index, + 3, changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -352,6 +360,7 @@ describe("RegistryManager", () => { removeRequest.requestType, removeRequest.recipient.id, removeRequest.index, + 4, removeRequest.recipient.recipient, removeRequest.recipient.metadataUrl, ); @@ -365,6 +374,7 @@ describe("RegistryManager", () => { removeRequest.requestType, removeRequest.recipient.id, removeRequest.index, + count - 1n, removeRequest.recipient.recipient, removeRequest.recipient.metadataUrl, ); diff --git a/packages/contracts/ts/index.ts b/packages/contracts/ts/index.ts index 98027a6c..5c74968e 100644 --- a/packages/contracts/ts/index.ts +++ b/packages/contracts/ts/index.ts @@ -1 +1,3 @@ export { ERegistryManagerRequestType, ERegistryManagerRequestStatus } from "./constants"; + +export * from "../typechain-types"; diff --git a/packages/interface/package.json b/packages/interface/package.json index 8b81a2f9..617fe4ae 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -48,6 +48,7 @@ "lucide-react": "^0.316.0", "maci-cli": "^2.4.0", "maci-domainobjs": "^2.4.0", + "maci-platform-contracts": "workspace:*", "next": "^14.1.0", "next-auth": "^4.24.5", "next-themes": "^0.2.1", diff --git a/packages/interface/src/components/AddedProjects.tsx b/packages/interface/src/components/AddedProjects.tsx index bb1c2ac8..79f538db 100644 --- a/packages/interface/src/components/AddedProjects.tsx +++ b/packages/interface/src/components/AddedProjects.tsx @@ -1,10 +1,17 @@ +import { zeroAddress } from "viem"; +import { useAccount } from "wagmi"; + import { useBallot } from "~/contexts/Ballot"; +import { useMaci } from "~/contexts/Maci"; import { useProjectCount } from "~/features/projects/hooks/useProjects"; export const AddedProjects = (): JSX.Element => { const { ballot } = useBallot(); + const { pollData } = useMaci(); + const { chain } = useAccount(); + const { data: projectCount } = useProjectCount({ registryAddress: pollData?.registry ?? zeroAddress, chain: chain! }); + const allocations = ballot.votes; - const { data: projectCount } = useProjectCount(); return (
@@ -20,7 +27,7 @@ export const AddedProjects = (): JSX.Element => { - {projectCount?.count} + {projectCount?.count.toString()}
diff --git a/packages/interface/src/components/EmptyState.tsx b/packages/interface/src/components/EmptyState.tsx index 1d1d242b..c14cdc3e 100644 --- a/packages/interface/src/components/EmptyState.tsx +++ b/packages/interface/src/components/EmptyState.tsx @@ -3,7 +3,7 @@ import { type PropsWithChildren } from "react"; import { Heading } from "./ui/Heading"; export const EmptyState = ({ title, children = null }: PropsWithChildren<{ title: string }>): JSX.Element => ( -
+
{title} diff --git a/packages/interface/src/contexts/Ballot.tsx b/packages/interface/src/contexts/Ballot.tsx index 5a53aec3..f7bc04c9 100644 --- a/packages/interface/src/contexts/Ballot.tsx +++ b/packages/interface/src/contexts/Ballot.tsx @@ -8,6 +8,7 @@ import { useMaci } from "./Maci"; export const BallotContext = createContext(undefined); +// the default ballot is an empty ballot const defaultBallot = { votes: [], published: false, edited: false }; export const BallotProvider: React.FC = ({ children }: BallotProviderProps) => { @@ -28,7 +29,8 @@ export const BallotProvider: React.FC = ({ children }: Ball [pollData], ); - const ballotContains = useCallback((id: string) => ballot.votes.find((v) => v.projectId === id), [ballot]); + // check if the ballot contains a specific project based on its index + const ballotContains = useCallback((index: number) => ballot.votes.find((v) => v.projectIndex === index), [ballot]); const toObject = useCallback( (key: string, arr: object[] = []) => arr.reduce((acc, x) => ({ ...acc, [x[key as keyof typeof acc]]: x }), {}), @@ -55,8 +57,8 @@ export const BallotProvider: React.FC = ({ children }: Ball // remove certain project from the ballot const removeFromBallot = useCallback( - (projectId: string) => { - const votes = ballot.votes.filter((v) => v.projectId !== projectId); + (projectIndex: number) => { + const votes = ballot.votes.filter((v) => v.projectIndex !== projectIndex); setBallot({ ...ballot, votes }); }, @@ -86,7 +88,7 @@ export const BallotProvider: React.FC = ({ children }: Ball setBallot({ ...ballot, published: true, edited: false }); }, [ballot, setBallot]); - /// Read existing ballot in localStorage + // Read existing ballot in localStorage useEffect(() => { const savedBallot = JSON.parse( localStorage.getItem("ballot") ?? JSON.stringify(defaultBallot), diff --git a/packages/interface/src/contexts/Maci.tsx b/packages/interface/src/contexts/Maci.tsx index 89c7ba9d..9221f262 100644 --- a/packages/interface/src/contexts/Maci.tsx +++ b/packages/interface/src/contexts/Maci.tsx @@ -3,15 +3,12 @@ import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; import { type StandardMerkleTreeData } from "@openzeppelin/merkle-tree/dist/standard"; import { type ZKEdDSAEventTicketPCD } from "@pcd/zk-eddsa-event-ticket-pcd/ZKEdDSAEventTicketPCD"; import { Identity } from "@semaphore-protocol/core"; -import { isAfter } from "date-fns"; -import { type Signer, BrowserProvider, AbiCoder } from "ethers"; +import { type Signer, AbiCoder } from "ethers"; import { signup, isRegisteredUser, publishBatch, type TallyData, - type IGetPollData, - getPoll, genKeyPair, GatekeeperTrait, getGatekeeperTrait, @@ -29,7 +26,7 @@ import { getSemaphoreProof } from "~/utils/semaphore"; import type { IVoteArgs, MaciContextType, MaciProviderProps } from "./types"; import type { PCD } from "@pcd/pcd-types"; -import type { EIP1193Provider } from "viem"; +import type { IPollData } from "~/utils/fetchPoll"; import type { Attestation } from "~/utils/types"; export const MaciContext = createContext(undefined); @@ -48,7 +45,7 @@ export const MaciProvider: React.FC = ({ children }: MaciProv const [initialVoiceCredits, setInitialVoiceCredits] = useState(0); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); - const [pollData, setPollData] = useState(); + const [pollData, setPollData] = useState(); const [tallyData, setTallyData] = useState(); const [treeData, setTreeData] = useState>(); @@ -251,6 +248,7 @@ export const MaciProvider: React.FC = ({ children }: MaciProv setSemaphoreIdentity(newSemaphoreIdentity); }, [address, signatureMessage, signMessageAsync, setMaciPrivKey, setMaciPubKey, setSemaphoreIdentity]); + // callback to be called from external component to store the zupass proof const storeZupassProof = useCallback( (proof: PCD) => { setZupassProof(proof); @@ -262,7 +260,7 @@ export const MaciProvider: React.FC = ({ children }: MaciProv const votingEndsAt = useMemo( () => pollData && pollData.duration !== 0 - ? new Date(Number(pollData.deployTime) * 1000 + Number(pollData.duration) * 1000) + ? new Date(Number(pollData.initTime) * 1000 + Number(pollData.duration) * 1000) : config.resultsAt, [pollData?.deployTime, pollData?.duration], ); @@ -410,15 +408,14 @@ export const MaciProvider: React.FC = ({ children }: MaciProv /// check the poll data and tally data useEffect(() => { - setIsLoading(true); - // if we have the subgraph url then it means we can get the poll data through there if (config.maciSubgraphUrl) { if (!poll.data) { - setIsLoading(false); return; } + setIsLoading(true); + const { isMerged, id } = poll.data; setPollData(poll.data); @@ -433,41 +430,6 @@ export const MaciProvider: React.FC = ({ children }: MaciProv } setIsLoading(false); - } else { - if (!window.ethereum) { - setIsLoading(false); - return; - } - - const provider = new BrowserProvider(window.ethereum as unknown as EIP1193Provider, { - chainId: config.network.id, - name: config.network.name, - }); - - getPoll({ - maciAddress: config.maciAddress!, - provider, - }) - .then((data) => { - setPollData(data); - return data; - }) - .then(async (data) => { - if (!data.isMerged || (votingEndsAt && isAfter(votingEndsAt, new Date()))) { - return undefined; - } - - return fetch(`${config.tallyUrl}/tally-${data.id}.json`) - .then((res) => res.json() as Promise) - .then((res) => { - setTallyData(res); - }) - .catch(() => undefined); - }) - .catch(console.error) - .finally(() => { - setIsLoading(false); - }); } }, [signer, votingEndsAt, setIsLoading, setTallyData, setPollData, poll.data]); diff --git a/packages/interface/src/contexts/types.ts b/packages/interface/src/contexts/types.ts index 8ceb3ad8..2c9470f6 100644 --- a/packages/interface/src/contexts/types.ts +++ b/packages/interface/src/contexts/types.ts @@ -1,9 +1,10 @@ import { type StandardMerkleTreeData } from "@openzeppelin/merkle-tree/dist/standard"; -import { type TallyData, type IGetPollData, type GatekeeperTrait } from "maci-cli/sdk"; +import { type TallyData, type GatekeeperTrait } from "maci-cli/sdk"; import { type ReactNode } from "react"; import type { PCD } from "@pcd/pcd-types"; import type { Ballot, Vote } from "~/features/ballot/types"; +import type { IPollData } from "~/utils/fetchPoll"; export interface IVoteArgs { voteOptionIndex: bigint; @@ -19,7 +20,7 @@ export interface MaciContextType { isRegistered?: boolean; pollId?: string; error?: string; - pollData?: IGetPollData; + pollData?: IPollData; tallyData?: TallyData; maciPubKey?: string; gatekeeperTrait?: GatekeeperTrait; @@ -41,9 +42,9 @@ export interface BallotContextType { ballot: Ballot; isLoading: boolean; addToBallot: (votes: Vote[], pollId?: string) => void; - removeFromBallot: (projectId: string) => void; + removeFromBallot: (projectIndex: number) => void; deleteBallot: () => void; - ballotContains: (id: string) => Vote | undefined; + ballotContains: (index: number) => Vote | undefined; sumBallot: (votes?: Vote[]) => number; publishBallot: () => void; } diff --git a/packages/interface/src/features/applications/components/ApplicationForm.tsx b/packages/interface/src/features/applications/components/ApplicationForm.tsx index 70b6bcff..49ef343a 100644 --- a/packages/interface/src/features/applications/components/ApplicationForm.tsx +++ b/packages/interface/src/features/applications/components/ApplicationForm.tsx @@ -1,4 +1,3 @@ -import { Transaction } from "@ethereum-attestation-service/eas-sdk"; import { useRouter } from "next/router"; import { useState, useCallback } from "react"; import { useLocalStorage } from "react-use"; @@ -52,13 +51,13 @@ export const ApplicationForm = (): JSX.Element => { }, [step, setStep]); const create = useCreateApplication({ - onSuccess: (data: Transaction) => { + onSuccess: (id: bigint) => { clearDraft(); - router.push(`/applications/confirmation?txHash=${data.tx.hash}`); + router.push(`/applications/confirmation?id=${id.toString()}`); }, - onError: (err: { reason?: string; data?: { message: string } }) => + onError: (err: { message: string }) => toast.error("Application create error", { - description: err.reason ?? err.data?.message, + description: err.message, }), }); diff --git a/packages/interface/src/features/applications/components/ApplicationHeader.tsx b/packages/interface/src/features/applications/components/ApplicationHeader.tsx index 2d341e95..e74f8003 100644 --- a/packages/interface/src/features/applications/components/ApplicationHeader.tsx +++ b/packages/interface/src/features/applications/components/ApplicationHeader.tsx @@ -1,9 +1,9 @@ -import type { Attestation } from "~/utils/types"; +import type { IRequest } from "~/utils/types"; import { SelectAllButton } from "./SelectAllButton"; interface IApplicationHeaderProps { - applications?: Attestation[]; + applications?: IRequest[]; } export const ApplicationHeader = ({ applications = [] }: IApplicationHeaderProps): JSX.Element => ( diff --git a/packages/interface/src/features/applications/components/ApplicationItem.tsx b/packages/interface/src/features/applications/components/ApplicationItem.tsx index d5f7dc8e..a320e926 100644 --- a/packages/interface/src/features/applications/components/ApplicationItem.tsx +++ b/packages/interface/src/features/applications/components/ApplicationItem.tsx @@ -10,41 +10,40 @@ import { useMetadata } from "~/hooks/useMetadata"; import { formatDate } from "~/utils/time"; import type { Application } from "~/features/applications/types"; -import type { Attestation } from "~/utils/types"; +import type { IRecipient, IRecipientContract } from "~/utils/types"; -export interface IApplicationItemProps extends Attestation { +export interface IApplicationItemProps { + index: string; + recipient: IRecipient | IRecipientContract; isApproved?: boolean; isLoading?: boolean; } export const ApplicationItem = ({ - id, + index, recipient, - name, - metadataPtr, - time, isApproved = false, isLoading = false, }: IApplicationItemProps): JSX.Element => { - const metadata = useMetadata(metadataPtr); + const metadata = useMetadata(recipient.metadataUrl); const form = useFormContext(); const { fundingSources = [], profileImageUrl } = metadata.data ?? {}; return ( - +
- +
- {name} + {metadata.data?.name}
@@ -57,7 +56,7 @@ export const ApplicationItem = ({ - {formatDate(time * 1000)} + {metadata.data?.submittedAt ? formatDate(metadata.data.submittedAt) : "N/A"}
diff --git a/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx b/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx index 17d8bfab..29a2df99 100644 --- a/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx +++ b/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx @@ -1,57 +1,37 @@ import Link from "next/link"; -import { useMemo, useEffect, useState } from "react"; +import { useEffect } from "react"; import { FiAlertCircle } from "react-icons/fi"; +import { zeroAddress } from "viem"; import { EmptyState } from "~/components/EmptyState"; import { Button } from "~/components/ui/Button"; import { Form } from "~/components/ui/Form"; import { Heading } from "~/components/ui/Heading"; import { Spinner } from "~/components/ui/Spinner"; -import { useApplications } from "~/features/applications/hooks/useApplications"; -import { fetchApprovedApplications } from "~/utils/fetchAttestationsWithoutCache"; - -import type { Attestation } from "~/utils/types"; +import { useMaci } from "~/contexts/Maci"; +import { useApprovedApplications, usePendingApplications } from "~/features/applications/hooks/useApplications"; import { useApproveApplication } from "../hooks/useApproveApplication"; -import { useApprovedApplications } from "../hooks/useApprovedApplications"; import { ApplicationsToApproveSchema } from "../types"; import { ApplicationHeader } from "./ApplicationHeader"; import { ApplicationItem } from "./ApplicationItem"; import { ApproveButton } from "./ApproveButton"; +/** + * Displays the applications that are pending approval. + */ export const ApplicationsToApprove = (): JSX.Element => { - const applications = useApplications(); - const approved = useApprovedApplications(); - const approve = useApproveApplication(); - const [refetchedData, setRefetchedData] = useState(); - - const approvedById = useMemo( - () => - [...(approved.data ?? []), ...(refetchedData ?? [])].reduce((map, x) => { - map.set(x.refUID, true); - return map; - }, new Map()), - [approved.data, refetchedData], - ); + const { pollData } = useMaci(); - const applicationsToApprove = applications.data?.filter((application) => !approvedById.get(application.id)); + const approved = useApprovedApplications(pollData?.registry ?? zeroAddress); + const pending = usePendingApplications(pollData?.registry ?? zeroAddress); + const approve = useApproveApplication(); useEffect(() => { - const fetchData = async () => { - const ret = await fetchApprovedApplications(); - setRefetchedData(ret); - }; - - /// delay refetch data for 5 seconds - const timeout = setTimeout(() => { - fetchData(); - }, 5000); - - return () => { - clearTimeout(timeout); - }; - }, [approve.isPending, approve.isSuccess]); + approved.refetch().catch(); + pending.refetch().catch(); + }, [approve.isSuccess, approve.isPending, approve.isError]); return (
@@ -61,7 +41,7 @@ export const ApplicationsToApprove = (): JSX.Element => {

- Select the applications you want to approve. You must be a configured admin to approve applications. + Select the applications you want to approve. You must be an admin to be able to approve applications.

@@ -70,7 +50,7 @@ export const ApplicationsToApprove = (): JSX.Element => { Newly submitted applications can take 10 minutes to show up.

-
{`${applications.data?.length} applications found`}
+
{`${(pending.data?.length ?? 0) + (approved.data?.length ?? 0)} applications found`}
{ approve.mutate(values.selected); }} > - {applications.isLoading && ( + {pending.isLoading && (
)} - {!applications.isLoading && !applications.data?.length ? ( - + {!pending.isLoading && !pending.data?.length ? ( + @@ -97,15 +77,14 @@ export const ApplicationsToApprove = (): JSX.Element => {
- + + + {pending.data?.map((item) => ( + + ))} - {applications.data?.map((item) => ( - + {approved.data?.map((item) => ( + ))}
diff --git a/packages/interface/src/features/applications/components/ReviewBar.tsx b/packages/interface/src/features/applications/components/ReviewBar.tsx index 9f3b1e6a..a09c9651 100644 --- a/packages/interface/src/features/applications/components/ReviewBar.tsx +++ b/packages/interface/src/features/applications/components/ReviewBar.tsx @@ -1,17 +1,18 @@ -import { useMemo, useCallback, useState, useEffect } from "react"; +import { useMemo, useCallback, useEffect } from "react"; import { FiAlertCircle } from "react-icons/fi"; +import { zeroAddress } from "viem"; import { StatusBar } from "~/components/StatusBar"; import { Button } from "~/components/ui/Button"; import { Spinner } from "~/components/ui/Spinner"; +import { useMaci } from "~/contexts/Maci"; +import { useProjectById } from "~/features/projects/hooks/useProjects"; import { useIsAdmin } from "~/hooks/useIsAdmin"; import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork"; -import { fetchApprovedApplications } from "~/utils/fetchAttestationsWithoutCache"; - -import type { Attestation } from "~/utils/types"; +import { IRecipient } from "~/utils/types"; +import { useApplicationByProjectId } from "../hooks/useApplications"; import { useApproveApplication } from "../hooks/useApproveApplication"; -import { useApprovedApplications } from "../hooks/useApprovedApplications"; interface IReviewBarProps { projectId: string; @@ -20,44 +21,42 @@ interface IReviewBarProps { export const ReviewBar = ({ projectId }: IReviewBarProps): JSX.Element => { const isAdmin = useIsAdmin(); const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork(); + const { pollData } = useMaci(); - const rawReturn = useApprovedApplications([projectId]); - const [refetchedData, setRefetchedData] = useState(); + const project = useProjectById(projectId, pollData?.registry ?? zeroAddress); + const application = useApplicationByProjectId(projectId, pollData?.registry ?? zeroAddress); + const approve = useApproveApplication(); - const approved = useMemo( - () => (rawReturn.data && rawReturn.data.length > 0) || (refetchedData && refetchedData.length > 0), - [rawReturn.data, refetchedData], - ); + // determine whether the project is approved ornot + const isApproved = useMemo(() => { + if (project.data && (project.data as unknown as IRecipient).initialized) { + return true; + } - const approve = useApproveApplication(); + return false; + }, [project.data, approve.isSuccess, approve.isError]); + // approve the application const onClick = useCallback(() => { - approve.mutate([projectId]); - }, [approve, projectId]); + if (!application.data) { + return; + } + approve.mutate([application.data.index]); + }, [approve, application.data]); + // refetch the application and project data when the approve mutation is successful or pending useEffect(() => { - const fetchData = async () => { - const ret = await fetchApprovedApplications([projectId]); - setRefetchedData(ret); - }; - - /// delay refetch data for 5 seconds - const timeout = setTimeout(() => { - fetchData(); - }, 5000); - - return () => { - clearTimeout(timeout); - }; - }, [approve.isPending, approve.isSuccess, projectId]); + application.refetch().catch(); + project.refetch().catch(); + }, [approve.isSuccess, approve.isPending]); return (
- {approved && } + {isApproved && } - {!approved && isAdmin && } + {!isApproved && isAdmin && } - {!approved && !isAdmin && ( + {!isApproved && !isAdmin && ( @@ -72,7 +71,7 @@ export const ReviewBar = ({ projectId }: IReviewBarProps): JSX.Element => { /> )} - {isAdmin && !approved && ( + {isAdmin && !isApproved && (
@@ -73,6 +65,6 @@ export const SubmitBallotButton = (): JSX.Element => { title="exceed initial voice credits" onOpenChange={setOpen} /> - +
); }; diff --git a/packages/interface/src/features/ballot/types/index.ts b/packages/interface/src/features/ballot/types/index.ts index 0d664c91..1c91bc2f 100644 --- a/packages/interface/src/features/ballot/types/index.ts +++ b/packages/interface/src/features/ballot/types/index.ts @@ -2,6 +2,7 @@ import { z } from "zod"; export const VoteSchema = z.object({ projectId: z.string(), + projectIndex: z.number().min(0), amount: z.number().min(0), }); diff --git a/packages/interface/src/features/projects/components/ProjectAvatar.tsx b/packages/interface/src/features/projects/components/ProjectAvatar.tsx index 22bba644..bbcb595d 100644 --- a/packages/interface/src/features/projects/components/ProjectAvatar.tsx +++ b/packages/interface/src/features/projects/components/ProjectAvatar.tsx @@ -1,21 +1,11 @@ import { type ComponentProps } from "react"; -import { type Address } from "viem"; import { Avatar } from "~/components/ui/Avatar"; -import { useProfileWithMetadata } from "~/hooks/useProfile"; export interface IProjectAvatarProps extends ComponentProps { - profileId?: Address; url?: string; } -export const ProjectAvatar = ({ - profileId = undefined, - url = undefined, - ...rest -}: IProjectAvatarProps): JSX.Element => { - const profile = useProfileWithMetadata(profileId); - const { profileImageUrl } = profile.data ?? {}; - - return ; -}; +export const ProjectAvatar = ({ url = undefined, ...rest }: IProjectAvatarProps): JSX.Element => ( + +); diff --git a/packages/interface/src/features/projects/components/ProjectBanner.tsx b/packages/interface/src/features/projects/components/ProjectBanner.tsx index c70292cf..caa6d3a5 100644 --- a/packages/interface/src/features/projects/components/ProjectBanner.tsx +++ b/packages/interface/src/features/projects/components/ProjectBanner.tsx @@ -1,25 +1,13 @@ import { type ComponentProps } from "react"; -import { type Address } from "viem"; import { Banner } from "~/components/ui/Banner"; -import { useProfileWithMetadata } from "~/hooks/useProfile"; export interface IProjectBannerProps extends ComponentProps { - profileId?: Address; url?: string; } -export const ProjectBanner = ({ - profileId = undefined, - url = undefined, - ...rest -}: IProjectBannerProps): JSX.Element => { - const profile = useProfileWithMetadata(profileId); - const { profileImageUrl, bannerImageUrl } = profile.data ?? {}; - - return ( -
- -
- ); -}; +export const ProjectBanner = ({ url = undefined, ...rest }: IProjectBannerProps): JSX.Element => ( +
+ +
+); diff --git a/packages/interface/src/features/projects/components/ProjectContributions.tsx b/packages/interface/src/features/projects/components/ProjectContributions.tsx index 7e8b1a62..70dab902 100644 --- a/packages/interface/src/features/projects/components/ProjectContributions.tsx +++ b/packages/interface/src/features/projects/components/ProjectContributions.tsx @@ -13,7 +13,7 @@ interface IProjectContributionsProps { } const ProjectContributions = ({ isLoading, project = undefined }: IProjectContributionsProps): JSX.Element => ( - <> +
Contributions @@ -34,7 +34,7 @@ const ProjectContributions = ({ isLoading, project = undefined }: IProjectContri OTHER: Globe, }[link.type]; return ( - <> +
{createElement(icon ?? "div", { className: "w-4 h-4 mt-1", })} @@ -42,13 +42,13 @@ const ProjectContributions = ({ isLoading, project = undefined }: IProjectContri
{link.description}
- +
); }} />
- +
); export default ProjectContributions; diff --git a/packages/interface/src/features/projects/components/ProjectDetails.tsx b/packages/interface/src/features/projects/components/ProjectDetails.tsx index f640620e..ad69fba9 100644 --- a/packages/interface/src/features/projects/components/ProjectDetails.tsx +++ b/packages/interface/src/features/projects/components/ProjectDetails.tsx @@ -8,7 +8,7 @@ import { VotingWidget } from "~/features/projects/components/VotingWidget"; import { useAppState } from "~/utils/state"; import { EAppState } from "~/utils/types"; -import type { Attestation } from "~/utils/types"; +import type { IRecipient } from "~/utils/types"; import { useProjectMetadata } from "../hooks/useProjects"; @@ -17,16 +17,11 @@ import { ProjectDescriptionSection } from "./ProjectDescriptionSection"; export interface IProjectDetailsProps { action?: ReactNode; - projectId?: string; - attestation?: Attestation; + project: IRecipient; } -const ProjectDetails = ({ - projectId = "", - attestation = undefined, - action = undefined, -}: IProjectDetailsProps): JSX.Element => { - const metadata = useProjectMetadata(attestation?.metadataPtr); +const ProjectDetails = ({ project, action = undefined }: IProjectDetailsProps): JSX.Element => { + const metadata = useProjectMetadata(project.metadataUrl); const { bio, websiteUrl, payoutAddress, github, twitter, fundingSources, profileImageUrl, bannerImageUrl } = metadata.data ?? {}; @@ -36,7 +31,7 @@ const ProjectDetails = ({ return (
- +
@@ -49,10 +44,12 @@ const ProjectDetails = ({
- {attestation?.name} + {metadata.data?.name} - {appState === EAppState.VOTING && } + {appState === EAppState.VOTING && ( + + )}
diff --git a/packages/interface/src/features/projects/components/ProjectItem.tsx b/packages/interface/src/features/projects/components/ProjectItem.tsx index 9af1dd0f..521e8d3c 100644 --- a/packages/interface/src/features/projects/components/ProjectItem.tsx +++ b/packages/interface/src/features/projects/components/ProjectItem.tsx @@ -8,7 +8,7 @@ import { formatNumber } from "~/utils/formatNumber"; import { useAppState } from "~/utils/state"; import { EAppState } from "~/utils/types"; -import type { Attestation } from "~/utils/types"; +import type { IRecipient } from "~/utils/types"; import { useProjectMetadata } from "../hooks/useProjects"; import { EProjectState } from "../types"; @@ -18,25 +18,25 @@ import { ProjectAvatar } from "./ProjectAvatar"; import { ProjectBanner } from "./ProjectBanner"; export interface IProjectItemProps { - attestation: Attestation; + recipient: IRecipient; isLoading: boolean; state?: EProjectState; action?: (e: Event) => void; } export const ProjectItem = ({ - attestation, + recipient, isLoading, state = undefined, action = undefined, }: IProjectItemProps): JSX.Element => { - const metadata = useProjectMetadata(attestation.metadataPtr); + const metadata = useProjectMetadata(recipient.metadataUrl); const appState = useAppState(); return (
@@ -46,7 +46,7 @@ export const ProjectItem = ({
- {attestation.name} + {metadata.data?.name}
diff --git a/packages/interface/src/features/projects/components/Projects.tsx b/packages/interface/src/features/projects/components/Projects.tsx index d8af65d8..ca709fa3 100644 --- a/packages/interface/src/features/projects/components/Projects.tsx +++ b/packages/interface/src/features/projects/components/Projects.tsx @@ -2,8 +2,8 @@ import clsx from "clsx"; import Link from "next/link"; import { useCallback } from "react"; import { FiAlertCircle } from "react-icons/fi"; +import { zeroAddress } from "viem"; -import { InfiniteLoading } from "~/components/InfiniteLoading"; import { SortFilter } from "~/components/SortFilter"; import { StatusBar } from "~/components/StatusBar"; import { Heading } from "~/components/ui/Heading"; @@ -18,26 +18,33 @@ import { EProjectState } from "../types"; import { ProjectItem, ProjectItemAwarded } from "./ProjectItem"; +const columnMap = { + 2: "grid-cols-1 md:grid-cols-2", + 3: "sm:grid-cols-2 lg:grid-cols-3", +} as const; + export const Projects = (): JSX.Element => { const appState = useAppState(); - const projects = useSearchProjects({ needApproval: appState !== EAppState.APPLICATION }); const { pollData, pollId, isRegistered } = useMaci(); + const projects = useSearchProjects({ search: "", registryAddress: pollData?.registry ?? zeroAddress }); + const { addToBallot, removeFromBallot, ballotContains, ballot } = useBallot(); const results = useResults(pollData); const handleAction = useCallback( - (projectId: string) => (e: Event) => { + (projectIndex: number, projectId: string) => (e: Event) => { e.preventDefault(); if (!pollId) { return; } - if (!ballotContains(projectId)) { + if (!ballotContains(projectIndex)) { addToBallot( [ { + projectIndex, projectId, amount: 0, }, @@ -45,20 +52,20 @@ export const Projects = (): JSX.Element => { pollId, ); } else { - removeFromBallot(projectId); + removeFromBallot(projectIndex); } }, [ballotContains, addToBallot, removeFromBallot, pollId], ); - const defineState = (projectId: string): EProjectState => { + const defineState = (projectIndex: number): EProjectState => { if (!isRegistered) { return EProjectState.UNREGISTERED; } - if (ballotContains(projectId) && ballot.published && !ballot.edited) { + if (ballotContains(projectIndex) && ballot.published && !ballot.edited) { return EProjectState.SUBMITTED; } - if (ballotContains(projectId)) { + if (ballotContains(projectIndex)) { return EProjectState.ADDED; } return EProjectState.DEFAULT; @@ -100,12 +107,11 @@ export const Projects = (): JSX.Element => {
- ( +
+ {projects.data?.map((item) => ( {!results.isLoading && appState === EAppState.RESULTS ? ( @@ -113,14 +119,14 @@ export const Projects = (): JSX.Element => { ) : null} - )} - /> + ))} +
); }; diff --git a/packages/interface/src/features/projects/components/ProjectsResults.tsx b/packages/interface/src/features/projects/components/ProjectsResults.tsx index 6e6ba5bc..6f8a1322 100644 --- a/packages/interface/src/features/projects/components/ProjectsResults.tsx +++ b/packages/interface/src/features/projects/components/ProjectsResults.tsx @@ -39,8 +39,8 @@ export const ProjectsResults = (): JSX.Element => { diff --git a/packages/interface/src/features/projects/components/VotingWidget.tsx b/packages/interface/src/features/projects/components/VotingWidget.tsx index fab111d4..84ff5687 100644 --- a/packages/interface/src/features/projects/components/VotingWidget.tsx +++ b/packages/interface/src/features/projects/components/VotingWidget.tsx @@ -11,12 +11,13 @@ import { EButtonState } from "../types"; interface IVotingWidgetProps { projectId: string; + projectIndex: number; } -export const VotingWidget = ({ projectId }: IVotingWidgetProps): JSX.Element => { +export const VotingWidget = ({ projectIndex, projectId }: IVotingWidgetProps): JSX.Element => { const { pollId, initialVoiceCredits } = useMaci(); const { ballotContains, removeFromBallot, addToBallot } = useBallot(); - const projectBallot = useMemo(() => ballotContains(projectId), [ballotContains, projectId]); + const projectBallot = useMemo(() => ballotContains(projectIndex), [ballotContains, projectId]); const projectIncluded = useMemo(() => !!projectBallot, [projectBallot]); const [amount, setAmount] = useState(projectBallot?.amount); @@ -32,10 +33,10 @@ export const VotingWidget = ({ projectId }: IVotingWidgetProps): JSX.Element => ); const handleRemove = useCallback(() => { - removeFromBallot(projectId); + removeFromBallot(projectIndex); setAmount(undefined); setButtonState(0); - }, [projectId, removeFromBallot]); + }, [projectIndex, removeFromBallot]); const handleInput = (e: ChangeEvent) => { setAmount(e.target.value as unknown as number); @@ -50,7 +51,7 @@ export const VotingWidget = ({ projectId }: IVotingWidgetProps): JSX.Element => return; } - addToBallot([{ projectId, amount }], pollId); + addToBallot([{ projectId, projectIndex, amount }], pollId); if (buttonState === EButtonState.DEFAULT) { setButtonState(EButtonState.ADDED); } else { diff --git a/packages/interface/src/features/projects/hooks/useProjects.ts b/packages/interface/src/features/projects/hooks/useProjects.ts index 8dc1fdee..6a661df1 100644 --- a/packages/interface/src/features/projects/hooks/useProjects.ts +++ b/packages/interface/src/features/projects/hooks/useProjects.ts @@ -1,64 +1,42 @@ -import { useMemo } from "react"; +import { Chain, Hex } from "viem"; import { type Application } from "~/features/applications/types"; -import { useFilter } from "~/features/filter/hooks/useFilter"; -import { type Filter } from "~/features/filter/types"; import { useMetadata } from "~/hooks/useMetadata"; import { api } from "~/utils/api"; -import type { UseTRPCInfiniteQueryResult, UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { Ballot } from "~/features/ballot/types"; -import type { Attestation } from "~/utils/types"; +import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; +import type { IRecipient } from "~/utils/types"; interface IUseSearchProjectsProps { - filterOverride?: Partial; - needApproval?: boolean; + search: string; + registryAddress: string; } -export function useProjectById(id: string): UseTRPCQueryResult { - return api.projects.get.useQuery({ ids: [id] }, { enabled: Boolean(id) }); +export function useProjectById(id: string, registryAddress: string): UseTRPCQueryResult { + return api.projects.get.useQuery({ ids: [id], registryAddress }, { enabled: Boolean(id) }); } -export function useProjectsById(ids: string[]): UseTRPCQueryResult { - return api.projects.get.useQuery({ ids }, { enabled: Boolean(ids.length) }); +export function useProjectsById(ids: string[], registryAddress: string): UseTRPCQueryResult { + return api.projects.get.useQuery({ ids, registryAddress }, { enabled: Boolean(ids.length) }); } -const seed = 0; export function useSearchProjects({ - filterOverride = {}, - needApproval = true, -}: IUseSearchProjectsProps): UseTRPCInfiniteQueryResult { - const { ...filter } = useFilter(); - - return api.projects.search.useInfiniteQuery( - { seed, ...filter, ...filterOverride, needApproval }, - { - getNextPageParam: (_, pages) => pages.length, - }, - ); -} - -export function useProjectIdMapping(ballot: Ballot): Record { - const { data } = api.projects.allApproved.useQuery(); - - const projectIndices = useMemo( - () => - ballot.votes.reduce>((acc, { projectId }) => { - const index = data?.findIndex((attestation) => attestation.id.toLowerCase() === projectId.toLowerCase()); - acc[projectId] = index ?? -1; - - return acc; - }, {}), - [data, ballot], - ); - - return projectIndices; + search, + registryAddress, +}: IUseSearchProjectsProps): UseTRPCQueryResult { + return api.projects.search.useQuery({ search, registryAddress }); } export function useProjectMetadata(metadataPtr?: string): UseTRPCQueryResult { return useMetadata(metadataPtr); } -export function useProjectCount(): UseTRPCQueryResult<{ count: number }, unknown> { - return api.projects.count.useQuery(); +export function useProjectCount({ + chain, + registryAddress, +}: { + chain: Chain; + registryAddress: Hex; +}): UseTRPCQueryResult<{ count: number }, unknown> { + return api.projects.count.useQuery({ chain, registryAddress }); } diff --git a/packages/interface/src/features/projects/hooks/useSelectProjects.ts b/packages/interface/src/features/projects/hooks/useSelectProjects.ts deleted file mode 100644 index b20d3f8e..00000000 --- a/packages/interface/src/features/projects/hooks/useSelectProjects.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useMemo, useState } from "react"; - -import { useBallot } from "~/contexts/Ballot"; -import { useMaci } from "~/contexts/Maci"; - -export interface IUseSelectProjectsReturn { - count: number; - add: () => void; - reset: () => void; - toggle: (id: string) => void; - getState: (id: string) => 0 | 1 | 2; -} - -export function useSelectProjects(): IUseSelectProjectsReturn { - const { addToBallot, ballotContains } = useBallot(); - const { pollId } = useMaci(); - - const [selected, setSelected] = useState>({}); - - const toAdd = useMemo( - () => - Object.keys(selected) - .filter((id) => selected[id]) - .map((projectId) => ({ projectId, amount: 0 })), - [selected], - ); - - return { - count: toAdd.length, - add: () => { - addToBallot(toAdd, pollId); - setSelected({}); - }, - reset: () => { - setSelected({}); - }, - toggle: (id: string) => { - if (!id) { - return; - } - - setSelected((s) => ({ ...s, [id]: !selected[id] })); - }, - getState: (id: string) => { - if (ballotContains(id)) { - return 2; - } - - return selected[id] ? 1 : 0; - }, - }; -} diff --git a/packages/interface/src/hooks/useMetadata.ts b/packages/interface/src/hooks/useMetadata.ts index 0c122010..ff23c001 100644 --- a/packages/interface/src/hooks/useMetadata.ts +++ b/packages/interface/src/hooks/useMetadata.ts @@ -8,6 +8,11 @@ export function useMetadata(metadataPtr?: string): UseTRPCQueryResult | File> { return useMutation({ mutationFn: async (data: Record | File) => { diff --git a/packages/interface/src/hooks/useProfile.ts b/packages/interface/src/hooks/useProfile.ts deleted file mode 100644 index a5072186..00000000 --- a/packages/interface/src/hooks/useProfile.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { type Address } from "viem"; - -import { api } from "~/utils/api"; - -import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { Attestation } from "~/utils/types"; - -import { useMetadata } from "./useMetadata"; - -export function useProfile(id?: Address): UseTRPCQueryResult { - return api.profile.get.useQuery({ id: String(id) }, { enabled: Boolean(id) }); -} - -export function useProfileWithMetadata( - id?: Address, -): UseTRPCQueryResult<{ profileImageUrl: string; bannerImageUrl: string }, unknown> { - const profile = useProfile(id); - - return useMetadata<{ profileImageUrl: string; bannerImageUrl: string }>(profile.data?.metadataPtr); -} diff --git a/packages/interface/src/hooks/useRegistry.ts b/packages/interface/src/hooks/useRegistry.ts new file mode 100644 index 00000000..75b7273e --- /dev/null +++ b/packages/interface/src/hooks/useRegistry.ts @@ -0,0 +1,52 @@ +import { DefaultError, useMutation, UseMutationResult } from "@tanstack/react-query"; +import { useAccount } from "wagmi"; + +import { approveApplication, submitApplication } from "~/utils/registry"; + +import type { Hex, TransactionReceipt } from "viem"; + +/** + * Submit an application for approval + * + * @returns whether the submission was successful + */ +export function useSubmitApplication(): UseMutationResult< + TransactionReceipt, + DefaultError, + { refUID?: string | undefined; metadataUrl: string; registryAddress: Hex; recipient: `0x${string}` } +> { + const { chain } = useAccount(); + + return useMutation({ + mutationFn: async ({ + refUID, + metadataUrl, + registryAddress, + recipient, + }: { + refUID?: string; + metadataUrl: string; + registryAddress: Hex; + recipient: Hex; + }) => submitApplication(chain!, metadataUrl, registryAddress, recipient, refUID), + }); +} + +/** + * Approve an application for a given refUID + * + * @returns whether the approval was successful + */ +export function useSubmitApproval(): UseMutationResult { + const { chain } = useAccount(); + + return useMutation({ + mutationFn: async ({ refUID }: { refUID: string }) => { + if (!chain) { + throw new Error("Connect wallet first"); + } + + return approveApplication(chain, refUID); + }, + }); +} diff --git a/packages/interface/src/hooks/useResults.ts b/packages/interface/src/hooks/useResults.ts index a807cd25..724b68e1 100644 --- a/packages/interface/src/hooks/useResults.ts +++ b/packages/interface/src/hooks/useResults.ts @@ -1,44 +1,38 @@ -import { config } from "~/config"; +import { zeroAddress } from "viem"; + import { api } from "~/utils/api"; import { useAppState } from "~/utils/state"; import { EAppState } from "~/utils/types"; +import type { IPollData } from "~/utils/fetchPoll"; import type { UseTRPCInfiniteQueryResult, UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { IGetPollData } from "maci-cli/sdk"; -import type { Attestation } from "~/utils/types"; +import type { IRecipient } from "~/utils/types"; export function useResults( - pollData?: IGetPollData, + pollData?: IPollData, ): UseTRPCQueryResult<{ averageVotes: number; projects: Record }, unknown> { const appState = useAppState(); - return api.results.votes.useQuery({ pollId: pollData?.id.toString() }, { enabled: appState === EAppState.RESULTS }); + return api.results.votes.useQuery({ pollId: pollData?.id.toString(), registryAddress: pollData?.registry ?? zeroAddress }, { enabled: appState === EAppState.RESULTS }); } -const seed = 0; -export function useProjectsResults( - pollData?: IGetPollData, -): UseTRPCInfiniteQueryResult { +export function useProjectsResults(pollData?: IPollData): UseTRPCInfiniteQueryResult { return api.results.projects.useInfiniteQuery( - { limit: config.pageSize, seed, pollId: pollData?.id.toString() }, + { registryAddress: pollData?.registry ?? zeroAddress }, { getNextPageParam: (_, pages) => pages.length, }, ); } -export function useProjectCount(): UseTRPCQueryResult<{ count: number }, unknown> { - return api.projects.count.useQuery(); -} - export function useProjectResults( id: string, - pollData?: IGetPollData, + pollData?: IPollData, ): UseTRPCQueryResult<{ amount: number }, unknown> { const appState = useAppState(); return api.results.project.useQuery( - { id, pollId: pollData?.id.toString() }, + { id, pollId: pollData?.id.toString(), registryAddress: pollData?.registry ?? zeroAddress }, { enabled: appState === EAppState.RESULTS }, ); } diff --git a/packages/interface/src/pages/applications/confirmation.tsx b/packages/interface/src/pages/applications/confirmation.tsx index 7c9817fc..a04259b4 100644 --- a/packages/interface/src/pages/applications/confirmation.tsx +++ b/packages/interface/src/pages/applications/confirmation.tsx @@ -2,23 +2,27 @@ import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { useMemo } from "react"; import { FiAlertCircle } from "react-icons/fi"; +import { zeroAddress } from "viem"; import { Alert } from "~/components/ui/Alert"; import { Heading } from "~/components/ui/Heading"; -import { useApplicationByTxHash } from "~/features/applications/hooks/useApplicationByTxHash"; +import { useMaci } from "~/contexts/Maci"; +import { useApplicationById } from "~/features/applications/hooks/useApplicationId"; import { ProjectItem } from "~/features/projects/components/ProjectItem"; import { Layout } from "~/layouts/DefaultLayout"; import { useAppState } from "~/utils/state"; -import { EAppState } from "~/utils/types"; +import { EAppState, IRecipient } from "~/utils/types"; const ConfirmProjectPage = (): JSX.Element => { const state = useAppState(); + const { pollData } = useMaci(); + const searchParams = useSearchParams(); - const txHash = useMemo(() => searchParams.get("txHash"), [searchParams]); - const project = useApplicationByTxHash(txHash ?? ""); + const applicationId = useMemo(() => searchParams.get("id"), [searchParams]); + const application = useApplicationById(pollData?.registry ?? zeroAddress, applicationId ?? ""); - const attestation = useMemo(() => project.data, [project]); + const project = useMemo(() => application.data, [application]); return ( @@ -40,9 +44,9 @@ const ConfirmProjectPage = (): JSX.Element => { {state !== EAppState.APPLICATION && } - {attestation && ( - - + {project && ( + + )}
diff --git a/packages/interface/src/pages/ballot/index.tsx b/packages/interface/src/pages/ballot/index.tsx index 9957677c..524d8209 100644 --- a/packages/interface/src/pages/ballot/index.tsx +++ b/packages/interface/src/pages/ballot/index.tsx @@ -38,7 +38,7 @@ const ClearBallot = (): JSX.Element | null => { }; return ( - <> +
); }; +/** + * Empty ballot component + * @returns a component that displays a message and a button to view projects + */ const EmptyBallot = (): JSX.Element => (
@@ -81,6 +85,10 @@ const EmptyBallot = (): JSX.Element => (
); +/** + * Ballot allocation form component + * @returns a component that displays the ballot allocation form + */ const BallotAllocationForm = (): JSX.Element => { const appState = useAppState(); const { ballot, sumBallot } = useBallot(); diff --git a/packages/interface/src/pages/projects/[projectId]/Project.tsx b/packages/interface/src/pages/projects/[projectId]/Project.tsx index f05cd1c7..e073a945 100644 --- a/packages/interface/src/pages/projects/[projectId]/Project.tsx +++ b/packages/interface/src/pages/projects/[projectId]/Project.tsx @@ -1,26 +1,30 @@ import { type GetServerSideProps } from "next"; +import { zeroAddress } from "viem"; +import { useMaci } from "~/contexts/Maci"; import { ReviewBar } from "~/features/applications/components/ReviewBar"; import ProjectDetails from "~/features/projects/components/ProjectDetails"; import { useProjectById } from "~/features/projects/hooks/useProjects"; import { LayoutWithSidebar } from "~/layouts/DefaultLayout"; import { useAppState } from "~/utils/state"; -import { EAppState } from "~/utils/types"; +import { EAppState, IRecipient } from "~/utils/types"; export interface IProjectDetailsProps { projectId?: string; } const ProjectDetailsPage = ({ projectId = "" }: IProjectDetailsProps): JSX.Element => { - const projects = useProjectById(projectId); - const { name } = projects.data?.[0] ?? {}; + const { pollData } = useMaci(); + + const projects = useProjectById(projectId, pollData?.registry ?? zeroAddress); + const appState = useAppState(); return ( - + {appState === EAppState.APPLICATION && } - + {projects.data && } ); }; diff --git a/packages/interface/src/pages/stats/index.tsx b/packages/interface/src/pages/stats/index.tsx index f28e9274..eb28f0a0 100644 --- a/packages/interface/src/pages/stats/index.tsx +++ b/packages/interface/src/pages/stats/index.tsx @@ -1,6 +1,7 @@ import { differenceInDays } from "date-fns"; import dynamic from "next/dynamic"; -import { useMemo, type PropsWithChildren } from "react"; +import { useEffect, useMemo, type PropsWithChildren } from "react"; +import { zeroAddress } from "viem"; import { useAccount } from "wagmi"; import { ConnectButton } from "~/components/ConnectButton"; @@ -8,7 +9,8 @@ import { Alert } from "~/components/ui/Alert"; import { Heading } from "~/components/ui/Heading"; import { config } from "~/config"; import { useMaci } from "~/contexts/Maci"; -import { useProjectCount, useProjectsResults, useResults } from "~/hooks/useResults"; +import { useProjectCount } from "~/features/projects/hooks/useProjects"; +import { useProjectsResults, useResults } from "~/hooks/useResults"; import { Layout } from "~/layouts/DefaultLayout"; import { formatNumber } from "~/utils/formatNumber"; import { useAppState } from "~/utils/state"; @@ -28,17 +30,22 @@ const Stat = ({ title, children = null }: PropsWithChildren<{ title: string }>) const Stats = () => { const { isLoading, pollData } = useMaci(); + const { chain, isConnected } = useAccount(); const results = useResults(pollData); - const count = useProjectCount(); + + const count = useProjectCount({ chain: chain!, registryAddress: pollData?.registry ?? zeroAddress }); const { data: projectsResults } = useProjectsResults(pollData); - const { isConnected } = useAccount(); const { averageVotes, projects = {} } = results.data ?? {}; + useEffect(() => { + + }, [projectsResults]) + const chartData = useMemo(() => { const data = (projectsResults?.pages[0] ?? []) .map((project) => ({ - x: project.name, + x: project.index, y: projects[project.id]?.votes, })) .slice(0, 15); @@ -75,7 +82,7 @@ const Stats = () => {
- {count.data?.count} + {count.data?.count.toString()} {Object.keys(projects).length} diff --git a/packages/interface/src/server/api/routers/applications.ts b/packages/interface/src/server/api/routers/applications.ts index c1c90d76..dfe3d57c 100644 --- a/packages/interface/src/server/api/routers/applications.ts +++ b/packages/interface/src/server/api/routers/applications.ts @@ -1,31 +1,24 @@ import { z } from "zod"; -import { config, eas } from "~/config"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { fetchAttestations } from "~/utils/fetchAttestations"; -import { createDataFilter } from "~/utils/fetchAttestationsUtils"; - -export const FilterSchema = z.object({ - limit: z.number().default(3 * 8), - cursor: z.number().default(0), -}); +import { + fetchApplicationById, + fetchApplicationByProjectId, + fetchApprovedApplications, + fetchPendingApplications, +} from "~/utils/fetchApplications"; export const applicationsRouter = createTRPCRouter({ - approvals: publicProcedure.input(z.object({ ids: z.array(z.string()).optional() })).query(async ({ input }) => - fetchAttestations([eas.schemas.approval], { - where: { - attester: { equals: config.admin }, - refUID: input.ids ? { in: input.ids } : undefined, - AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", config.roundId)], - }, - }), - ), - list: publicProcedure.input(FilterSchema).query(async () => - fetchAttestations([eas.schemas.metadata], { - orderBy: [{ time: "desc" }], - where: { - AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", config.roundId)], - }, - }), - ), + approvals: publicProcedure + .input(z.object({ registryAddress: z.string() })) + .query(async ({ input }) => fetchApprovedApplications(input.registryAddress)), + pending: publicProcedure + .input(z.object({ registryAddress: z.string() })) + .query(async ({ input }) => fetchPendingApplications(input.registryAddress)), + getById: publicProcedure + .input(z.object({ registryAddress: z.string(), id: z.string() })) + .query(async ({ input }) => fetchApplicationById(input.registryAddress, input.id)), + getByProjectId: publicProcedure + .input(z.object({ registryAddress: z.string(), projectId: z.string() })) + .query(async ({ input }) => fetchApplicationByProjectId(input.projectId, input.registryAddress)), }); diff --git a/packages/interface/src/server/api/routers/projects.ts b/packages/interface/src/server/api/routers/projects.ts index fbb5d2e0..a225216a 100644 --- a/packages/interface/src/server/api/routers/projects.ts +++ b/packages/interface/src/server/api/routers/projects.ts @@ -1,166 +1,37 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod"; -import { config, eas } from "~/config"; -import { type Filter, FilterSchema, OrderBy, SortOrder } from "~/features/filter/types"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { fetchAttestations } from "~/utils/fetchAttestations"; -import { createDataFilter, createSearchFilter } from "~/utils/fetchAttestationsUtils"; -import { fetchMetadata } from "~/utils/fetchMetadata"; +import { fetchApprovedProjects, fetchProjects } from "~/utils/fetchProjects"; +import { getProjectCount } from "~/utils/registry"; -import type { Attestation } from "~/utils/types"; +import type { Chain, Hex } from "viem"; export const projectsRouter = createTRPCRouter({ - count: publicProcedure.query(async () => - fetchAttestations([eas.schemas.approval], { - where: { - attester: { equals: config.admin }, - AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", config.roundId)], - }, - }).then((attestations = []) => - // Handle multiple approvals of an application - group by refUID - ({ - count: Object.keys(attestations.reduce((acc, x) => ({ ...acc, [x.refUID]: true }), {})).length, - }), + count: publicProcedure + .input(z.object({ chain: z.custom(), registryAddress: z.string() })) + .query(async ({ input }) => + getProjectCount(input.chain, input.registryAddress as Hex).then((count) => ({ + count, + })), ), - ), - get: publicProcedure.input(z.object({ ids: z.array(z.string()) })).query(async ({ input: { ids } }) => { - if (!ids.length) { - throw new TRPCError({ code: "NOT_FOUND" }); - } + get: publicProcedure + .input(z.object({ ids: z.array(z.string()), registryAddress: z.string() })) + .query(async ({ input: { ids, registryAddress } }) => { + if (!ids.length) { + throw new TRPCError({ code: "NOT_FOUND" }); + } - return fetchAttestations([eas.schemas.metadata], { - where: { id: { in: ids } }, - }); - }), + const projects = await fetchProjects(registryAddress); - getByTransactionId: publicProcedure - .input(z.object({ transactionId: z.string() })) - .query(async ({ input: { transactionId } }) => - fetchAttestations([eas.schemas.metadata], { - where: { txid: { equals: transactionId } }, - }).then((applications) => applications[0]), - ), - - search: publicProcedure.input(FilterSchema).query(async ({ input }) => { - const filters = [ - createDataFilter("type", "bytes32", "application"), - createDataFilter("round", "bytes32", config.roundId), - ]; - - if (input.search) { - filters.push(createSearchFilter(input.search)); - } - - if (input.needApproval) { - return fetchAttestations([eas.schemas.approval], { - where: { - attester: { equals: config.admin }, - ...createDataFilter("type", "bytes32", "application"), - }, - }).then((attestations = []) => { - const approvedIds = attestations.map(({ refUID }) => refUID).filter(Boolean); - - return fetchAttestations([eas.schemas.metadata], { - take: input.limit, - skip: input.cursor * input.limit, - orderBy: [createOrderBy(input.orderBy, input.sortOrder)], - where: { - id: { in: approvedIds }, - AND: filters, - }, - }); - }); - } - - return fetchAttestations([eas.schemas.metadata], { - take: input.limit, - skip: input.cursor * input.limit, - orderBy: [createOrderBy(input.orderBy, input.sortOrder)], - where: { - attester: { equals: config.admin }, - AND: filters, - }, - }); - }), - - // Used for distribution to get the projects' payoutAddress - // To get this data we need to fetch all projects and their metadata - payoutAddresses: publicProcedure.input(z.object({ ids: z.array(z.string()) })).query(async ({ input }) => - fetchAttestations([eas.schemas.metadata], { - where: { id: { in: input.ids } }, - }) - .then((attestations) => - Promise.all( - attestations.map((attestation) => - fetchMetadata(attestation.metadataPtr).then((data) => { - const { payoutAddress } = data as unknown as { - payoutAddress: string; - }; - - return { projectId: attestation.id, payoutAddress }; - }), - ), - ), - ) - .then((projects) => projects.reduce((acc, x) => ({ ...acc, [x.projectId]: x.payoutAddress }), {})), - ), + return projects.find((project) => ids.includes(project.id)); + }), - allApproved: publicProcedure.query(async () => { - const filters = [ - createDataFilter("type", "bytes32", "application"), - createDataFilter("round", "bytes32", config.roundId), - ]; - - return fetchAttestations([eas.schemas.approval], { - where: { - attester: { equals: config.admin }, - ...createDataFilter("type", "bytes32", "application"), - }, - }).then((attestations = []) => { - const approvedIds = attestations.map(({ refUID }) => refUID).filter(Boolean); - - return fetchAttestations([eas.schemas.metadata], { - orderBy: [createOrderBy(OrderBy.time, SortOrder.asc)], - where: { - id: { in: approvedIds }, - AND: filters, - }, - }); - }); - }), + search: publicProcedure + .input(z.object({ search: z.string(), registryAddress: z.string() })) + .query(async ({ input }) => { + // get the projects that are approved + return fetchApprovedProjects(input.registryAddress); + }), }); - -export async function getAllApprovedProjects(): Promise { - const filters = [ - createDataFilter("type", "bytes32", "application"), - createDataFilter("round", "bytes32", config.roundId), - ]; - - return fetchAttestations([eas.schemas.approval], { - where: { - attester: { equals: config.admin }, - ...createDataFilter("type", "bytes32", "application"), - }, - }).then((attestations = []) => { - const approvedIds = attestations.map(({ refUID }) => refUID).filter(Boolean); - - return fetchAttestations([eas.schemas.metadata], { - orderBy: [createOrderBy(OrderBy.time, SortOrder.asc)], - where: { - id: { in: approvedIds }, - AND: filters, - }, - }); - }); -} - -function createOrderBy(orderBy: Filter["orderBy"], sortOrder: Filter["sortOrder"]) { - const key = { - time: "time", - name: "decodedDataJson", - }[orderBy]; - - return { [key]: sortOrder }; -} diff --git a/packages/interface/src/server/api/routers/results.ts b/packages/interface/src/server/api/routers/results.ts index cdb876ac..15e42f96 100644 --- a/packages/interface/src/server/api/routers/results.ts +++ b/packages/interface/src/server/api/routers/results.ts @@ -2,49 +2,32 @@ import { TRPCError } from "@trpc/server"; import { type TallyData } from "maci-cli/sdk"; import { z } from "zod"; -import { config, eas } from "~/config"; +import { config } from "~/config"; import { FilterSchema } from "~/features/filter/types"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { fetchAttestations } from "~/utils/fetchAttestations"; - -import { getAllApprovedProjects } from "./projects"; +import { fetchApprovedProjects, fetchProjects } from "~/utils/fetchProjects"; export const resultsRouter = createTRPCRouter({ votes: publicProcedure - .input(z.object({ pollId: z.string().nullish() })) - .query(async ({ input }) => calculateMaciResults(input.pollId)), + .input(z.object({ pollId: z.string().nullish(), registryAddress: z.string() })) + .query(async ({ input }) => calculateMaciResults(input.registryAddress, input.pollId)), project: publicProcedure - .input(z.object({ id: z.string(), pollId: z.string().nullish() })) + .input(z.object({ id: z.string(), pollId: z.string().nullish(), registryAddress: z.string() })) .query(async ({ input }) => { - const { projects } = await calculateMaciResults(input.pollId); + const { projects } = await calculateMaciResults(input.registryAddress, input.pollId); return { amount: projects[input.id]?.votes ?? 0, }; }), - projects: publicProcedure.input(FilterSchema.extend({ pollId: z.string().nullish() })).query(async ({ input }) => { - const { projects } = await calculateMaciResults(input.pollId); - - const sortedIDs = Object.entries(projects) - .sort((a, b) => b[1].votes - a[1].votes) - .map(([id]) => id) - .slice(input.cursor * input.limit, input.cursor * input.limit + input.limit); - - return fetchAttestations([eas.schemas.metadata], { - where: { - id: { in: sortedIDs }, - }, - }).then((attestations) => - // Results aren't returned from EAS in the same order as the `where: { in: sortedIDs }` - // Sort the attestations based on the sorted array - attestations.sort((a, b) => sortedIDs.indexOf(a.id) - sortedIDs.indexOf(b.id)), - ); - }), + projects: publicProcedure + .input(FilterSchema.extend({ pollId: z.string().nullish(), registryAddress: z.string() })) + .query(async ({ input }) => fetchProjects(input.registryAddress)), }); -export async function calculateMaciResults(pollId?: string | null): Promise<{ +export async function calculateMaciResults(registryAddress: string, pollId?: string | null): Promise<{ averageVotes: number; projects: Record; }> { @@ -56,7 +39,7 @@ export async function calculateMaciResults(pollId?: string | null): Promise<{ fetch(`${config.tallyUrl}/tally-${pollId}.json`) .then((res) => res.json() as Promise) .catch(() => undefined), - getAllApprovedProjects(), + fetchApprovedProjects(registryAddress), ]); if (!tallyData) { @@ -83,7 +66,13 @@ export async function calculateMaciResults(pollId?: string | null): Promise<{ }; } -function calculateAverage(votes: number[]) { +/** + * Calculate the average of an array of numbers + * + * @param votes - An array of numbers + * @returns The average of the array + */ +function calculateAverage(votes: number[]): number { if (votes.length === 0) { return 0; } diff --git a/packages/interface/src/utils/fetchApplications.ts b/packages/interface/src/utils/fetchApplications.ts new file mode 100644 index 00000000..465b2e24 --- /dev/null +++ b/packages/interface/src/utils/fetchApplications.ts @@ -0,0 +1,271 @@ +import { config } from "~/config"; + +import type { IRequest } from "./types"; + +export interface GraphQLResponse { + data?: { + requests: IRequest[]; + }; +} + +// Fetch only pending add requests +const PendingRequests = ` + query PendingAddRequests($registryAddress: String!) { + requests(where: { requestType: "Add", status: "Pending", recipient_: { registry: $registryAddress } }) { + index + recipient { + id + metadataUrl + index + initialized + payout + registry { + id + } + } + } + } +`; + +// Fetch only rejected add requests +const RejectedRequests = ` + query RejectedRequests($registryAddress: String!) { + requests(where: { status: "Rejected", requestType: "Add", recipient_: { registry: $registryAddress } }) { + index + recipient { + id + metadataUrl + index + initialized + payout + registry { + id + } + } + } + } +`; + +// Fetch only approved add requests +const ApprovedRequests = ` + query ApprovedRequests($registryAddress: String!) { + requests(where: { status: "Approved", requestType: "Add", recipient_: { registry: $registryAddress } }) { + index + recipient { + id + metadataUrl + index + initialized + payout + registry { + id + } + } + } + } +`; + +const IndividualRequest = ` + query PendingAddRequests($registryAddress: String!, $recipientId: String!) { + requests(where: { requestType: "Add", status: "Pending", recipient_: { + registry: $registryAddress, + id: $recipientId + } + }) { + index + recipient { + id + metadataUrl + index + initialized + payout + registry { + id + } + } + } + } +`; + +const ApplicationById = ` + query ApplicationById($registryAddress: String!, $id: String!) { + requests(where: { recipient_: { registry: $registryAddress, initialized: false }, id: $id, requestType: "Add", status: "Pending" }) { + id + index + recipient { + id + metadataUrl + index + initialized + payout + registry { + id + } + } + } + } +`; + +/** + * Fetch application by project ID + * + * @param projectId - the project ID + * @param registryAddress - the registry ID + */ +export async function fetchApplicationByProjectId(projectId: string, registryAddress: string): Promise { + const response = await fetch(config.maciSubgraphUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: IndividualRequest, + variables: { recipientId: projectId, registryAddress }, + }), + }); + + const result = (await response.json()) as GraphQLResponse; + + if (!result.data) { + throw new Error("No data returned from GraphQL query"); + } + + return result.data.requests[0]!; +} + +/** + * Fetch all pending applications + * + * @returns the pending add requests + */ +export async function fetchPendingApplications(registryAddress: string): Promise { + const response = await fetch(config.maciSubgraphUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: PendingRequests, + variables: { registryAddress }, + }), + }); + + const result = (await response.json()) as GraphQLResponse; + + if (!result.data) { + throw new Error("No data returned from GraphQL query"); + } + + return result.data.requests; +} + +/** + * Fetch applications that have been rejected + * + * @param registryAddress - the registry ID to filter by + * @returns the rejected add requests + */ +export async function fetchRejectedApplications(registryAddress: string): Promise { + const response = await fetch(config.maciSubgraphUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: RejectedRequests, + variables: { registryAddress }, + }), + }); + + const result = (await response.json()) as GraphQLResponse; + + if (!result.data) { + throw new Error("No data returned from GraphQL query"); + } + + return result.data.requests; +} + +/** + * Fetch applications that have been approved + * + * @param registryAddress - the registry ID to filter by + * @returns the approved add requests + */ +export async function fetchApprovedApplications(registryAddress: string): Promise { + const response = await fetch(config.maciSubgraphUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: ApprovedRequests, + variables: { registryAddress }, + }), + }); + + const result = (await response.json()) as GraphQLResponse; + + if (!result.data) { + throw new Error("No data returned from GraphQL query"); + } + + return result.data.requests; +} + +/** + * Fetch all applications + * + * @param registryAddress - the registry ID to filter by + * @returns the add requests + */ +export async function fetchApplications(registryAddress: string): Promise { + const response = await fetch(config.maciSubgraphUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: ApprovedRequests, + variables: { registryAddress }, + }), + }); + + const result = (await response.json()) as GraphQLResponse; + + if (!result.data) { + throw new Error("No data returned from GraphQL query"); + } + + return result.data.requests; +} + +/** + * Fetch application by ID + * + * @param registryAddress - the registry ID + * @param id - the application ID + * @returns the application + */ +export async function fetchApplicationById(registryAddress: string, id: string): Promise { + const response = await fetch(config.maciSubgraphUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: ApplicationById, + variables: { id, registryAddress }, + }), + }); + + const result = (await response.json()) as GraphQLResponse; + + if (!result.data?.requests) { + throw new Error("No application found with this ID"); + } + + const request = result.data.requests[0]!; + + return request; +} diff --git a/packages/interface/src/utils/fetchMetadata.ts b/packages/interface/src/utils/fetchMetadata.ts index 7954f929..d4b72e8a 100644 --- a/packages/interface/src/utils/fetchMetadata.ts +++ b/packages/interface/src/utils/fetchMetadata.ts @@ -4,6 +4,12 @@ import { createCachedFetch } from "./fetch"; const ttl = 2147483647; const cachedFetch = createCachedFetch({ ttl }); +/** + * Fetches project metadata from a given URL. + * + * @param url - The URL to fetch the metadata from. + * @returns The metadata. + */ export async function fetchMetadata(url: string): Promise<{ data: T; error: Error }> { const ipfsGateway = process.env.NEXT_PUBLIC_IPFS_GATEWAY ?? "https://dweb.link/ipfs/"; diff --git a/packages/interface/src/utils/fetchPoll.ts b/packages/interface/src/utils/fetchPoll.ts index 9294d6c1..c975f2c7 100644 --- a/packages/interface/src/utils/fetchPoll.ts +++ b/packages/interface/src/utils/fetchPoll.ts @@ -1,21 +1,13 @@ +import { BigNumberish } from "ethers"; import { IGetPollData } from "maci-cli/sdk"; +import { type Hex, zeroAddress } from "viem"; import { config } from "~/config"; - import { createCachedFetch } from "./fetch"; -const cachedFetch = createCachedFetch({ ttl: 1000 * 60 * 10 }); +import type { Poll } from "./types"; -interface Poll { - pollId: string; - createdAt: string; - duration: string; - stateRoot: string; - messageRoot: string; - numSignups: string; - id: string; - mode: string; -} +const cachedFetch = createCachedFetch({ ttl: 1000 * 60 * 10 }); export interface GraphQLResponse { data?: { @@ -34,11 +26,25 @@ const PollQuery = ` numSignups id mode + initTime + registry { + id + } } } `; -export async function fetchPoll(): Promise { +export interface IPollData extends IGetPollData { + registry: Hex; + initTime: BigNumberish; +} + +/** + * Fetches the poll data from the subgraph + * + * @returns The poll data + */ +export async function fetchPoll(): Promise { const poll = ( await cachedFetch<{ polls: Poll[] }>(config.maciSubgraphUrl, { method: "POST", @@ -57,5 +63,7 @@ export async function fetchPoll(): Promise { numSignups: poll?.numSignups ?? 0, address: poll?.id ?? "", mode: poll?.mode ?? "", + initTime: poll?.initTime ?? 0, + registry: poll?.registry ? (poll.registry.id as Hex) : zeroAddress, }; } diff --git a/packages/interface/src/utils/fetchProjects.ts b/packages/interface/src/utils/fetchProjects.ts new file mode 100644 index 00000000..ce62ac0b --- /dev/null +++ b/packages/interface/src/utils/fetchProjects.ts @@ -0,0 +1,103 @@ +import { config } from "~/config"; + +import { createCachedFetch } from "./fetch"; +import { IRecipient } from "./types"; + +const cachedFetch = createCachedFetch({ ttl: 1000 }); + +export interface GraphQLResponse { + data?: { + recipients: IRecipient[]; + }; +} + +// query to fetch all approved projects +const ApprovedProjects = ` + query Recipients($registryAddress: String) { + recipients (where:{ deleted:false, initialized: true, registry: $registryAddress }) { + id + payout + metadataUrl + index + initialized + registry { + id + } + } + }`; + +const Projects = ` + query Recipients($registryAddress: String) { + recipients (where:{ deleted:false, registry: $registryAddress }) { + id + payout + metadataUrl + index + initialized + registry { + id + } + } + }`; + +/** + * Fetch all projects + * + * @returns the projects + */ +export async function fetchProjects(registryAddress: string): Promise { + const response = await cachedFetch<{ recipients: IRecipient[] }>(config.maciSubgraphUrl, { + method: "POST", + body: JSON.stringify({ + query: Projects, + variables: { registryAddress }, + }), + }).then((resp: GraphQLResponse) => resp.data?.recipients); + + const recipients = response?.map((request) => ({ + id: request.id, + metadataUrl: request.metadataUrl, + payout: request.payout, + initialized: request.initialized, + index: request.index, + })); + + return recipients ?? []; +} + +/** + * Fetch all approved projects + * + * @returns the projects + */ +export async function fetchApprovedProjects(registryAddress: string): Promise { + const response = await cachedFetch<{ recipients: IRecipient[] }>(config.maciSubgraphUrl, { + method: "POST", + body: JSON.stringify({ + query: ApprovedProjects, + variables: { registryAddress }, + }), + }).then((resp: GraphQLResponse) => resp.data?.recipients); + + const recipients = response?.map((request) => ({ + id: request.id, + metadataUrl: request.metadataUrl, + payout: request.payout, + initialized: request.initialized, + index: request.index, + })); + + return recipients ?? []; +} + +/** + * Fetch a project by ID + * + * @param registryAddress - the registry ID + * @param id - the project ID + * @returns the project + */ +export async function fetchProjectById(registryAddress: string, id: string): Promise { + const projects = await fetchProjects(registryAddress); + return projects.find((project) => project.id === id); +} diff --git a/packages/interface/src/utils/registry.ts b/packages/interface/src/utils/registry.ts new file mode 100644 index 00000000..bdbb4887 --- /dev/null +++ b/packages/interface/src/utils/registry.ts @@ -0,0 +1,231 @@ +import { hexlify, randomBytes } from "ethers"; +import { + MACI__factory as MACIFactory, + RegistryManager__factory as RegistryManagerFactory, + BaseRegistry__factory as BaseRegistryFactory, + Poll__factory as PollFactory, +} from "maci-platform-contracts/typechain-types"; +import { Chain, createPublicClient, createWalletClient, custom, Hex, http, TransactionReceipt } from "viem"; + +import { config } from "~/config"; + +import { ERequestStatus, ERequestType, IRequest } from "./types"; + +/** + * Get the registry address + * + * @param chain - The chain to use + * @param pollId - The poll id + * @returns The registry address + */ +export const getRegistryAddress = async (chain: Chain, pollId: bigint): Promise => { + const publicClient = createPublicClient({ + transport: custom(window.ethereum), + chain, + }); + + const pollAddress = ( + (await publicClient.readContract({ + abi: MACIFactory.abi, + address: config.maciAddress as Hex, + functionName: "getPoll", + args: [pollId], + })) as { poll: Hex; messageProcessor: Hex; tally: Hex } + ).poll; + + return (await publicClient.readContract({ + abi: PollFactory.abi, + address: pollAddress, + functionName: "registry", + })) as Hex; +}; + +/** + * Get the registry manager contract address + */ +export const getRegistryManagerContract = async (chain: Chain): Promise => { + const publicClient = createPublicClient({ + transport: custom(window.ethereum), + chain, + }); + + const registryManagerAddress = (await publicClient.readContract({ + abi: MACIFactory.abi, + address: config.maciAddress as Hex, + functionName: "registryManager", + })) as Hex; + + return registryManagerAddress; +}; + +/** + * Approve an application to join the registry + * @param chain - The chain to use + * @param index - The index of the application to approve + * @returns True if the application was approved, false otherwise + */ +export const approveApplication = async (chain: Chain, index: string): Promise => { + const publicClient = createPublicClient({ + transport: custom(window.ethereum), + chain, + }); + + // eslint-disable-next-line + const [account] = await window.ethereum!.request({ method: "eth_requestAccounts" }); + + const client = createWalletClient({ + // eslint-disable-next-line + account, + chain, + transport: custom(window.ethereum), + }); + + const registryManagerAddress = await getRegistryManagerContract(chain); + + try { + const tx = await client.writeContract({ + abi: RegistryManagerFactory.abi, + address: registryManagerAddress, + functionName: "approve", + args: [index], + chain, + // eslint-disable-next-line + account, + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: tx }); + return receipt.status === "success"; + } catch (error) { + return false; + } +}; + +/** + * Reject an application to join the registry + * + * @param chain - The chain to use + * @param index - The index of the application to reject + * @returns True if the application was rejected, false otherwise + */ +export const rejectApplication = async (chain: Chain, index: bigint): Promise => { + // eslint-disable-next-line + const [account] = await window.ethereum!.request({ method: "eth_requestAccounts" }); + + const publicClient = createPublicClient({ + transport: custom(window.ethereum), + chain, + }); + const client = createWalletClient({ + // eslint-disable-next-line + account, + chain, + transport: custom(window.ethereum), + }); + + const registryManagerAddress = await getRegistryManagerContract(chain); + + try { + const tx = await client.writeContract({ + abi: RegistryManagerFactory.abi, + address: registryManagerAddress, + functionName: "rejectApplication", + args: [index], + chain, + // eslint-disable-next-line + account, + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: tx }); + return receipt.status === "success"; + } catch (error) { + return false; + } +}; + +/** + * Submit an application to join the registry + * + * @param chain - The chain to use + * @param index - The index of the application to submit + * @param metadataPtr - The metadata url + * @param registryAddress - The registry address + * @param recipientAddress - The recipient address + * @param attestationId - The attestation id + * @returns The transaction receipt + */ +export const submitApplication = async ( + chain: Chain, + metadataUrl: string, + registryAddress: Hex, + recipientAddress: Hex, + attestationId?: string, +): Promise => { + // eslint-disable-next-line + const [account] = await window.ethereum!.request({ method: "eth_requestAccounts" }); + + const publicClient = createPublicClient({ + transport: custom(window.ethereum), + chain, + }); + + const walletClient = createWalletClient({ + // eslint-disable-next-line + account, + chain, + transport: custom(window.ethereum), + }); + + const registryManagerAddress = await getRegistryManagerContract(chain); + + const request: IRequest = { + registry: registryAddress, + requestType: ERequestType.Add, + recipient: { + recipient: recipientAddress, + metadataUrl, + id: attestationId ?? (hexlify(randomBytes(32)) as Hex), + }, + index: "0", + status: ERequestStatus.Pending, + }; + + try { + const tx = await walletClient.writeContract({ + abi: RegistryManagerFactory.abi, + address: registryManagerAddress, + functionName: "process", + args: [request], + chain, + // eslint-disable-next-line + account, + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: tx }); + + return receipt; + } catch (error) { + throw new Error("Failed to submit application"); + } +}; + +/** + * Get the number of projects in the registry + * + * @param chain - The chain to use + * @param registryAddress - The registry address + * @returns The number of projects in the registry + */ +export const getProjectCount = async (chain: Chain, registryAddress: Hex): Promise => { + const publicClient = createPublicClient({ + transport: http(), + chain, + }); + + const count = (await publicClient.readContract({ + abi: BaseRegistryFactory.abi, + address: registryAddress, + functionName: "recipientCount", + })) as bigint; + + return count; +}; diff --git a/packages/interface/src/utils/types.ts b/packages/interface/src/utils/types.ts index 036951d1..63ddccf0 100644 --- a/packages/interface/src/utils/types.ts +++ b/packages/interface/src/utils/types.ts @@ -1,4 +1,4 @@ -import { type Address } from "viem"; +import type { Hex, Address } from "viem"; export enum EAppState { LOADING = "LOADING", @@ -88,3 +88,161 @@ export const AttestationsQuery = ` } } `; + +/** + * The request type + */ +export enum ERequestType { + /** + * Add a new recipient + */ + Add = 0, + /** + * Change a recipient + */ + Change = 1, + /** + * Remove a recipient + */ + Remove = 2, +} + +/** + * The request status + */ +export enum ERequestStatus { + /** + * The request is pending + */ + Pending = 0, + /** + * The request is approved + */ + Approved = 1, + /** + * The request is rejected + */ + Rejected = 2, +} + +/** + * The recipient data + */ +export interface IRecipient { + /** + * The recipient id (optional on chain so can use 0) + */ + id: string; + /** + * The recipient metadata url + */ + metadataUrl: string; + /** + * The recipient address + */ + payout: Hex; + /** + * The recipient index + */ + index: string; + /** + * Whether it was approved or not + */ + initialized?: boolean; +} + +/** + * The recipient data for the contract + */ +export interface IRecipientContract { + /** + * The recipient id (optional on chain so can use 0) + */ + id: string; + /** + * The recipient metadata url + */ + metadataUrl: string; + /** + * The recipient address + */ + recipient: Hex; + /** + * Whether it was approved or not + */ + initialized?: boolean; +} + +/** + * The request data + */ +export interface IRequest { + /** + * The index of the application (optional onchain for Add so can use 0) + */ + index: string; + /** + * The registry address + */ + registry: Hex; + /** + * The request type + */ + requestType: ERequestType; + /** + * The request status + */ + status: ERequestStatus; + /** + * The recipient data + */ + recipient: IRecipient | IRecipientContract; +} + +/** + * The poll data + */ +export interface Poll { + /** + * The poll id + */ + pollId: string; + /** + * The poll creation date + */ + createdAt: string; + /** + * The poll duration + */ + duration: string; + /** + * MACI's state root + */ + stateRoot: string; + /** + * The poll message root + */ + messageRoot: string; + /** + * The poll number of signups + */ + numSignups: string; + /** + * The poll id + */ + id: string; + /** + * The poll mode + */ + mode: string; + /** + * The poll init time + */ + initTime: string; + /** + * The poll registry address + */ + registry: { + id: string; + }; +} diff --git a/packages/subgraph/schemas/schema.v1.graphql b/packages/subgraph/schemas/schema.v1.graphql index f474836e..bff1400c 100644 --- a/packages/subgraph/schemas/schema.v1.graphql +++ b/packages/subgraph/schemas/schema.v1.graphql @@ -40,7 +40,7 @@ enum Status { } type Request @entity { - id: ID! + id: String! requestType: RequestType! index: BigInt status: Status! @@ -68,6 +68,7 @@ type Recipient @entity { type Registry @entity { id: Bytes! # address + metadataUrl: String "relations" poll: Poll recipients: [Recipient!]! @derivedFrom(field: "registry") diff --git a/packages/subgraph/src/poll.ts b/packages/subgraph/src/poll.ts index cd6be511..46603b5e 100644 --- a/packages/subgraph/src/poll.ts +++ b/packages/subgraph/src/poll.ts @@ -9,6 +9,7 @@ import { PublishMessage as PublishMessageEvent, SetRegistry as SetRegistryEvent, } from "../generated/templates/Poll/Poll"; +import { Registry as RegistryContract } from "../generated/templates/Registry/Registry"; import { ONE_BIG_INT } from "./utils/constants"; import { createOrLoadRegistry } from "./utils/entity"; @@ -75,8 +76,10 @@ export function handleSetRegistry(event: SetRegistryEvent): void { return; } + const contract = RegistryContract.bind(event.params.registry); const registry = createOrLoadRegistry(event.params.registry); registry.poll = poll.id; + registry.metadataUrl = contract.getRegistryMetadataUrl(); registry.save(); poll.registry = event.params.registry; diff --git a/packages/subgraph/src/registry.ts b/packages/subgraph/src/registry.ts index 0e89bd34..0ba2a48f 100644 --- a/packages/subgraph/src/registry.ts +++ b/packages/subgraph/src/registry.ts @@ -13,6 +13,8 @@ export function handleAddRecipient(event: RecipientAdded): void { event.address, ); + // we want to ensure that the index is the recipient index in the registry + recipient.index = event.params.index; recipient.initialized = true; recipient.save(); } @@ -25,6 +27,7 @@ export function handleChangeRecipient(event: RecipientChanged): void { } recipient.metadataUrl = event.params.metadataUrl.toString(); + recipient.id = event.params.id; recipient.index = event.params.index; recipient.initialized = true; recipient.deleted = false; diff --git a/packages/subgraph/templates/subgraph.template.yaml b/packages/subgraph/templates/subgraph.template.yaml index d533a3e4..a20113fa 100644 --- a/packages/subgraph/templates/subgraph.template.yaml +++ b/packages/subgraph/templates/subgraph.template.yaml @@ -51,11 +51,11 @@ dataSources: - name: BaseRegistry file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/registry/BaseRegistry.sol/BaseRegistry.json eventHandlers: - - event: RequestSent(indexed address,indexed uint8,indexed bytes32,uint256,address,string) + - event: RequestSent(indexed address,indexed uint8,indexed bytes32,uint256,uint256,address,string) handler: handleRequestSent - - event: RequestApproved(indexed address,indexed uint8,indexed bytes32,uint256,address,string) + - event: RequestApproved(indexed address,indexed uint8,indexed bytes32,uint256,uint256,address,string) handler: handleRequestApproved - - event: RequestRejected(indexed address,indexed uint8,indexed bytes32,uint256,address,string) + - event: RequestRejected(indexed address,indexed uint8,indexed bytes32,uint256,uint256,address,string) handler: handleRequestRejected file: ./src/registryManager.ts templates: @@ -73,6 +73,8 @@ templates: abis: - name: Poll file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/maci/Poll.sol/Poll.json + - name: Registry + file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/registry/BaseRegistry.sol/BaseRegistry.json eventHandlers: - event: MergeMaciState(indexed uint256,indexed uint256) handler: handleMergeMaciState diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57f5f457..1a239221 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -402,10 +402,13 @@ importers: version: 0.316.0(react@18.2.0) maci-cli: specifier: ^2.4.0 - version: 2.4.0(3cqgokiuvxx4ajhx37zjyqxqrq) + version: 2.4.0(ajppvhbj75go4cdbr4h4vhruha) maci-domainobjs: specifier: ^2.4.0 version: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-platform-contracts: + specifier: workspace:* + version: link:../contracts next: specifier: ^14.1.0 version: 14.2.5(@babel/core@7.25.2)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -538,7 +541,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-import-resolver-typescript: specifier: ^3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) + version: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0) eslint-plugin-import: specifier: ^2.30.0 version: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) @@ -16558,9 +16561,9 @@ snapshots: '@nomicfoundation/ethereumjs-rlp': 5.0.4 ethereum-cryptography: 0.1.3 - '@nomicfoundation/hardhat-chai-matchers@2.0.7(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': + '@nomicfoundation/hardhat-chai-matchers@2.0.7(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) '@types/chai-as-promised': 7.1.8 chai: 4.5.0 chai-as-promised: 7.1.2(chai@4.5.0) @@ -16591,15 +16594,6 @@ snapshots: hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) ordinal: 1.0.3 - '@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': - dependencies: - debug: 4.3.6(supports-color@8.1.1) - ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - hardhat: 2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) - lodash.isequal: 4.5.0 - transitivePeerDependencies: - - supports-color - '@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': dependencies: debug: 4.3.6(supports-color@8.1.1) @@ -16618,9 +16612,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-ignition-ethers@0.15.5(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': + '@nomicfoundation/hardhat-ignition-ethers@0.15.5(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-ignition': 0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) '@nomicfoundation/ignition-core': 0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -16702,6 +16696,27 @@ snapshots: ethereumjs-util: 7.1.5 hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + '@nomicfoundation/hardhat-toolbox@5.0.0(4lzaewf5vhmhgkr3zdkxldxwse)': + dependencies: + '@nomicfoundation/hardhat-chai-matchers': 2.0.7(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ignition-ethers': 0.15.5(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-network-helpers': 1.0.11(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-verify': 2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@typechain/ethers-v6': 0.5.1(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4) + '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.5.4)) + '@types/chai': 4.3.17 + '@types/mocha': 10.0.7 + '@types/node': 20.14.14 + chai: 4.5.0 + ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + hardhat-gas-reporter: 1.0.10(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + solidity-coverage: 0.8.12(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) + typechain: 8.3.2(typescript@5.5.4) + typescript: 5.5.4 + '@nomicfoundation/hardhat-toolbox@5.0.0(6zbd34p7xszhw7fs5zk33ckexe)': dependencies: '@nomicfoundation/hardhat-chai-matchers': 2.0.7(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) @@ -16744,27 +16759,6 @@ snapshots: typechain: 8.3.2(typescript@5.5.4) typescript: 5.5.4 - '@nomicfoundation/hardhat-toolbox@5.0.0(wq4v7k5ocgbchgotiijfxhcogu)': - dependencies: - '@nomicfoundation/hardhat-chai-matchers': 2.0.7(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-ignition-ethers': 0.15.5(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-network-helpers': 1.0.11(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-verify': 2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@typechain/ethers-v6': 0.5.1(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4) - '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.5.4)) - '@types/chai': 4.3.17 - '@types/mocha': 10.0.7 - '@types/node': 20.14.14 - chai: 4.5.0 - ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) - hardhat-gas-reporter: 1.0.10(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) - solidity-coverage: 0.8.12(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4) - typechain: 8.3.2(typescript@5.5.4) - typescript: 5.5.4 - '@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': dependencies: '@ethersproject/abi': 5.7.0 @@ -17554,7 +17548,7 @@ snapshots: '@semaphore-protocol/identity': 3.15.2 '@types/json-bigint': 1.0.4 '@zk-kit/eddsa-poseidon': 1.0.3 - '@zk-kit/utils': 1.2.1 + '@zk-kit/utils': 1.2.0 js-sha256: 0.11.0 json-bigint: 1.0.0 poseidon-lite: 0.3.0 @@ -22771,7 +22765,7 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) @@ -22794,12 +22788,29 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0): + dependencies: + debug: 4.3.6(supports-color@8.1.1) + enhanced-resolve: 5.17.1 + eslint: 8.57.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + fast-glob: 3.3.2 + get-tsconfig: 4.7.6 + is-core-module: 2.15.0 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0): dependencies: debug: 4.3.6(supports-color@8.1.1) enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 @@ -22835,7 +22846,7 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -22850,14 +22861,24 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): + dependencies: + debug: 3.2.7(supports-color@8.1.1) + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) + eslint: 8.57.0 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -26678,16 +26699,16 @@ snapshots: - typescript - utf-8-validate - maci-cli@2.4.0(3cqgokiuvxx4ajhx37zjyqxqrq): + maci-cli@2.4.0(ajppvhbj75go4cdbr4h4vhruha): dependencies: '@commander-js/extra-typings': 12.1.0(commander@12.1.0) - '@nomicfoundation/hardhat-toolbox': 5.0.0(wq4v7k5ocgbchgotiijfxhcogu) + '@nomicfoundation/hardhat-toolbox': 5.0.0(4lzaewf5vhmhgkr3zdkxldxwse) commander: 12.1.0 dotenv: 16.4.5 ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) maci-circuits: 2.4.0(@types/snarkjs@0.7.8)(bufferutil@4.0.8)(utf-8-validate@5.0.10) - maci-contracts: 2.4.0(fiyhxdrnyqwclxh26nwcfabgem) + maci-contracts: 2.4.0(tnqgnvgpfdbm2nfzhwjuiounlm) maci-core: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) maci-crypto: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) maci-domainobjs: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -26789,10 +26810,10 @@ snapshots: - typescript - utf-8-validate - maci-contracts@2.4.0(fiyhxdrnyqwclxh26nwcfabgem): + maci-contracts@2.4.0(tnqgnvgpfdbm2nfzhwjuiounlm): dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-toolbox': 5.0.0(wq4v7k5ocgbchgotiijfxhcogu) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-toolbox': 5.0.0(4lzaewf5vhmhgkr3zdkxldxwse) '@openzeppelin/contracts': 5.0.2 '@openzeppelin/merkle-tree': 1.0.7 circomlibjs: 0.1.7(bufferutil@4.0.8)(utf-8-validate@5.0.10)