diff --git a/libs/credentials/src/index.test.ts b/libs/credentials/src/index.test.ts index a620bcd4..f0bf03ab 100644 --- a/libs/credentials/src/index.test.ts +++ b/libs/credentials/src/index.test.ts @@ -147,12 +147,16 @@ describe("Credentials library", () => { }) describe("# queryGraph", () => { - it("Should return a function that can be used to query graphs data using GraphQL", () => { - const query = queryGraph( + it("Should return a function that can be used to query graphs data using GraphQL", async () => { + const query = await queryGraph( "https://easscan.org/graphql", ` query { - attestations { + attestations(where: { + recipient: { + equals: "0x" + } + }) { recipient attester revocable @@ -164,7 +168,7 @@ describe("Credentials library", () => { ` ) - expect(query).toBeUndefined() + expect(query).toEqual({}) }) }) diff --git a/libs/credentials/src/queryGraph.ts b/libs/credentials/src/queryGraph.ts index 70b79b9d..4d7aa5e8 100644 --- a/libs/credentials/src/queryGraph.ts +++ b/libs/credentials/src/queryGraph.ts @@ -8,7 +8,7 @@ import { request } from "@bandada/utils" * @returns The function to query the graph. */ export default function queryGraph(endpoint: string, query: string) { - request(endpoint, { + return request(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, data: JSON.stringify({ diff --git a/libs/credentials/src/types/index.ts b/libs/credentials/src/types/index.ts index d138e3ee..45b03327 100644 --- a/libs/credentials/src/types/index.ts +++ b/libs/credentials/src/types/index.ts @@ -27,7 +27,8 @@ export type BlockchainContext = { } export type EASContext = { - queryGraph: (query: string) => Promise + network: EASNetworks + address: BigNumberish } export type Context = Web2Context | BlockchainContext | EASContext diff --git a/libs/credentials/src/validators/easAttestations/index.test.ts b/libs/credentials/src/validators/easAttestations/index.test.ts index 5cf98aa9..d1cb09b9 100644 --- a/libs/credentials/src/validators/easAttestations/index.test.ts +++ b/libs/credentials/src/validators/easAttestations/index.test.ts @@ -1,65 +1,27 @@ -import { validateCredentials } from "../.." +import { EASNetworks, validateCredentials } from "../.." import easAttestations from "./index" -describe("EASAttestations", () => { - const queryGraphMocked = { - queryGraph: jest.fn() - } - - queryGraphMocked.queryGraph.mockReturnValue([ - { - id: "0x52561c95029d9f2335839ddc96a69ee9737a18e2a781e64659b7bd645ccb8efc", - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", - attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", - revocable: true, - revoked: false, - schemaId: - "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", - isOffchain: false - }, - { - id: "0xee06a022c7d55f67bac213d6b2cd384a899ef79a57f1f5f148e45c313b4fdebe", - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", - attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", - revocable: true, - revoked: false, - schemaId: - "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", - isOffchain: false - }, - { - id: "0xfbc0f1aac4379c18fa9a5b6493825234a8ca82a2a296148465d150c2e64c6202", - recipient: "0x0000000000000000000000000000000000000000", - attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", - revocable: true, - revoked: false, - schemaId: - "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", - isOffchain: false - }, - { - id: "0x227510204bcfe7b543388b82c6e02aafe7b0d0a20e4f159794e8121611aa601b", - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", - attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", - revocable: true, - revoked: false, - schemaId: - "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", - isOffchain: false - } - ]) +jest.mock("../..", () => ({ + EASNetworks: { ETHEREUM_SEPOLIA: "sepolia" }, + validateCredentials: jest.fn() +})) +describe("EASAttestations", () => { it("Should return true if an account has greater than or equal to 3 attestations", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => true) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 3, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8" + schemaId: + "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c" } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -67,12 +29,13 @@ describe("EASAttestations", () => { }) it("Should return true if the given optional criterias are satisfied", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => true) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 1, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", schemaId: "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", @@ -82,7 +45,8 @@ describe("EASAttestations", () => { } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -90,12 +54,13 @@ describe("EASAttestations", () => { }) it("Should return false if the attester optional criteria doesn't match", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => false) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 1, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d4", schemaId: "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", @@ -105,7 +70,8 @@ describe("EASAttestations", () => { } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d4" } ) @@ -113,12 +79,13 @@ describe("EASAttestations", () => { }) it("Should return false if the schemaId optional criteria doesn't match", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => false) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 1, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", schemaId: "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5d", @@ -128,7 +95,8 @@ describe("EASAttestations", () => { } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -136,12 +104,13 @@ describe("EASAttestations", () => { }) it("Should return false if the revocable optional criteria doesn't match", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => false) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 1, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", schemaId: "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", @@ -151,7 +120,8 @@ describe("EASAttestations", () => { } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -159,12 +129,13 @@ describe("EASAttestations", () => { }) it("Should return false if the revoked optional criteria doesn't match", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => false) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 1, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", schemaId: "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", @@ -174,7 +145,8 @@ describe("EASAttestations", () => { } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -182,12 +154,13 @@ describe("EASAttestations", () => { }) it("Should return false if the isOffchain optional criteria doesn't match", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => false) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 1, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8", attester: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3", schemaId: "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", @@ -197,7 +170,8 @@ describe("EASAttestations", () => { } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -205,16 +179,20 @@ describe("EASAttestations", () => { }) it("Should return false if an account has less than 3 attestations", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => false) + const result = await validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 3, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae9" + schemaId: + "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c" } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x" } ) @@ -222,6 +200,10 @@ describe("EASAttestations", () => { }) it("Should throw an error if a mandatory criteria parameter is missing", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => { + throw new Error("Parameter 'minAttestations' has not been defined") + }) + const fun = () => validateCredentials( { @@ -229,7 +211,8 @@ describe("EASAttestations", () => { criteria: {} }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -239,18 +222,26 @@ describe("EASAttestations", () => { }) it("Should throw an error if a criteria parameter should not exist", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => { + throw new Error( + "Parameter 'test' should not be part of the criteria" + ) + }) + const fun = () => validateCredentials( { id: easAttestations.id, criteria: { minAttestations: 1, - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae9", + schemaId: + "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", test: 123 } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) @@ -260,17 +251,23 @@ describe("EASAttestations", () => { }) it("Should throw a type error if a criteria parameter has the wrong type", async () => { + ;(validateCredentials as any).mockImplementationOnce(async () => { + throw new Error("Parameter 'minAttestations' is not a number") + }) + const fun = () => validateCredentials( { id: easAttestations.id, criteria: { minAttestations: "1", - recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae9" + schemaId: + "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c" } }, { - queryGraph: queryGraphMocked.queryGraph + network: EASNetworks.ETHEREUM_SEPOLIA, + address: "0x63A35A52c0ac206108EBbf559E4C7109dAd281d3" } ) diff --git a/libs/credentials/src/validators/easAttestations/index.ts b/libs/credentials/src/validators/easAttestations/index.ts index f428bb33..aa93eeb8 100644 --- a/libs/credentials/src/validators/easAttestations/index.ts +++ b/libs/credentials/src/validators/easAttestations/index.ts @@ -1,10 +1,10 @@ -import { Context, EASContext, Validator } from "../.." +import { Context, Validator } from "../.." +import provider from "../../providers/eas" export type Criteria = { minAttestations: number - recipient: string - attester?: string schemaId?: string + attester?: string revocable?: boolean revoked?: boolean isOffchain?: boolean @@ -18,10 +18,6 @@ const validator: Validator = { type: "number", optional: false }, - recipient: { - type: "string", - optional: false - }, attester: { type: "string", optional: true @@ -51,11 +47,9 @@ const validator: Validator = { * @returns True if the user meets the criteria. */ async validate(criteria: Criteria, context: Context) { - if ("queryGraph" in context) { - const getAttestations = (context as EASContext).queryGraph - + if ("network" in context) { const { - recipient, + minAttestations, attester, schemaId, revocable, @@ -63,55 +57,45 @@ const validator: Validator = { isOffchain } = criteria - const attestations = await getAttestations(` - query { - attestations { - recipient - attester - revocable - revoked - schemaId - isOffchain - } - } - `) + let whereConditions = `recipient: { equals: "${context.address}" }` + + if (attester !== undefined && attester !== null) { + whereConditions += `attester: { equals: "${attester}" },` + } + + if (schemaId !== undefined && schemaId !== null) { + whereConditions += `schemaId: { equals: "${schemaId}" },` + } - const filteredAttestations = attestations.filter( - (attestation: any) => { - // Criteria checks. - if (attestation.recipient !== recipient) return false + if (revocable !== undefined && revocable !== null) { + whereConditions += `revocable: { equals: ${revocable} },` + } - if ( - attester !== undefined && - attestation.attester !== attester - ) - return false - if ( - schemaId !== undefined && - attestation.schemaId !== schemaId - ) - return false - if ( - revocable !== undefined && - attestation.revocable !== revocable - ) - return false - if ( - revoked !== undefined && - attestation.revoked !== revoked - ) - return false - if ( - isOffchain !== undefined && - attestation.isOffchain !== isOffchain - ) - return false + if (revoked !== undefined && revoked !== null) { + whereConditions += `revoked: { equals: ${revoked} },` + } - return true + if (isOffchain !== undefined && isOffchain !== null) { + whereConditions += `isOffchain: { equals: ${isOffchain} },` + } + + const query = `query { + attestations(where: { ${whereConditions} }) { + recipient + attester + schemaId + revocable + revoked + isOffchain } + }` + + const attestations = await provider.queryGraph( + context.network, + query ) - return filteredAttestations.length >= criteria.minAttestations + return attestations.data.attestations.length >= minAttestations } throw new Error("No recipient value found")