Skip to content

Commit

Permalink
feat: refactor attestation methods
Browse files Browse the repository at this point in the history
  • Loading branch information
alvaroraminelli committed Feb 7, 2024
1 parent eba43e3 commit ba1a847
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 97 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,12 @@ type UseName = {
### hasVerifiedAttestations

```tsx
import { base } from 'viem/chains';
import { hasVerifiedAttestations } from '@coinbase/onchainkit';

const hasVerifiedAttestation = await hasVerifiedAttestations(
'0x44a7D120beA87455071cebB841eF91E6Ae21bC1a',
base,
'0x1234567890abcdef1234567890abcdef12345678',
['VERIFIED ACCOUNT'],
);
```
Expand All @@ -419,7 +421,10 @@ const hasVerifiedAttestation = await hasVerifiedAttestations(
```tsx
import { getVerifiedAttestations } from '@coinbase/onchainkit';

const attestations = await getVerifiedAttestation('0x44a7D120beA87455071cebB841eF91E6Ae21bC1a');
const attestations = await getVerifiedAttestation(
base,
'0x1234567890abcdef1234567890abcdef12345678',
);
```

<br />
Expand Down
43 changes: 20 additions & 23 deletions src/core/attestation.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
import type { Address } from "viem";
import { gql, request } from "graphql-request";
import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'
import { Attestation, AttestationSchema } from "./types";
import { AttestationSchema } from "./types";
import type { Chain } from 'viem'

// TODO: should resolve based on chain id?
export const indexApi = "https://base.easscan.org/graphql";
export const easScanGraphQLAPI = "https://base.easscan.org/graphql";

const schemaUids: Record<number, Record<AttestationSchema, string>> = {
const supportedChains: Record<number, { schemaUids: Record<string, string>, attesterAddresses: string[] }> = {
[base.id]: {
"VERIFIED COUNTRY":"0x1801901fabd0e6189356b4fb52bb0ab855276d84f7ec140839fbd1f6801ca065",
"VERIFIED ACCOUNT": "0xf8b05c79f090979bf4a80270aba232dff11a10d9ca55c4f88de95317970f0de9"
schemaUids: {
"VERIFIED COUNTRY":"0x1801901fabd0e6189356b4fb52bb0ab855276d84f7ec140839fbd1f6801ca065",
"VERIFIED ACCOUNT": "0xf8b05c79f090979bf4a80270aba232dff11a10d9ca55c4f88de95317970f0de9"
},
attesterAddresses: ["0x357458739F90461b99789350868CD7CF330Dd7EE"]
},
};
}

export const attesterAddresses: Record<number, string[]> = {
[base.id]: ["0x357458739F90461b99789350868CD7CF330Dd7EE"],
};
export function isChainSupported(chain: Chain): boolean {
return !!supportedChains[chain.id];
}

// TODO: use client from network but allow passing a custom client
export const client = createPublicClient({
chain: base,
transport: http(),
});

export function schemasToUids(schemas: AttestationSchema[], clientChainId?: number): string[] {
// TODO: better error handling
export function getChainSchemasUids(schemas: AttestationSchema[], clientChainId?: number): string[] {
if (!clientChainId) {
return [];
}
return schemas.map((schema) => schemaUids[clientChainId][schema]);
}
return schemas.map((schema) => supportedChains[clientChainId]['schemaUids'][schema]);
}

export function getAttesterAddresses(chain: Chain): string[] {
return supportedChains[chain.id].attesterAddresses;
}
103 changes: 40 additions & 63 deletions src/core/getAttestation.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import type { Address } from "viem";
import type { Chain } from 'viem';
import { gql, request } from "graphql-request";
import { Attestation, AttestationSchema } from "./types";
import { schemasToUids, client, indexApi, attesterAddresses } from './attestation';
import { getChainSchemasUids, easScanGraphQLAPI, getAttesterAddresses } from './attestation';

const attestationsQuery = gql`
query AttestationsForUsers(
$where: AttestationWhereInput
$orderBy: [AttestationOrderByWithRelationInput!]
$distinct: [AttestationScalarFieldEnum!]
$take: Int
) {
attestations(
where: $where
orderBy: $orderBy
distinct: $distinct
take: $take
) {
query AttestationsForUsers($where: AttestationWhereInput, $orderBy: [AttestationOrderByWithRelationInput!], $distinct: [AttestationScalarFieldEnum!], $take: Int) {
attestations(where: $where, orderBy: $orderBy, distinct: $distinct, take: $take) {
attester
expirationTime
id
Expand All @@ -28,60 +19,46 @@ const attestationsQuery = gql`
}
`;

/**
* Retrieves attestations for a given address and chain, optionally filtered by schemas.
*
* @param chain - The blockchain of interest.
* @param address - The address for which attestations are being queried.
* @param filters - Optional filters including schemas to further refine the query.
* @returns A promise that resolves to an array of Attestations.
* @throws Will throw an error if the request to the GraphQL API fails.
*/
export async function getAttestation(
chain: Chain,
address: Address,
filters?: { schemas?: AttestationSchema[] }
): Promise<Attestation[]> {

let conditions: any = {
attester: {
in: attesterAddresses[client.chain?.id],
},
recipient: {
equals: address,
},
// Not revoked
revoked: {
equals: false,
},
OR: [
{
// No expiration
expirationTime: {
equals: 0,
},
},
{
// Not expired
expirationTime: {
gt: Math.round(Date.now() / 1000),
},
},
],
};

if (filters?.schemas?.length && filters?.schemas?.length > 0) {
conditions.schemaId = {
in: schemasToUids(filters.schemas, client.chain?.id),
try {
const conditions: Record<string, any> = {
attester: { in: getAttesterAddresses(chain) },
recipient: { equals: address },
revoked: { equals: false }, // Not revoked
OR: [
{ expirationTime: { equals: 0 } },
{ expirationTime: { gt: Math.round(Date.now() / 1000)
} }], // Not expired
};

if (filters?.schemas?.length) {
conditions.schemaId = { in: getChainSchemasUids(filters.schemas, chain.id) };
}

const variables = {
where: { AND: [conditions] },
orderBy: [{ timeCreated: "desc" }],
distinct: ["schemaId", "attester"],
take: 10,
};
}

const variables = {
where: {
AND: [conditions],
},
orderBy: [
{
timeCreated: "desc",
},
],
distinct: ["schemaId", "attester"],
take: 10,
};
const data: any = await request(
indexApi,
attestationsQuery,
variables,
);
return data?.attestations || [];
const data: { attestations: Attestation[] } = await request(easScanGraphQLAPI, attestationsQuery, variables);
return data?.attestations || [];
} catch (error) {
console.error(`Error in getAttestation: ${(error as Error).message}`);
throw error;
}
}
31 changes: 22 additions & 9 deletions src/core/hasVerifiedAttestations.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
import type { Address } from "viem";
import type { Chain } from 'viem';
import { AttestationSchema } from "./types";
import { getAttestation } from './getAttestation';
import { schemasToUids, client } from './attestation';

import { isChainSupported, getChainSchemasUids } from './attestation';

/**
* Checks if the specified address has verified attestations for the given chain and expected schemas.
*
* @param chain - The blockchain to check for attestations.
* @param address - The address to check for attestations.
* @param expectedSchemas - An array of attestation schemas that are expected.
* @returns A promise that resolves to a boolean indicating whether the address has the expected attestations.
* @throws Will throw an error if the chain is not supported.
*/
export async function hasVerifiedAttestations(
chain: Chain,
address: Address,
expectedSchemas: AttestationSchema[] = []
): Promise<boolean> {
if (!address || expectedSchemas?.length === 0) {
if (!chain || !address || expectedSchemas.length === 0) {
return false;
}
const schemaUids = schemasToUids(expectedSchemas, client.chain.id);
const attestations = await getAttestation(address, { schemas: expectedSchemas });
const schemasFound = attestations.map(
(attestation) => attestation.schemaId,
);

return schemaUids.every((desiredSchemaUid) => schemasFound.includes(desiredSchemaUid));
if (!isChainSupported(chain)) {
throw new Error(`Chain ${chain.id} is not supported`);
}

const schemaUids = getChainSchemasUids(expectedSchemas, chain.id);
const attestations = await getAttestation(chain, address, { schemas: expectedSchemas });
const schemasFound = attestations.map(attestation => attestation.schemaId);

return schemaUids.every(schemaUid => schemasFound.includes(schemaUid));
}

0 comments on commit ba1a847

Please sign in to comment.