Skip to content

Commit

Permalink
Merge pull request #4 from gitcoinco/eas-updates
Browse files Browse the repository at this point in the history
EAS contract config with round network
  • Loading branch information
carlbarrdahl authored Apr 6, 2024
2 parents 7cf3ad8 + 4191a3e commit 0243b9c
Show file tree
Hide file tree
Showing 22 changed files with 183 additions and 107 deletions.
1 change: 1 addition & 0 deletions public/.well-known/walletconnect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8e64b458-f676-4d5d-915b-9a15170b65b7=bbf017f88c8435a30e24b0575fc87e18a21a1ed561ec5e5cab4fb5349699e32b
59 changes: 44 additions & 15 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,6 @@ export const theme = {
colorMode: "light",
};

export const eas = {
contracts: {
eas:
process.env.NEXT_PUBLIC_EAS_CONTRACT_ADDRESS ??
"0x4200000000000000000000000000000000000021",
schemaRegistry:
process.env.NEXT_PUBLIC_EAS_SCHEMA_REGISTRY_ADDRESS ??
"0x4200000000000000000000000000000000000020",
},
schemas: {
metadata: process.env.NEXT_PUBLIC_METADATA_SCHEMA!,
approval: process.env.NEXT_PUBLIC_APPROVAL_SCHEMA!,
},
};

