Skip to content

Commit

Permalink
Merge pull request #392 from privacy-scaling-explorations/integration…
Browse files Browse the repository at this point in the history
…/eas

Add EAS provider with default `EAS_ATTESTATIONS` validator
  • Loading branch information
vplasencia authored Feb 19, 2024
2 parents a5e0153 + 2c48d03 commit eeec708
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 13 deletions.
8 changes: 4 additions & 4 deletions libs/credentials/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,31 @@ describe("Credentials library", () => {
it("Should add a provider to the list of supported providers", () => {
addProvider({} as any)

expect(providers).toHaveLength(4)
expect(providers).toHaveLength(5)
})
})

describe("# addProviders", () => {
it("Should add 2 providers to the list of supported providers", () => {
addProviders([{} as any, {} as any])

expect(providers).toHaveLength(6)
expect(providers).toHaveLength(7)
})
})

describe("# addValidator", () => {
it("Should add a validator to the list of supported validators", () => {
addValidator({} as any)

expect(validators).toHaveLength(8)
expect(validators).toHaveLength(9)
})
})

describe("# addValidators", () => {
it("Should add 2 validators to the list of supported validators", () => {
addValidators([{} as any, {} as any])

expect(validators).toHaveLength(10)
expect(validators).toHaveLength(11)
})
})

Expand Down
4 changes: 2 additions & 2 deletions libs/credentials/src/providers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Provider } from "./types"
import { github, twitter, blockchain } from "./providers/index"
import { github, twitter, blockchain, eas } from "./providers/index"

const providers: Provider[] = [github, twitter, blockchain]
const providers: Provider[] = [github, twitter, blockchain, eas]

export default providers
22 changes: 22 additions & 0 deletions libs/credentials/src/providers/eas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { EASProvider, EASNetworks } from "../.."
import queryGraph from "../../queryGraph"

// Graph endpoint for ethereum mainnet.
const easDefaultEndpoint = `https://easscan.org/graphql`
const easNetworkEndpoint = (network: EASNetworks) =>
`https://${network}.easscan.org/graphql`

const provider: EASProvider = {
name: "eas",

async queryGraph(network: EASNetworks, query: string) {
return queryGraph(
network === EASNetworks.ETHEREUM
? easDefaultEndpoint
: easNetworkEndpoint(network),
query
)
}
}

export default provider
3 changes: 2 additions & 1 deletion libs/credentials/src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import github from "./github"
import twitter from "./twitter"
import blockchain from "./blockchain"
import eas from "./eas"

export { github, twitter, blockchain }
export { github, twitter, blockchain, eas }
18 changes: 18 additions & 0 deletions libs/credentials/src/queryGraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { request } from "@bandada/utils"

/**
* It returns a function that can be used to query graphs
* data using GraphQL style queries for the EAS provider supported by Bandada.
* @param endpoint The endpoint of the graph.
* @param query The query to execute to fetch the data.
* @returns The function to query the graph.
*/
export default function queryGraph(endpoint: string, query: string) {
request(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
data: JSON.stringify({
query
})
})
}
29 changes: 27 additions & 2 deletions libs/credentials/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { BigNumberish } from "ethers"

export enum EASNetworks {
ETHEREUM = "ethereum",
ETHEREUM_SEPOLIA = "sepolia",
ARBITRUM = "arbitrum",
BASE = "base",
BASE_GOERLI = "base-goerli",
LINEA = "linea",
OPTIMISM = "optimism",
OPTIMISM_GOERLI = "optimism-goerli"
}

