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

  1. 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
  1. 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>
  );
}
  1. 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

  1. 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.