From f6cceb37dd0f90d0b4f66ac4d02be89f04fb91a3 Mon Sep 17 00:00:00 2001 From: yu-zhen Date: Wed, 17 Jul 2024 12:40:01 +0900 Subject: [PATCH] fix: fetch results cached st not getting latest data --- src/contexts/Maci.tsx | 2 +- .../components/AdminButtonsBar.tsx | 36 ------ .../components/ApplicationsToApprove.tsx | 37 ++++-- .../applications/components/ApproveButton.tsx | 41 ------ .../applications/components/ReviewBar.tsx | 51 +++++++- .../hooks/useApplicationByTxHash.ts | 2 +- .../applications/hooks/useApplications.ts | 2 +- .../hooks/useApprovedApplications.ts | 2 +- .../projects/components/ProjectDetails.tsx | 9 +- .../projects/components/ProjectItem.tsx | 3 +- src/features/projects/hooks/useProjects.ts | 2 +- src/hooks/useProfile.ts | 2 +- src/hooks/useResults.ts | 2 +- src/server/api/routers/applications.ts | 3 +- src/server/api/routers/profile.ts | 3 +- src/server/api/routers/projects.ts | 5 +- src/server/api/routers/voters.ts | 8 +- src/utils/fetchAttestations.ts | 120 +----------------- src/utils/fetchAttestationsUtils.ts | 48 +++++++ src/utils/fetchAttestationsWithoutCache.ts | 33 +++++ src/utils/types.ts | 73 +++++++++++ 21 files changed, 257 insertions(+), 227 deletions(-) delete mode 100644 src/features/applications/components/AdminButtonsBar.tsx delete mode 100644 src/features/applications/components/ApproveButton.tsx create mode 100644 src/utils/fetchAttestationsUtils.ts create mode 100644 src/utils/fetchAttestationsWithoutCache.ts diff --git a/src/contexts/Maci.tsx b/src/contexts/Maci.tsx index c71cf1ea..c03fd992 100644 --- a/src/contexts/Maci.tsx +++ b/src/contexts/Maci.tsx @@ -22,7 +22,7 @@ import { getSemaphoreProof } from "~/utils/semaphore"; import type { IVoteArgs, MaciContextType, MaciProviderProps } from "./types"; import type { Signer } from "ethers"; -import type { Attestation } from "~/utils/fetchAttestations"; +import type { Attestation } from "~/utils/types"; export const MaciContext = createContext(undefined); diff --git a/src/features/applications/components/AdminButtonsBar.tsx b/src/features/applications/components/AdminButtonsBar.tsx deleted file mode 100644 index b6eb0c3f..00000000 --- a/src/features/applications/components/AdminButtonsBar.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useCallback } from "react"; - -import { Button } from "~/components/ui/Button"; -import { useIsAdmin } from "~/hooks/useIsAdmin"; -import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork"; - -import { useApproveApplication } from "../hooks/useApproveApplication"; - -interface IAdminButtonsBarProps { - projectId: string; -} - -export const AdminButtonsBar = ({ projectId }: IAdminButtonsBarProps): JSX.Element => { - const isAdmin = useIsAdmin(); - const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork(); - - const approve = useApproveApplication({}); - - const onClick = useCallback(() => { - approve.mutate([projectId]); - }, [approve, projectId]); - - return ( -
- -
- ); -}; diff --git a/src/features/applications/components/ApplicationsToApprove.tsx b/src/features/applications/components/ApplicationsToApprove.tsx index be9bdd25..fecc394e 100644 --- a/src/features/applications/components/ApplicationsToApprove.tsx +++ b/src/features/applications/components/ApplicationsToApprove.tsx @@ -1,6 +1,6 @@ import { ClockIcon } from "lucide-react"; import Link from "next/link"; -import { useMemo } from "react"; +import { useMemo, useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; import { FiAlertCircle } from "react-icons/fi"; import { z } from "zod"; @@ -17,9 +17,11 @@ import { ProjectAvatar } from "~/features/projects/components/ProjectAvatar"; import { useIsAdmin } from "~/hooks/useIsAdmin"; import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork"; import { useMetadata } from "~/hooks/useMetadata"; -import { type Attestation } from "~/utils/fetchAttestations"; +import { fetchApprovedApplications } from "~/utils/fetchAttestationsWithoutCache"; import { formatDate } from "~/utils/time"; +import type { Attestation } from "~/utils/types"; + import { useApproveApplication } from "../hooks/useApproveApplication"; import { useApprovedApplications } from "../hooks/useApprovedApplications"; @@ -130,7 +132,9 @@ export const ApplicationItem = ({
- {isApproved ? Approved : Pending} + {isApproved && Approved} + + {!isApproved && Pending}
@@ -146,18 +150,35 @@ type TApplicationsToApprove = z.infer; export const ApplicationsToApprove = (): JSX.Element => { const applications = useApplications(); const approved = useApprovedApplications(); - const approve = useApproveApplication({}); + const approve = useApproveApplication(); + const [refetchedData, setRefetchedData] = useState(); const approvedById = useMemo( () => - approved.data?.reduce((map, x) => { + [...(approved.data ?? []), ...(refetchedData ?? [])].reduce((map, x) => { map.set(x.refUID, true); return map; }, new Map()), - [approved.data], + [approved.data, refetchedData], ); - const applicationsToApprove = applications.data?.filter((application) => !approvedById?.get(application.id)); + const applicationsToApprove = applications.data?.filter((application) => !approvedById.get(application.id)); + + 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]); return (
@@ -207,7 +228,7 @@ export const ApplicationsToApprove = (): JSX.Element => { ))} diff --git a/src/features/applications/components/ApproveButton.tsx b/src/features/applications/components/ApproveButton.tsx deleted file mode 100644 index a8dde55b..00000000 --- a/src/features/applications/components/ApproveButton.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import dynamic from "next/dynamic"; -import { type PropsWithChildren } from "react"; - -import { Badge } from "~/components/ui/Badge"; -import { Button } from "~/components/ui/Button"; -import { useApproveApplication } from "~/features/applications/hooks/useApproveApplication"; -import { useIsAdmin } from "~/hooks/useIsAdmin"; - -import { useApprovedApplications } from "../hooks/useApprovedApplications"; - -const ApproveButton = ({ - children = "Approve project", - projectIds = [], -}: PropsWithChildren<{ projectIds: string[] }>) => { - const isAdmin = useIsAdmin(); - const approvals = useApprovedApplications(projectIds); - - const approve = useApproveApplication(); - if (approvals.data?.length) { - return ( - - Approved - - ); - } - return ( - isAdmin && ( - - ) - ); -}; - -export default dynamic(() => Promise.resolve(ApproveButton)); diff --git a/src/features/applications/components/ReviewBar.tsx b/src/features/applications/components/ReviewBar.tsx index 03a8e43e..659739a8 100644 --- a/src/features/applications/components/ReviewBar.tsx +++ b/src/features/applications/components/ReviewBar.tsx @@ -1,12 +1,17 @@ -import { useMemo } from "react"; +import { useMemo, useCallback, useState, useEffect } from "react"; import { FiAlertCircle } from "react-icons/fi"; import { StatusBar } from "~/components/StatusBar"; +import { Button } from "~/components/ui/Button"; +import { Spinner } from "~/components/ui/Spinner"; import { useIsAdmin } from "~/hooks/useIsAdmin"; +import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork"; +import { fetchApprovedApplications } from "~/utils/fetchAttestationsWithoutCache"; -import { useApprovedApplications } from "../hooks/useApprovedApplications"; +import type { Attestation } from "~/utils/types"; -import { AdminButtonsBar } from "./AdminButtonsBar"; +import { useApproveApplication } from "../hooks/useApproveApplication"; +import { useApprovedApplications } from "../hooks/useApprovedApplications"; interface IReviewBarProps { projectId: string; @@ -14,9 +19,37 @@ interface IReviewBarProps { export const ReviewBar = ({ projectId }: IReviewBarProps): JSX.Element => { const isAdmin = useIsAdmin(); + const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork(); + const rawReturn = useApprovedApplications([projectId]); + const [refetchedData, setRefetchedData] = useState(); + + const approved = useMemo( + () => (rawReturn.data && rawReturn.data.length > 0) || (refetchedData && refetchedData.length > 0), + [rawReturn.data, refetchedData], + ); + + const approve = useApproveApplication(); - const approved = useMemo(() => rawReturn.data && rawReturn.data.length > 0, [rawReturn]); + const onClick = useCallback(() => { + approve.mutate([projectId]); + }, [approve, projectId]); + + 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]); return (
@@ -39,7 +72,15 @@ export const ReviewBar = ({ projectId }: IReviewBarProps): JSX.Element => { /> )} - {isAdmin && !approved && } + {isAdmin && !approved && ( +
+ +
+ )}
); }; diff --git a/src/features/applications/hooks/useApplicationByTxHash.ts b/src/features/applications/hooks/useApplicationByTxHash.ts index 4ad843cf..c70ae65a 100644 --- a/src/features/applications/hooks/useApplicationByTxHash.ts +++ b/src/features/applications/hooks/useApplicationByTxHash.ts @@ -1,7 +1,7 @@ import { api } from "~/utils/api"; import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { Attestation } from "~/utils/fetchAttestations"; +import type { Attestation } from "~/utils/types"; export function useApplicationByTxHash(transactionId: string): UseTRPCQueryResult { return api.projects.getByTransactionId.useQuery({ transactionId }); diff --git a/src/features/applications/hooks/useApplications.ts b/src/features/applications/hooks/useApplications.ts index 8e3f60a7..bd92b59d 100644 --- a/src/features/applications/hooks/useApplications.ts +++ b/src/features/applications/hooks/useApplications.ts @@ -1,7 +1,7 @@ import { api } from "~/utils/api"; import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { Attestation } from "~/utils/fetchAttestations"; +import type { Attestation } from "~/utils/types"; export function useApplications(): UseTRPCQueryResult { return api.applications.list.useQuery({}); diff --git a/src/features/applications/hooks/useApprovedApplications.ts b/src/features/applications/hooks/useApprovedApplications.ts index ece39ea8..6c3ed93b 100644 --- a/src/features/applications/hooks/useApprovedApplications.ts +++ b/src/features/applications/hooks/useApprovedApplications.ts @@ -1,7 +1,7 @@ import { api } from "~/utils/api"; import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { Attestation } from "~/utils/fetchAttestations"; +import type { Attestation } from "~/utils/types"; export function useApprovedApplications(ids?: string[]): UseTRPCQueryResult { return api.applications.approvals.useQuery({ ids }); diff --git a/src/features/projects/components/ProjectDetails.tsx b/src/features/projects/components/ProjectDetails.tsx index a5ddf299..6a2c7f16 100644 --- a/src/features/projects/components/ProjectDetails.tsx +++ b/src/features/projects/components/ProjectDetails.tsx @@ -5,7 +5,10 @@ import { Navigation } from "~/components/ui/Navigation"; import { ProjectAvatar } from "~/features/projects/components/ProjectAvatar"; import { ProjectBanner } from "~/features/projects/components/ProjectBanner"; import { VotingWidget } from "~/features/projects/components/VotingWidget"; -import { type Attestation } from "~/utils/fetchAttestations"; +import { useAppState } from "~/utils/state"; +import { EAppState } from "~/utils/types"; + +import type { Attestation } from "~/utils/types"; import { useProjectMetadata } from "../hooks/useProjects"; @@ -30,6 +33,8 @@ const ProjectDetails = ({ const { bio, websiteUrl, payoutAddress, github, twitter, fundingSources, profileImageUrl, bannerImageUrl } = metadata.data ?? {}; + const appState = useAppState(); + return (
@@ -47,7 +52,7 @@ const ProjectDetails = ({

{attestation?.name}

- + {appState === EAppState.VOTING && }
diff --git a/src/features/projects/components/ProjectItem.tsx b/src/features/projects/components/ProjectItem.tsx index a896d9a7..e12826f0 100644 --- a/src/features/projects/components/ProjectItem.tsx +++ b/src/features/projects/components/ProjectItem.tsx @@ -4,11 +4,12 @@ import { Button } from "~/components/ui/Button"; import { Heading } from "~/components/ui/Heading"; import { Skeleton } from "~/components/ui/Skeleton"; import { config } from "~/config"; -import { type Attestation } from "~/utils/fetchAttestations"; import { formatNumber } from "~/utils/formatNumber"; import { useAppState } from "~/utils/state"; import { EAppState } from "~/utils/types"; +import type { Attestation } from "~/utils/types"; + import { useProjectMetadata } from "../hooks/useProjects"; import { EProjectState } from "../types"; diff --git a/src/features/projects/hooks/useProjects.ts b/src/features/projects/hooks/useProjects.ts index ff45f0a9..8dc1fdee 100644 --- a/src/features/projects/hooks/useProjects.ts +++ b/src/features/projects/hooks/useProjects.ts @@ -8,7 +8,7 @@ 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/fetchAttestations"; +import type { Attestation } from "~/utils/types"; interface IUseSearchProjectsProps { filterOverride?: Partial; diff --git a/src/hooks/useProfile.ts b/src/hooks/useProfile.ts index 43063d54..a5072186 100644 --- a/src/hooks/useProfile.ts +++ b/src/hooks/useProfile.ts @@ -3,7 +3,7 @@ import { type Address } from "viem"; import { api } from "~/utils/api"; import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { Attestation } from "~/utils/fetchAttestations"; +import type { Attestation } from "~/utils/types"; import { useMetadata } from "./useMetadata"; diff --git a/src/hooks/useResults.ts b/src/hooks/useResults.ts index 94b633ff..a807cd25 100644 --- a/src/hooks/useResults.ts +++ b/src/hooks/useResults.ts @@ -5,7 +5,7 @@ import { EAppState } from "~/utils/types"; import type { UseTRPCInfiniteQueryResult, UseTRPCQueryResult } from "@trpc/react-query/shared"; import type { IGetPollData } from "maci-cli/sdk"; -import type { Attestation } from "~/utils/fetchAttestations"; +import type { Attestation } from "~/utils/types"; export function useResults( pollData?: IGetPollData, diff --git a/src/server/api/routers/applications.ts b/src/server/api/routers/applications.ts index 2d457501..c1c90d76 100644 --- a/src/server/api/routers/applications.ts +++ b/src/server/api/routers/applications.ts @@ -2,7 +2,8 @@ import { z } from "zod"; import { config, eas } from "~/config"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { createDataFilter, fetchAttestations } from "~/utils/fetchAttestations"; +import { fetchAttestations } from "~/utils/fetchAttestations"; +import { createDataFilter } from "~/utils/fetchAttestationsUtils"; export const FilterSchema = z.object({ limit: z.number().default(3 * 8), diff --git a/src/server/api/routers/profile.ts b/src/server/api/routers/profile.ts index 62bc0dce..bf36f11e 100644 --- a/src/server/api/routers/profile.ts +++ b/src/server/api/routers/profile.ts @@ -3,7 +3,8 @@ import { z } from "zod"; import { eas } from "~/config"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { fetchAttestations, createDataFilter } from "~/utils/fetchAttestations"; +import { fetchAttestations } from "~/utils/fetchAttestations"; +import { createDataFilter } from "~/utils/fetchAttestationsUtils"; export const profileRouter = createTRPCRouter({ get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => diff --git a/src/server/api/routers/projects.ts b/src/server/api/routers/projects.ts index 22506e46..12299520 100644 --- a/src/server/api/routers/projects.ts +++ b/src/server/api/routers/projects.ts @@ -4,9 +4,12 @@ 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, createDataFilter, createSearchFilter, type Attestation } from "~/utils/fetchAttestations"; +import { fetchAttestations } from "~/utils/fetchAttestations"; +import { createDataFilter, createSearchFilter } from "~/utils/fetchAttestationsUtils"; import { fetchMetadata } from "~/utils/fetchMetadata"; +import type { Attestation } from "~/utils/types"; + export const projectsRouter = createTRPCRouter({ count: publicProcedure.query(async () => fetchAttestations([eas.schemas.approval], { diff --git a/src/server/api/routers/voters.ts b/src/server/api/routers/voters.ts index daa6ae77..d3f0b2dc 100644 --- a/src/server/api/routers/voters.ts +++ b/src/server/api/routers/voters.ts @@ -2,12 +2,8 @@ import { z } from "zod"; import { config, eas } from "~/config"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { - fetchAttestations, - createDataFilter, - fetchApprovedVoter, - fetchApprovedVoterAttestations, -} from "~/utils/fetchAttestations"; +import { fetchAttestations, fetchApprovedVoter, fetchApprovedVoterAttestations } from "~/utils/fetchAttestations"; +import { createDataFilter } from "~/utils/fetchAttestationsUtils"; export const FilterSchema = z.object({ limit: z.number().default(3 * 8), diff --git a/src/utils/fetchAttestations.ts b/src/utils/fetchAttestations.ts index dfc5b860..24b1ddb8 100644 --- a/src/utils/fetchAttestations.ts +++ b/src/utils/fetchAttestations.ts @@ -1,70 +1,11 @@ -import { encodeBytes32String, decodeBytes32String } from "ethers"; -import { fromHex, type Address } from "viem"; - import { config, eas } from "~/config"; import { createCachedFetch } from "./fetch"; +import { parseAttestation, createDataFilter } from "./fetchAttestationsUtils"; +import { type AttestationWithMetadata, type AttestationsFilter, type Attestation, AttestationsQuery } from "./types"; const cachedFetch = createCachedFetch({ ttl: 1000 * 60 * 10 }); -export interface AttestationWithMetadata { - id: string; - refUID: string; - attester: Address; - recipient: Address; - revoked: boolean; - time: number; - decodedDataJson: string; - schemaId: string; - txid: string; -} - -export type Attestation = Omit & { - name: string; - metadataPtr: string; -}; - -interface MatchFilter { - equals?: string; - in?: string[]; - gte?: number; -} -interface MatchWhere { - id?: MatchFilter; - attester?: MatchFilter; - recipient?: MatchFilter; - refUID?: MatchFilter; - schemaId?: MatchFilter; - time?: MatchFilter; - txid?: MatchFilter; - decodedDataJson?: { contains: string }; - AND?: MatchWhere[]; -} -interface AttestationsFilter { - take?: number; - skip?: number; - orderBy?: { - time?: "asc" | "desc"; - }[]; - where?: MatchWhere; -} - -const AttestationsQuery = ` - query Attestations($where: AttestationWhereInput, $orderBy: [AttestationOrderByWithRelationInput!], $take: Int, $skip: Int) { - attestations(take: $take, skip: $skip, orderBy: $orderBy, where: $where) { - id - refUID - decodedDataJson - attester - recipient - revoked - schemaId - txid - time - } - } -`; - export async function fetchAttestations(schema: string[], filter?: AttestationsFilter): Promise { const startsAt = config.startsAt && Math.floor(+config.startsAt / 1000); @@ -110,60 +51,3 @@ export async function fetchApprovedVoterAttestations(address: string): Promise attestations); } - -function parseAttestation({ decodedDataJson, ...attestation }: AttestationWithMetadata): Attestation { - return { ...attestation, ...parseDecodedMetadata(decodedDataJson) }; -} - -interface Metadata { - name: string; - metadataPtr: string; - round: string; - type: string; -} - -export function parseDecodedMetadata(json: string): Metadata { - const data = JSON.parse(json) as { name: string; value: { value: string } }[]; - const metadata = data.reduce((acc, x) => ({ ...acc, [x.name]: x.value.value }), {}) as Metadata; - - return { - ...metadata, - // type: parseBytes(metadata.type), - // round: parseBytes(metadata.round), - }; -} - -export const parseBytes = (hex: string): string => decodeBytes32String(fromHex(hex as Address, "bytes")); - -export const formatBytes = (string: string): string => encodeBytes32String(string); - -const typeMaps = { - bytes32: (v: string) => formatBytes(v), - string: (v: string) => v, -}; - -export interface AttestationFilter { - decodedDataJson: { - contains: string; - }; -} - -export function createSearchFilter(value: string): AttestationFilter { - const formatter = typeMaps.string; - - return { - decodedDataJson: { - contains: formatter(value), - }, - }; -} - -export function createDataFilter(name: string, type: "bytes32" | "string", value: string): AttestationFilter { - const formatter = typeMaps[type]; - - return { - decodedDataJson: { - contains: `"name":"${name}","type":"${type}","value":"${formatter(value)}`, - }, - }; -} diff --git a/src/utils/fetchAttestationsUtils.ts b/src/utils/fetchAttestationsUtils.ts new file mode 100644 index 00000000..28d08410 --- /dev/null +++ b/src/utils/fetchAttestationsUtils.ts @@ -0,0 +1,48 @@ +import { encodeBytes32String, decodeBytes32String } from "ethers"; +import { fromHex, type Address } from "viem"; + +import type { Metadata, AttestationWithMetadata, Attestation, AttestationFilter } from "./types"; + +export function parseAttestation({ decodedDataJson, ...attestation }: AttestationWithMetadata): Attestation { + return { ...attestation, ...parseDecodedMetadata(decodedDataJson) }; +} + +export function parseDecodedMetadata(json: string): Metadata { + const data = JSON.parse(json) as { name: string; value: { value: string } }[]; + const metadata = data.reduce((acc, x) => ({ ...acc, [x.name]: x.value.value }), {}) as Metadata; + + return { + ...metadata, + // type: parseBytes(metadata.type), + // round: parseBytes(metadata.round), + }; +} + +export const parseBytes = (hex: string): string => decodeBytes32String(fromHex(hex as Address, "bytes")); + +export const formatBytes = (string: string): string => encodeBytes32String(string); + +const typeMaps = { + bytes32: (v: string) => formatBytes(v), + string: (v: string) => v, +}; + +export function createSearchFilter(value: string): AttestationFilter { + const formatter = typeMaps.string; + + return { + decodedDataJson: { + contains: formatter(value), + }, + }; +} + +export function createDataFilter(name: string, type: "bytes32" | "string", value: string): AttestationFilter { + const formatter = typeMaps[type]; + + return { + decodedDataJson: { + contains: `"name":"${name}","type":"${type}","value":"${formatter(value)}`, + }, + }; +} diff --git a/src/utils/fetchAttestationsWithoutCache.ts b/src/utils/fetchAttestationsWithoutCache.ts new file mode 100644 index 00000000..3f328971 --- /dev/null +++ b/src/utils/fetchAttestationsWithoutCache.ts @@ -0,0 +1,33 @@ +import { config, eas } from "~/config"; + +import { parseAttestation, createDataFilter } from "./fetchAttestationsUtils"; +import { type AttestationWithMetadata, type Attestation, AttestationsQuery } from "./types"; + +export async function fetchApprovedApplications(ids?: string[]): Promise { + const startsAt = config.startsAt && Math.floor(+config.startsAt / 1000); + + return fetch(eas.url, { + method: "POST", + headers: { + "content-type": "application/json;charset=UTF-8", + }, + body: JSON.stringify({ + query: AttestationsQuery, + variables: { + where: { + schemaId: { in: [eas.schemas.approval] }, + revoked: { equals: false }, + time: { gte: startsAt }, + attester: { equals: config.admin }, + refUID: ids ? { in: ids } : undefined, + AND: [ + createDataFilter("type", "bytes32", "application"), + createDataFilter("round", "bytes32", config.roundId), + ], + }, + }, + }), + }) + .then((res) => res.json() as unknown as { data: { attestations: AttestationWithMetadata[] }; error: Error }) + .then((r) => r.data.attestations.map(parseAttestation)); +} diff --git a/src/utils/types.ts b/src/utils/types.ts index f238d142..45935ea6 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,5 @@ +import { type Address } from "viem"; + export enum EAppState { LOADING = "LOADING", REVIEWING = "REVIEWING", @@ -12,3 +14,74 @@ export enum EInfoCardState { ONGOING = "ONGOING", UPCOMING = "UPCOMING", } + +export interface AttestationWithMetadata { + id: string; + refUID: string; + attester: Address; + recipient: Address; + revoked: boolean; + time: number; + decodedDataJson: string; + schemaId: string; + txid: string; +} + +export type Attestation = Omit & { + name: string; + metadataPtr: string; +}; + +export interface MatchFilter { + equals?: string; + in?: string[]; + gte?: number; +} +export interface MatchWhere { + id?: MatchFilter; + attester?: MatchFilter; + recipient?: MatchFilter; + refUID?: MatchFilter; + schemaId?: MatchFilter; + time?: MatchFilter; + txid?: MatchFilter; + decodedDataJson?: { contains: string }; + AND?: MatchWhere[]; +} +export interface AttestationsFilter { + take?: number; + skip?: number; + orderBy?: { + time?: "asc" | "desc"; + }[]; + where?: MatchWhere; +} + +export interface AttestationFilter { + decodedDataJson: { + contains: string; + }; +} + +export interface Metadata { + name: string; + metadataPtr: string; + round: string; + type: string; +} + +export const AttestationsQuery = ` + query Attestations($where: AttestationWhereInput, $orderBy: [AttestationOrderByWithRelationInput!], $take: Int, $skip: Int) { + attestations(take: $take, skip: $skip, orderBy: $orderBy, where: $where) { + id + refUID + decodedDataJson + attester + recipient + revoked + schemaId + txid + time + } + } +`;