export type Web2Context = {
utils?: {
api: (endpoint: string) => Promise<any>
Expand All @@ -16,9 +27,19 @@ export type BlockchainContext = {
blockNumber?: number
}

export type Context = Web2Context | BlockchainContext
export type EASContext = {
recipient: BigNumberish
queryGraph: (query: string) => Promise<any>
attester?: BigNumberish
schemaId?: BigNumberish
}

export type Context = Web2Context | BlockchainContext | EASContext

export type Handler = (criteria: any, context: Context) => Promise<boolean>
export type Handler = (
criteria: any,
context: Context
) => Promise<boolean> | boolean

export interface Provider {
name: string
Expand All @@ -42,6 +63,10 @@ export interface BlockchainProvider extends Provider {
getJsonRpcProvider: (url: string) => Promise<any>
}

export interface EASProvider extends Provider {
queryGraph: (network: EASNetworks, query: string) => Promise<any>
}

export interface Validator {
id: string
criteriaABI: any
Expand Down
2 changes: 1 addition & 1 deletion libs/credentials/src/validateCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default async function validateCredentials(
})
}

if ("getAddress" in provider) {
if ("getAddress" in provider || "queryGraph" in provider) {
return validator.validate(criteria, {
...context
})
Expand Down
6 changes: 4 additions & 2 deletions libs/credentials/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
twitterFollowers,
twitterFollowingUser,
blockchainTransactions,
blockchainBalance
blockchainBalance,
easAttestations
} from "./validators/index"

const validators: Validator[] = [
Expand All @@ -16,7 +17,8 @@ const validators: Validator[] = [
twitterFollowers,
twitterFollowingUser,
blockchainTransactions,
blockchainBalance
blockchainBalance,
easAttestations
]

export default validators
139 changes: 139 additions & 0 deletions libs/credentials/src/validators/easAttestations/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { validateCredentials } from "../.."
import easAttestations from "./index"

describe("EASAttestations", () => {
const queryGraphMocked = {
queryGraph: jest.fn()
}

it("Should return true if an account has greater than or equal to 3 attestations", async () => {
queryGraphMocked.queryGraph.mockReturnValue([
{
id: "0x52561c95029d9f2335839ddc96a69ee9737a18e2a781e64659b7bd645ccb8efc",
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8"
},
{
id: "0xee06a022c7d55f67bac213d6b2cd384a899ef79a57f1f5f148e45c313b4fdebe",
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8"
},
{
id: "0xfbc0f1aac4379c18fa9a5b6493825234a8ca82a2a296148465d150c2e64c6202",
recipient: "0x0000000000000000000000000000000000000000"
},
{
id: "0x227510204bcfe7b543388b82c6e02aafe7b0d0a20e4f159794e8121611aa601b",
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8"
}
])

const result = await validateCredentials(
{
id: easAttestations.id,
criteria: {
minAttestations: 3
}
},
{
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8",
queryGraph: queryGraphMocked.queryGraph
}
)

expect(result).toBeTruthy()
})

it("Should return false if an account has less than 3 attestations", async () => {
queryGraphMocked.queryGraph.mockReturnValue([
{
id: "0x52561c95029d9f2335839ddc96a69ee9737a18e2a781e64659b7bd645ccb8efc",
recipient: "0x0000000000000000000000000000000000000000"
},
{
id: "0xee06a022c7d55f67bac213d6b2cd384a899ef79a57f1f5f148e45c313b4fdebe",
recipient: "0x0000000000000000000000000000000000000000"
},
{
id: "0xfbc0f1aac4379c18fa9a5b6493825234a8ca82a2a296148465d150c2e64c6202",
recipient: "0x0000000000000000000000000000000000000000"
},
{
id: "0x227510204bcfe7b543388b82c6e02aafe7b0d0a20e4f159794e8121611aa601b",
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8"
}
])

const result = await validateCredentials(
{
id: easAttestations.id,
criteria: {
minAttestations: 3
}
},
{
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae8",
queryGraph: queryGraphMocked.queryGraph
}
)

expect(result).toBeFalsy()
})

it("Should throw an error if a criteria parameter is missing", async () => {
const fun = () =>
validateCredentials(
{
id: easAttestations.id,
criteria: {}
},
{
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae9",
queryGraph: queryGraphMocked.queryGraph
}
)

await expect(fun).rejects.toThrow(
"Parameter 'minAttestations' has not been defined"
)
})

it("Should throw an error if a criteria parameter should not exist", async () => {
const fun = () =>
validateCredentials(
{
id: easAttestations.id,
criteria: {
minAttestations: 1,
test: 123
}
},
{
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae9",
queryGraph: queryGraphMocked.queryGraph
}
)

await expect(fun).rejects.toThrow(
"Parameter 'test' should not be part of the criteria"
)
})

it("Should throw a type error if a criteria parameter has the wrong type", async () => {
const fun = () =>
validateCredentials(
{
id: easAttestations.id,
criteria: {
minAttestations: "1"
}
},
{
recipient: "0x9aB3971e1b065701C72C5f3cAFbF33118dC51ae9",
queryGraph: queryGraphMocked.queryGraph
}
)

await expect(fun).rejects.toThrow(
"Parameter 'minAttestations' is not a number"
)
})
})
46 changes: 46 additions & 0 deletions libs/credentials/src/validators/easAttestations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Context, EASContext, Validator } from "../.."

export type Criteria = {
minAttestations: number
}

const validator: Validator = {
id: "EAS_ATTESTATIONS",

criteriaABI: {
minAttestations: "number"
},

/**
* It checks if a user has greater than or equal to 'minAttestations' attestations.
* @param criteria The criteria used to check user's credentials.
* @param context Context variables.
* @returns True if the user meets the criteria.
*/
async validate(criteria: Criteria, context: Context) {
if ("recipient" in context) {
const { recipient } = context as EASContext

const getAttestations = (context as EASContext).queryGraph

const attestations = await getAttestations(`
query {
attestations {
id
recipient
}
}
`)

const recipientAttestations = attestations.filter(
(attestation: any) => attestation.recipient === recipient
)

return recipientAttestations.length >= criteria.minAttestations
}

throw new Error("No recipient value found")
}
}

export default validator
4 changes: 3 additions & 1 deletion libs/credentials/src/validators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import twitterFollowers from "./twitterFollowers"
import twitterFollowingUser from "./twitterFollowingUser"
import blockchainTransactions from "./blockchainTransactions"
import blockchainBalance from "./blockchainBalance"
import easAttestations from "./easAttestations"

export {
githubFollowers,
Expand All @@ -13,5 +14,6 @@ export {
twitterFollowingUser,
githubPersonalStars,
blockchainTransactions,
blockchainBalance
blockchainBalance,
easAttestations
}

0 comments on commit eeec708

Please sign in to comment.