Skip to content

Commit

Permalink
use route handler for applying of grants (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
technophile-04 authored Feb 19, 2024
1 parent 7982fbc commit ae8d7f2
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 66 deletions.
2 changes: 1 addition & 1 deletion packages/nextjs/app/_components/HomepageHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const HomepageHero = () => (
finished SRE or completed one of our batches? This could be your next step in BuidlGuidl’s journey.
</p>
<Link
href="/submit-grant"
href="/apply"
className="btn btn-primary btn-md border-1 border-black rounded-2xl px-14 font-medium shadow-none"
>
Learn More
Expand Down
54 changes: 54 additions & 0 deletions packages/nextjs/app/api/grants/new/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NextResponse } from "next/server";
import { recoverTypedDataAddress } from "viem";
import { createGrant } from "~~/services/database/grants";
import { findUserByAddress } from "~~/services/database/users";
import { EIP_712_DOMAIN, EIP_712_TYPES__APPLY_FOR_GRANT } from "~~/utils/eip712";

type ReqBody = {
title?: string;
description?: string;
askAmount?: string;
signature?: `0x${string}`;
signer?: string;
};

// TODO: We could also add extra validtion of nonce
export async function POST(req: Request) {
try {
const { title, description, askAmount, signature, signer } = (await req.json()) as ReqBody;

if (!title || !description || !askAmount || isNaN(Number(askAmount)) || !signature || !signer) {
return NextResponse.json({ error: "Invalid form details submited" }, { status: 400 });
}

// Verif if the builder is present
const builder = await findUserByAddress(signer);
if (!builder.exists) {
return NextResponse.json({ error: "Only buidlguild builders can submit for grants" }, { status: 401 });
}

const recoveredAddress = await recoverTypedDataAddress({
domain: EIP_712_DOMAIN,
types: EIP_712_TYPES__APPLY_FOR_GRANT,
primaryType: "Message",
message: { title, description, askAmount },
signature: signature,
});

if (recoveredAddress !== signer) {
return NextResponse.json({ error: "Recovered address did not match signer" }, { status: 401 });
}

const grant = await createGrant({
title: title,
description: description,
askAmount: Number(askAmount),
builder: signer,
});

return NextResponse.json({ grant }, { status: 201 });
} catch (e) {
console.error(e);
return NextResponse.json({ error: "Error processing form" }, { status: 500 });
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
"use client";

import { useRouter } from "next/navigation";
import { submitGrantAction } from "../_actions";
import SubmitButton from "./SubmitButton";
import { useAccount, useSignMessage } from "wagmi";
import { useAccount, useSignTypedData } from "wagmi";
import { EIP_712_DOMAIN, EIP_712_TYPES__APPLY_FOR_GRANT } from "~~/utils/eip712";
import { notification } from "~~/utils/scaffold-eth";

const selectOptions = [0.1, 0.25, 0.5, 1];

const Form = () => {
const { signMessageAsync } = useSignMessage();
const { address: connectedAddress } = useAccount();
const { signTypedDataAsync } = useSignTypedData();
const router = useRouter();

const clientFormAction = async (formData: FormData) => {
if (!connectedAddress) {
notification.error("Please connect your wallet");
return;
}

try {
const formState = Object.fromEntries(formData.entries());
if (formState.title === "" || formState.description === "") {
notification.error("Title and description are required");
const title = formData.get("title");
const description = formData.get("description");
const askAmount = formData.get("askAmount");
if (!title || !description || !askAmount) {
notification.error("Please fill all the fields");
return;
}

const signature = await signMessageAsync({ message: JSON.stringify(formState) });
const signedMessageObject = {
signature: signature,
address: connectedAddress,
};
const signature = await signTypedDataAsync({
domain: EIP_712_DOMAIN,
types: EIP_712_TYPES__APPLY_FOR_GRANT,
primaryType: "Message",
message: {
title: title as string,
description: description as string,
askAmount: askAmount as string,
},
});

// server action
const submitGrantActionWithSignedMessage = submitGrantAction.bind(null, signedMessageObject);
await submitGrantActionWithSignedMessage(formData);
const res = await fetch("/api/grants/new", {
method: "POST",
body: JSON.stringify({ title, description, askAmount, signature, signer: connectedAddress }),
});

if (!res.ok) {
const data = await res.json();
throw new Error(data.error || "Error submitting grant proposal");
}

notification.success("Proposal submitted successfully!");
router.push("/");
Expand All @@ -54,6 +72,7 @@ const Form = () => {
placeholder="title"
name="title"
autoComplete="off"
type="text"
/>
</div>
</div>
Expand Down
File renamed without changes.
51 changes: 0 additions & 51 deletions packages/nextjs/app/submit-grant/_actions/index.ts

This file was deleted.

8 changes: 8 additions & 0 deletions packages/nextjs/utils/eip712.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ export const EIP_712_DOMAIN = {
chainId: 10,
} as const;

export const EIP_712_TYPES__APPLY_FOR_GRANT = {
Message: [
{ name: "title", type: "string" },
{ name: "description", type: "string" },
{ name: "askAmount", type: "string" },
],
} as const;

// ToDo. We could add more fields (grant title, builder, etc)
export const EIP_712_TYPES__REVIEW_GRANT = {
Message: [
Expand Down

0 comments on commit ae8d7f2

Please sign in to comment.