Demo codes from Worldcoin NextJS Template Repository:
app/api/auth/[...nextauth]/route.ts:
import NextAuth, { NextAuthOptions } from "next-auth";
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 };
app/api/confirm-payment/route.ts:
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 });
}
}
}
app/api/initiate-payment/route.ts:
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 });
}
app/api/verify/route.ts:
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 });
}
}
app/pages.tsx:
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>
);
}
app/layout.tsx:
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>
);
}
components/Verify/index.tsx:
"use client";
import {
MiniKit,
VerificationLevel,
ISuccessResult,
MiniAppVerifyActionErrorPayload,
IVerifyResponse,
} from "@worldcoin/minikit-js";
import { useCallback, useState } from "react";
export type VerifyCommandInput = {
action: string;
signal?: string;
verification_level?: VerificationLevel; // Default: Orb
};
const verifyPayload: VerifyCommandInput = {
action: "test-action", // This is your action ID from the Developer Portal
signal: "",
verification_level: VerificationLevel.Orb, // Orb | Device
};
export const VerifyBlock = () => {
const [handleVerifyResponse, setHandleVerifyResponse] = useState<
MiniAppVerifyActionErrorPayload | IVerifyResponse | null
>(null);
const handleVerify = useCallback(async () => {
if (!MiniKit.isInstalled()) {
console.warn("Tried to invoke 'verify', but MiniKit is not installed.");
return null;
}
const { finalPayload } = await MiniKit.commandsAsync.verify(verifyPayload);
// no need to verify if command errored
if (finalPayload.status === "error") {
console.log("Command error");
console.log(finalPayload);
setHandleVerifyResponse(finalPayload);
return finalPayload;
}
// Verify the proof in the backend
const verifyResponse = await fetch(`/api/verify`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
payload: finalPayload as ISuccessResult, // Parses only the fields we need to verify
action: verifyPayload.action,
signal: verifyPayload.signal, // Optional
}),
});
// TODO: Handle Success!
const verifyResponseJson = await verifyResponse.json();
if (verifyResponseJson.status === 200) {
console.log("Verification success!");
console.log(finalPayload);
}
setHandleVerifyResponse(verifyResponseJson);
return verifyResponseJson;
}, []);
return (
<div>
<h1>Verify Block</h1>
<button className="bg-green-500 p-4" onClick={handleVerify}>
Test Verify
</button>
<span>{JSON.stringify(handleVerifyResponse, null, 2)}</span>
</div>
);
};
components/SignIn/index.tsx:
"use client";
import { signIn, signOut, useSession } from "next-auth/react";
export const SignIn = () => {
const { data: session } = useSession();
if (session) {
return (
<>
Signed in as {session?.user?.name?.slice(0, 10)} <br />
<button onClick={() => signOut()}>Sign out</button>
</>
);
} else {
return (
<>
Not signed in <br />
<button onClick={() => signIn()}>Sign in</button>
</>
);
}
};
components/Pay/index.tsx:
"use client";
import {
MiniKit,
tokenToDecimals,
Tokens,
PayCommandInput,
} from "@worldcoin/minikit-js";
const sendPayment = async () => {
try {
const res = await fetch(`/api/initiate-payment`, {
method: "POST",
});
const { id } = await res.json();
console.log(id);
const payload: PayCommandInput = {
reference: id,
to: "0x0c892815f0B058E69987920A23FBb33c834289cf", // Test address
tokens: [
{
symbol: Tokens.WLD,
token_amount: tokenToDecimals(0.5, Tokens.WLD).toString(),
},
{
symbol: Tokens.USDCE,
token_amount: tokenToDecimals(0.1, Tokens.USDCE).toString(),
},
],
description: "Watch this is a test",
};
if (MiniKit.isInstalled()) {
return await MiniKit.commandsAsync.pay(payload);
}
return null;
} catch (error: unknown) {
console.log("Error sending payment", error);
return null;
}
};
const handlePay = async () => {
if (!MiniKit.isInstalled()) {
console.error("MiniKit is not installed");
return;
}
const sendPaymentResponse = await sendPayment();
const response = sendPaymentResponse?.finalPayload;
if (!response) {
return;
}
if (response.status == "success") {
const res = await fetch(`${process.env.NEXTAUTH_URL}/api/confirm-payment`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ payload: response }),
});
const payment = await res.json();
if (payment.success) {
// Congrats your payment was successful!
console.log("SUCCESS!");
} else {
// Payment failed
console.log("FAILED!");
}
}
};
export const PayBlock = () => {
return (
<button className="bg-blue-500 p-4" onClick={handlePay}>
Pay
</button>
);
};
components/Eruda/eruda-provider.tsx:
"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}>;
};
components/Eruda/index.tsx:
"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 {props.children};
};
components/minikit-provider.tsx:
"use client"; // Required for Next.js
import { MiniKit } from "@worldcoin/minikit-js";
import { ReactNode, useEffect } from "react";
export default function MiniKitProvider({ children }: { children: ReactNode }) {
useEffect(() => {
MiniKit.install();
console.log(MiniKit.isInstalled());
}, []);
return <>{children}>;
}
components/next-auth-provider.tsx:
"use client";
import { SessionProvider } from "next-auth/react";
import { ReactNode } from "react";
export default function NextAuthProvider({
children,
}: {
children: ReactNode;
}) {
return <SessionProvider>{children}</SessionProvider>;
}