Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add generative art minting #274

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions prisma/migrations/20240325152421_add_mint_request/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- CreateTable
CREATE TABLE "MintRequest" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"walletAddress" TEXT NOT NULL,
"stringifiedPublicKeys" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "MintRequest_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "MintRequest" ADD CONSTRAINT "MintRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
12 changes: 11 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ model User {
admin Admin?
buidlMint BuidlMint[]
itemRedeemed ItemRedeemed[]
MintRequest MintRequest[]
}

model Admin {
Expand Down Expand Up @@ -119,7 +120,7 @@ model Quest {
summonId String? @unique
buidlReward Int
itemId Int? @unique
priority Int @default(0)
priority Int @default(0)
isHidden Boolean @default(false)
createdAt DateTime @default(now())
userRequirements UserRequirement[]
Expand Down Expand Up @@ -248,3 +249,12 @@ model ItemRedeemed {
user User @relation(fields: [userId], references: [id])
item Item @relation(fields: [itemId], references: [id])
}

model MintRequest {
id Int @id @default(autoincrement())
userId Int
walletAddress String
stringifiedPublicKeys String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
54 changes: 54 additions & 0 deletions src/pages/api/mint/art.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@/lib/server/prisma";
import { verifyAuthToken } from "@/lib/server/auth";

interface MintRequest {
authToken: string;
walletAddress: string;
stringifiedPublicKeys: string;
}

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}

try {
const { authToken, walletAddress, stringifiedPublicKeys }: MintRequest =
req.body;

if (!authToken || !walletAddress || !stringifiedPublicKeys) {
return res.status(400).json({ message: "Missing required fields" });
}

const userId = await verifyAuthToken(authToken);
if (!userId) {
return res.status(401).json({ message: "Invalid or expired authToken" });
}

const existingMintRequest = await prisma.mintRequest.findFirst({
where: { userId },
});
if (existingMintRequest) {
return res
.status(400)
.json({ message: "User already has a mint request" });
}

await prisma.mintRequest.create({
data: {
userId: userId,
walletAddress,
stringifiedPublicKeys,
},
});

return res.status(200).json({});
} catch (error) {
console.error("Request error: ", error);
res.status(500).json({ message: "Error processing request" });
}
}
7 changes: 5 additions & 2 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,10 @@ export default function Social() {
<Link href="/leaderboard">
<Button size="sm">View leaderboard</Button>
</Link>
{claveInfo?.claveWalletAddress ? (
<Link href="/mint">
<Button size="sm">Mint Generative Art NFT</Button>
</Link>
{/* {claveInfo?.claveWalletAddress ? (
<Button onClick={() => setCashOutOpen(true)} size="sm">
Sync with Clave
</Button>
Expand All @@ -525,7 +528,7 @@ export default function Social() {
</Button>
) : (
<></>
)}
)} */}
</div>
</div>
</div>
Expand Down
103 changes: 103 additions & 0 deletions src/pages/mint/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Button } from "@/components/Button";
import { Input } from "@/components/Input";
import { FormStepLayout } from "@/layouts/FormStepLayout";
import {
getAuthToken,
getLocationSignatures,
getUsers,
} from "@/lib/client/localStorage";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { toast } from "sonner";

const MintPage = () => {
const [walletAddress, setWalletAddress] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const router = useRouter();

const handleMint = async (event: React.FormEvent<Element>) => {
event.preventDefault();
if (!walletAddress || walletAddress.slice(0, 2) !== "0x") {
toast.error("Please enter a valid wallet address 0x...");
return;
}
setLoading(true);

const token = getAuthToken();
if (!token) {
toast.error("You must be logged in to mint an NFT");
setLoading(false);
return;
}

const users = getUsers();
const userSignaturePublicKeys: string[] = Object.values(users)
.filter((user) => user.sigPk && user.inTs)
.map((user) => user.sigPk!);

const locations = getLocationSignatures();
const locationSignaturePublicKeys: string[] = Object.values(locations)
.filter((location) => location.pk)
.map((location) => location.pk);

const stringifiedPublicKeys = JSON.stringify({
users: userSignaturePublicKeys,
locations: locationSignaturePublicKeys,
});

const response = await fetch("/api/mint/art", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
authToken: token.value,
walletAddress,
stringifiedPublicKeys,
}),
});

if (!response.ok) {
const error = await response.json();
console.error("Error minting NFT: ", error.messsage);
toast.error("Error minting NFT! Please try again later.");
} else {
toast.info(
"Successfully processed mint request - please wait a few days for the NFT to be minted."
);
}
setLoading(false);

router.push("/");
};

return (
<FormStepLayout
title="Mint generative art NFT"
description="Input a wallet address to receive a unique NFT from your taps at ETHDenver! Note: this will publish the public keys of the signatures you've collected on IPFS."
onSubmit={handleMint}
actions={
<div className="flex flex-col gap-4">
<Button loading={loading} type="submit">
Submit
</Button>
<Link href="/" className="link text-center">
Back
</Link>
</div>
}
>
<Input
label="Wallet address"
placeholder="0x1234..."
type="text"
name="walletAddress"
value={walletAddress}
onChange={(event) => setWalletAddress(event.target.value)}
required
/>
</FormStepLayout>
);
};
export default MintPage;