Skip to content

Commit

Permalink
Init template (#1)
Browse files Browse the repository at this point in the history
* feat: initialize template

* chore: bump minikit

* fix: async handlers

* fix: to client side
  • Loading branch information
michalstruck authored Oct 31, 2024
1 parent 34003b1 commit d88b62a
Show file tree
Hide file tree
Showing 27 changed files with 4,212 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
APP_ID="INSERT_APPID"
DEV_PORTAL_API_KEY="APIKEY"
WLD_CLIENT_ID=
WLD_CLIENT_SECRET=
NEXTAUTH_URL=http://localhost:3000
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

.env
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
# minikit-next-template
Template repository for building a mini app in NextJS
## This template provides a minimal setup to get Next.js working with MiniKit

## Setup

```bash
cp .env.example .env
pnpm i
pnpm dev

```

To run as a mini app choose a production app in the dev portal and use NGROK to tunnel. Set the `NEXTAUTH_URL` and the redirect url if using sign in with worldcoin to that ngrok url

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View docs: [Docs](https://minikit-docs.vercel.app/mini-apps)

[Developer Portal](https://developer.worldcoin.org/)
36 changes: 36 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import NextAuth, { NextAuthOptions } from "next-auth";

export const authOptions: NextAuthOptions = {
secret: process.env.NEXTAUTH_SECRET,

providers: [
{
id: "worldcoin",
name: "Worldcoin",
type: "oauth",
wellKnown: "https://id.worldcoin.org/.well-known/openid-configuration",
authorization: { params: { scope: "openid" } },
clientId: process.env.WLD_CLIENT_ID,
clientSecret: process.env.WLD_CLIENT_SECRET,
idToken: true,
checks: ["state", "nonce", "pkce"],
profile(profile) {
return {
id: profile.sub,
name: profile.sub,
verificationLevel:
profile["https://id.worldcoin.org/v1"].verification_level,
};
},
},
],
callbacks: {
async signIn({ user }) {
return true;
},
},
debug: process.env.NODE_ENV === "development",
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
44 changes: 44 additions & 0 deletions app/api/confirm-payment/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { MiniAppPaymentSuccessPayload } from "@worldcoin/minikit-js";
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";

interface IRequestPayload {
payload: MiniAppPaymentSuccessPayload;
}

export async function POST(req: NextRequest) {
const { payload } = (await req.json()) as IRequestPayload;

// IMPORTANT: Here we should fetch the reference you created in /initiate-payment to ensure the transaction we are verifying is the same one we initiated
// const reference = getReferenceFromDB();
const cookieStore = cookies();

const reference = cookieStore.get("payment-nonce")?.value;

console.log(reference);

if (!reference) {
return NextResponse.json({ success: false });
}
console.log(payload);
// 1. Check that the transaction we received from the mini app is the same one we sent
if (payload.reference === reference) {
const response = await fetch(
`https://developer.worldcoin.org/api/v2/minikit/transaction/${payload.transaction_id}?app_id=${process.env.APP_ID}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${process.env.DEV_PORTAL_API_KEY}`,
},
}
);
const transaction = await response.json();
// 2. Here we optimistically confirm the transaction.
// Otherwise, you can poll until the status == mined
if (transaction.reference == reference && transaction.status != "failed") {
return NextResponse.json({ success: true });
} else {
return NextResponse.json({ success: false });
}
}
}
17 changes: 17 additions & 0 deletions app/api/initiate-payment/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
const uuid = crypto.randomUUID().replace(/-/g, "");

// TODO: Store the ID field in your database so you can verify the payment later
cookies().set({
name: "payment-nonce",
value: uuid,
httpOnly: true,
});

console.log(uuid);

return NextResponse.json({ id: uuid });
}
35 changes: 35 additions & 0 deletions app/api/verify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
verifyCloudProof,
IVerifyResponse,
ISuccessResult,
} from "@worldcoin/minikit-js";
import { NextRequest, NextResponse } from "next/server";

interface IRequestPayload {
payload: ISuccessResult;
action: string;
signal: string | undefined;
}

export async function POST(req: NextRequest) {
const { payload, action, signal } = (await req.json()) as IRequestPayload;
const app_id = process.env.APP_ID as `app_${string}`;
const verifyRes = (await verifyCloudProof(
payload,
app_id,
action,
signal
)) as IVerifyResponse; // Wrapper on this

console.log(verifyRes);

if (verifyRes.success) {
// This is where you should perform backend actions if the verification succeeds
// Such as, setting a user as "verified" in a database
return NextResponse.json({ verifyRes, status: 200 });
} else {
// This is where you should handle errors from the World ID /verify endpoint.
// Usually these errors are due to a user having already verified.
return NextResponse.json({ verifyRes, status: 400 });
}
}
Binary file added app/favicon.ico
Binary file not shown.
33 changes: 33 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}
37 changes: 37 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import MiniKitProvider from "@/components/minikit-provider";
import dynamic from "next/dynamic";
import NextAuthProvider from "@/components/next-auth-provider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const ErudaProvider = dynamic(
() => import("../components/Eruda").then((c) => c.ErudaProvider),
{
ssr: false,
}
);
return (
<html lang="en">
<NextAuthProvider>
<ErudaProvider>
<MiniKitProvider>
<body className={inter.className}>{children}</body>
</MiniKitProvider>
</ErudaProvider>
</NextAuthProvider>
</html>
);
}
13 changes: 13 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { PayBlock } from "@/components/Pay";
import { SignIn } from "@/components/SignIn";
import { VerifyBlock } from "@/components/Verify";

export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24 gap-y-3">
<SignIn />
<VerifyBlock />
<PayBlock />
</main>
);
}
18 changes: 18 additions & 0 deletions components/Eruda/eruda-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";

import eruda from "eruda";
import { ReactNode, useEffect } from "react";

export const Eruda = (props: { children: ReactNode }) => {
useEffect(() => {
if (typeof window !== "undefined") {
try {
eruda.init();
} catch (error) {
console.log("Eruda failed to initialize", error);
}
}
}, []);

return <>{props.children}</>;
};
15 changes: 15 additions & 0 deletions components/Eruda/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import dynamic from "next/dynamic";
import { ReactNode } from "react";

const Eruda = dynamic(() => import("./eruda-provider").then((c) => c.Eruda), {
ssr: false,
});

export const ErudaProvider = (props: { children: ReactNode }) => {
if (process.env.NEXT_PUBLIC_APP_ENV === "production") {
return props.children;
}
return <Eruda>{props.children}</Eruda>;
};
Loading

0 comments on commit d88b62a

Please sign in to comment.