Building with Next.js
This guide will walk you through integrating Chipi Pay into your Next.js application with Clerk authentication and secure wallet storage. We’ll cover everything from installation to implementing secure transfer flows.
Prerequisites
- Node.js 16 or later
- Next.js 13 or later
- Basic knowledge of React and Next.js
- A Chipi Pay account
- A Clerk account for authentication
Getting Started
To get started with Chipi Pay in your Next.js application, you’ll need to have a basic Next.js project set up. If you don’t have one yet, you can create it using:
npx create-next-app@latest my-chipi-app
cd my-chipi-app
Installation
First, install the required packages:
# Install Chipi Pay SDK
npm install @chipi-pay/chipi-sdk
# Install Clerk
npm install @clerk/nextjs
Configuration
- Create a
.env.local
file in your project root and add your API keys:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key
CLERK_SECRET_KEY=your_clerk_secret_key
NEXT_PUBLIC_CHIPI_API_KEY=your_chipi_api_key
- Set up the Chipi Pay provider in your application:
// app/providers.tsx
"use client";
import { ChipiProvider } from "@chipi-pay/chipi-sdk";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ChipiProvider
config={{
apiPublicKey: process.env.NEXT_PUBLIC_CHIPI_API_KEY,
}}
>
{children}
</ChipiProvider>
);
}
- Update your root layout to use the providers:
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
import { Providers } from './providers'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<ClerkProvider>
<Providers>{children}</Providers>
</ClerkProvider>
</body>
</html>
)
}
Implementing Wallet Storage with Clerk
- Create a server action to save the wallet:
// app/actions.ts
"use server";
import { auth } from "@clerk/nextjs";
export async function saveWallet(walletData: any) {
const { userId } = auth();
if (!userId) {
throw new Error("Not authenticated");
}
// Save wallet to Clerk's public metadata
await clerkClient.users.updateUser(userId, {
publicMetadata: {
wallet: walletData
}
});
}
Implementing Transfer Flow
Here’s an example of implementing a transfer flow with Clerk authentication:
// app/page.tsx
"use client";
import { useState } from "react";
import { useUser } from "@clerk/nextjs";
import {
useApprove,
useStake,
useCreateWallet,
useTransfer,
useWithdraw,
useCallAnyContract,
} from "@chipi-pay/chipi-sdk";
import { saveWallet } from "./actions";
export default function Home() {
const { user } = useUser();
const { createWalletAsync, createWalletResponse } = useCreateWallet();
const { transferAsync } = useTransfer();
const [amount, setAmount] = useState("");
const [recipientAddress, setRecipientAddress] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState("");
const handleCreateWallet = async (encryptKey: string) => {
try {
setIsProcessing(true);
setError("");
const response = await createWalletAsync(encryptKey);
console.log("creation response", response);
// Save wallet to Clerk's public metadata
await saveWallet(response);
alert("Wallet created");
} catch (error) {
setError(error.message || "Failed to create wallet");
alert("Error creating wallet: " + error.message);
} finally {
setIsProcessing(false);
}
};
const handleSend = async (pin: string) => {
try {
setIsProcessing(true);
setError("");
// Get stored wallet from Clerk's public metadata
const storedWallet = user?.publicMetadata?.wallet;
if (!storedWallet) {
setError("No wallet found. Please create a wallet first.");
return;
}
// Get the bearer token
const token = await user?.getToken({ template: "chipi-sdk" });
if (!token) {
setError("No bearer token found");
return;
}
const transferResponse = await transferAsync({
encryptKey: pin,
bearerToken: token,
wallet: JSON.parse(storedWallet),
contractAddress: USDC_CONTRACT,
recipient: recipientAddress,
amount: amount,
decimals: 6,
});
console.log("transfer response", transferResponse);
alert("Transferred successfully");
// Clear form
setAmount("");
setRecipientAddress("");
} catch (error) {
setError(error.message || "Transfer failed");
alert("Error: " + error.message);
} finally {
setIsProcessing(false);
}
};
return (
<div className="p-5">
{error && <div className="text-red-500 mb-4">{error}</div>}
<div className="space-y-4">
<h1 className="text-2xl font-bold">Chipi SDK</h1>
<button
onClick={() => handleCreateWallet("01234")}
disabled={isProcessing}
className="bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50"
>
{isProcessing ? "Processing..." : "Create Wallet"}
</button>
<div className="space-y-2">
<input
type="text"
placeholder="Recipient Address"
value={recipientAddress}
onChange={(e) => setRecipientAddress(e.target.value)}
className="w-full p-2 border rounded"
/>
<input
type="number"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="w-full p-2 border rounded"
/>
<button
onClick={() => handleSend("01234")}
disabled={isProcessing || !amount || !recipientAddress}
className="bg-green-500 text-white px-4 py-2 rounded disabled:opacity-50 w-full"
>
{isProcessing ? "Processing..." : "Make Transfer"}
</button>
</div>
</div>
</div>
);
}
Next Steps
- Add error handling and loading states
- Customize the UI to match your app’s design
Need help? Check out our Telegram Community for
support and to connect with other developers.