export const networks = {
mainnet: "mainnet",
optimism: "optimism",
Expand All @@ -46,7 +31,50 @@ export const networks = {
sepolia: "sepolia",
base: "base",
baseGoerli: "baseGoerli",
celo: "celo",
} as const;

export const eas = {
contracts: {
[networks.mainnet]: {
eas: "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587",
registry: "0xA7b39296258348C78294F95B872b282326A97BDF",
},
[networks.arbitrum]: {
eas: "0xbD75f629A22Dc1ceD33dDA0b68c546A1c035c458",
registry: "0xA310da9c5B885E7fb3fbA9D66E9Ba6Df512b78eB",
},
[networks.celo]: {
eas: "0x72E1d8ccf5299fb36fEfD8CC4394B8ef7e98Af92",
registry: "0x5ece93bE4BDCF293Ed61FA78698B594F2135AF34",
schemas: {
metadata:
"0xf01bd22db2b104f6a7096f3625307b1c03b863b73f08e71557ebf1adc20cf1bf",
approval:
"0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c",
},
},
[networks.linea]: {
eas: "0xaEF4103A04090071165F78D45D83A0C0782c2B2a",
registry: "0x55D26f9ae0203EF95494AE4C170eD35f4Cf77797",
},
[networks.sepolia]: {
eas: "0xC2679fBD37d54388Ce493F1DB75320D236e1815e",
registry: "0x0a7E2Ff54e76B8E6659aedc9103FB21c038050D0",
},
default: {
eas: "0x4200000000000000000000000000000000000021",
registry: "0x4200000000000000000000000000000000000020",
schemas: {
approval:
"0x858e0bc94997c072d762d90440966759b57c8bca892d4c9447d2eeb205f14c69",
metadata:
"0xd00c966351896bd3dc37d22017bf1ef23165f859d7546a2aba12a01623dec912",
},
},
},
};

export const supportedNetworks = Object.values(networks).map((chain) => ({
...allChains[chain],
chain,
Expand All @@ -64,6 +92,7 @@ export const easApiEndpoints = {
[networks.sepolia]: "https://sepolia.easscan.org/graphql",
[networks.base]: "https://base.easscan.org/graphql",
[networks.baseGoerli]: "https://base-goerli.easscan.org/graphql",
[networks.celo]: "https://celo.easscan.org/graphql",
} as const;

export const impactCategories = {
Expand Down
4 changes: 0 additions & 4 deletions src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ export const env = createEnv({
NEXT_PUBLIC_SIGN_STATEMENT: z.string().optional(),
NEXT_PUBLIC_FEEDBACK_URL: z.string().default("#"),

NEXT_PUBLIC_EAS_CONTRACT_ADDRESS: z.string(),

NEXT_PUBLIC_APPROVAL_SCHEMA: z.string().startsWith("0x"),
NEXT_PUBLIC_METADATA_SCHEMA: z.string().startsWith("0x"),

Expand Down Expand Up @@ -72,8 +70,6 @@ export const env = createEnv({

NEXT_PUBLIC_FEEDBACK_URL: process.env.NEXT_PUBLIC_FEEDBACK_URL,

NEXT_PUBLIC_EAS_CONTRACT_ADDRESS:
process.env.NEXT_PUBLIC_EAS_CONTRACT_ADDRESS,
NEXT_PUBLIC_WALLETCONNECT_ID: process.env.NEXT_PUBLIC_WALLETCONNECT_ID,
NEXT_PUBLIC_ALCHEMY_ID: process.env.NEXT_PUBLIC_ALCHEMY_ID,

Expand Down
11 changes: 7 additions & 4 deletions src/features/applications/hooks/useApproveApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import { eas } from "~/config";
import { useEthersSigner } from "~/hooks/useEthersSigner";
import { toast } from "sonner";
import { useCurrentRound } from "~/features/rounds/hooks/useRound";
import { getContracts } from "~/lib/eas/createEAS";

export function useApproveApplication(opts?: { onSuccess?: () => void }) {
const attest = useAttest();
const signer = useEthersSigner();

const { data: round } = useCurrentRound();

const roundId = String(round?.id);

return useMutation({
onSuccess: () => {
toast.success("Application approved successfully!");
Expand All @@ -25,16 +24,20 @@ export function useApproveApplication(opts?: { onSuccess?: () => void }) {
}),
mutationFn: async (applicationIds: string[]) => {
if (!signer) throw new Error("Connect wallet first");
if (!round?.network) throw new Error("Round network not configured");

const contracts = getContracts(round.network);

const attestations = await Promise.all(
applicationIds.map((refUID) =>
createAttestation(
{
values: { type: "application", round: roundId },
schemaUID: eas.schemas.approval,
values: { type: "application", round: round.id },
schemaUID: contracts.schemas.approval,
refUID,
},
signer,
contracts,
),
),
);
Expand Down
1 change: 0 additions & 1 deletion src/features/applications/hooks/useApprovedApplications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useCurrentRound } from "~/features/rounds/hooks/useRound";
import { api } from "~/utils/api";

export function useApprovedApplications(ids?: string[]) {
Expand Down
8 changes: 6 additions & 2 deletions src/features/applications/hooks/useCreateApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useAttest, useCreateAttestation } from "~/hooks/useEAS";
import type { Application, Profile } from "../types";
import { type TransactionError } from "~/features/voters/hooks/useApproveVoters";
import { useCurrentRound } from "~/features/rounds/hooks/useRound";
import { getContracts } from "~/lib/eas/createEAS";

export function useCreateApplication({
onSuccess,
Expand All @@ -29,11 +30,14 @@ export function useCreateApplication({
}) => {
if (!roundId) throw new Error("Round ID must be defined");
console.log("Uploading profile and application metadata");
if (!round?.network) throw new Error("Round network must be configured");

const contracts = getContracts(round.network);
return Promise.all([
upload.mutateAsync(values.application).then(({ url: metadataPtr }) => {
console.log("Creating application attestation data");
return attestation.mutateAsync({
schemaUID: eas.schemas.metadata,
schemaUID: contracts.schemas.metadata,
values: {
name: values.application.name,
metadataType: 0, // "http"
Expand All @@ -46,7 +50,7 @@ export function useCreateApplication({
upload.mutateAsync(values.profile).then(({ url: metadataPtr }) => {
console.log("Creating profile attestation data");
return attestation.mutateAsync({
schemaUID: eas.schemas.metadata,
schemaUID: contracts.schemas.metadata,
values: {
name: values.profile.name,
metadataType: 0, // "http"
Expand Down
5 changes: 4 additions & 1 deletion src/features/lists/hooks/useCreateList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useAttest, useCreateAttestation } from "~/hooks/useEAS";
import { type TransactionError } from "~/features/voters/hooks/useApproveVoters";
import { type List } from "../types";
import { useCurrentRound } from "~/features/rounds/hooks/useRound";
import { getContracts } from "~/lib/eas/createEAS";

export function useCreateList({
onSuccess,
Expand All @@ -25,12 +26,14 @@ export function useCreateList({
onError,
mutationFn: async (values: List) => {
console.log("Uploading list metadata");
if (!round?.network) throw new Error("Round network must be configured");
const contracts = getContracts(round.network);
return upload
.mutateAsync(values)
.then(({ url: metadataPtr }) => {
console.log("Creating application attestation data");
return attestation.mutateAsync({
schemaUID: eas.schemas.metadata,
schemaUID: contracts.schemas.metadata,
values: {
name: values.name,
metadataType: 0, // "http"
Expand Down
2 changes: 1 addition & 1 deletion src/features/rounds/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const RoundSchema = z
admins: z.array(EthAddressSchema),
description: z.string().nullable(),
network: z.string().nullable(),
tokenAddress: EthAddressSchema.nullable(),
tokenAddress: EthAddressSchema.or(z.string().nullish()),
poolId: z.number().nullable(),
calculationType: CalculationTypeSchema,
calculationConfig: z
Expand Down
11 changes: 7 additions & 4 deletions src/features/voters/hooks/useApproveVoters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useMutation } from "@tanstack/react-query";
import { createAttestation } from "~/lib/eas/createAttestation";
import { useCurrentRound } from "~/features/rounds/hooks/useRound";
import { api } from "~/utils/api";
import { getContracts } from "~/lib/eas/createEAS";

// TODO: Move this to a shared folders
export type TransactionError = { reason?: string; data?: { message: string } };
Expand All @@ -23,22 +24,24 @@ export function useApproveVoters({
const attest = useAttest();
const signer = useEthersSigner();
const { data: round } = useCurrentRound();
const roundId = String(round?.id);

return useMutation({
mutationFn: async (voters: string[]) => {
if (!signer) throw new Error("Connect wallet first");
if (!roundId) throw new Error("Round ID must be defined");
if (!round) throw new Error("Round must be defined");
if (!round?.network) throw new Error("Round network must be configured");

const contracts = getContracts(round.network);
const attestations = await Promise.all(
voters.map((recipient) =>
createAttestation(
{
values: { type: "voter", round: roundId },
schemaUID: eas.schemas.approval,
values: { type: "voter", round: round.id },
schemaUID: contracts.schemas.approval,
recipient,
},
signer,
contracts,
),
),
);
Expand Down
12 changes: 8 additions & 4 deletions src/hooks/useEAS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,32 @@ import { type MultiAttestationRequest } from "@ethereum-attestation-service/eas-

import { useEthersSigner } from "~/hooks/useEthersSigner";
import { createAttestation } from "~/lib/eas/createAttestation";
import { createEAS } from "~/lib/eas/createEAS";
import { createEAS, getContracts } from "~/lib/eas/createEAS";
import { useCurrentRound } from "~/features/rounds/hooks/useRound";

export function useCreateAttestation() {
const signer = useEthersSigner();
const { data: round } = useCurrentRound();
return useMutation({
mutationFn: async (data: {
values: Record<string, unknown>;
schemaUID: string;
}) => {
if (!signer) throw new Error("Connect wallet first");
return createAttestation(data, signer);
if (!round?.network) throw new Error("Round network not configured");
return createAttestation(data, signer, getContracts(round.network));
},
});
}

export function useAttest() {
const signer = useEthersSigner();
const { data: round } = useCurrentRound();
return useMutation({
mutationFn: async (attestations: MultiAttestationRequest[]) => {
if (!signer) throw new Error("Connect wallet first");
const eas = createEAS(signer);

if (!round?.network) throw new Error("Round network not configured");
const eas = createEAS(signer, round?.network);
return eas.multiAttest(attestations);
},
});
Expand Down
18 changes: 17 additions & 1 deletion src/layouts/DefaultLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from "~/features/rounds/hooks/useRound";
import { useRoundState } from "~/features/rounds/hooks/useRoundState";
import { useSession } from "next-auth/react";
import { Button } from "~/components/ui/Button";
import Link from "next/link";

type Props = PropsWithChildren<
{
Expand All @@ -21,7 +23,8 @@ export const Layout = ({ children, ...props }: Props) => {
const { address } = useAccount();

const domain = useCurrentDomain();
const { data: round } = useCurrentRound();
const { data: round, isPending } = useCurrentRound();

const navLinks = [
{
href: `/${domain}/projects`,
Expand Down Expand Up @@ -51,6 +54,19 @@ export const Layout = ({ children, ...props }: Props) => {
);
}

if (!isPending && !round) {
return (
<BaseLayout>
<div className="flex flex-col items-center gap-4 py-8">
Round not found
<Button as={Link} href={"/"}>
Go home
</Button>
</div>
</BaseLayout>
);
}

return (
<BaseLayout {...props} header={<Header navLinks={navLinks} />}>
{children}
Expand Down
24 changes: 14 additions & 10 deletions src/lib/eas/createAttestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
SchemaRegistry,
type SchemaValue,
type AttestationRequest,
type SchemaRecord,
} from "@ethereum-attestation-service/eas-sdk";
import { type Signer } from "ethers";
import * as config from "~/config";

import { eas } from "~/config";

type Params = {
values: Record<string, unknown>;
Expand All @@ -17,12 +19,21 @@ type Params = {
export async function createAttestation(
params: Params,
signer: Signer,
contracts: typeof eas.contracts.default,
): Promise<AttestationRequest> {
console.log("Getting recipient address");
const recipient = params.recipient ?? (await signer.getAddress());

const schemaRegistry = new SchemaRegistry(contracts.registry);
console.log("Connecting signer to SchemaRegistry...");
schemaRegistry.connect(signer);
console.log("Getting schema record...", params.schemaUID);
const schemaRecord = await schemaRegistry.getSchema({
uid: params.schemaUID,
});

console.log("Encoding attestation data");
const data = await encodeData(params, signer);
const data = await encodeData(params, schemaRecord);

return {
schema: params.schemaUID,
Expand All @@ -36,15 +47,8 @@ export async function createAttestation(
};
}

async function encodeData({ values, schemaUID }: Params, signer: Signer) {
const schemaRegistry = new SchemaRegistry(
config.eas.contracts.schemaRegistry,
);
console.log("Connecting signer to SchemaRegistry...");
schemaRegistry.connect(signer);

async function encodeData({ values }: Params, schemaRecord: SchemaRecord) {
console.log("Getting schema record...");
const schemaRecord = await schemaRegistry.getSchema({ uid: schemaUID });

const schemaEncoder = new SchemaEncoder(schemaRecord.schema);

Expand Down
Loading

0 comments on commit 0243b9c

Please sign in to comment.