Skip to main content

Project Structure

The template follows Next.js App Router conventions with a clean separation of concerns.

Core Files Explained

app/layout.tsx

The root layout wraps your entire app with the wallet provider:
import WalletProvider from "@/wallet/provider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <WalletProvider>{children}</WalletProvider>
      </body>
    </html>
  );
}

app/page.tsx

The main page composes all components:
"use client";

import Header from "@/components/header";
import Padder from "@/components/padder";
import EncryptedInput from "@/components/encrypted-input";
import Balance from "@/components/balance";

const Page = () => {
  return (
    <Padder>
      <Header />
      <div className="max-w-md mx-auto">
        <EncryptedInput />
        <Balance />
      </div>
    </Padder>
  );
};

export default Page;

utils/constants.ts

Contains program IDs, PDA derivation, and helper functions:
import { PublicKey } from "@solana/web3.js";

// Your deployed program ID (from IDL)
export const PROGRAM_ID = new PublicKey(idl.address);

// Inco Lightning program on devnet
export const INCO_LIGHTNING_PROGRAM_ID = new PublicKey(
  "5sjEbPiqgZrYwR31ahR6Uk9wf5awoX61YGg7jExQSwaj"
);

// Account discriminators for filtering
export const INCO_MINT_DISCRIMINATOR = [254, 129, 245, 169, 202, 143, 198, 4];
export const INCO_ACCOUNT_DISCRIMINATOR = [18, 233, 131, 18, 230, 173, 249, 89];

Key Helper Functions

getAllowancePda

Derives the allowance PDA for a handle and wallet:
export const getAllowancePda = (
  handle: bigint,
  allowedAddress: PublicKey
): [PublicKey, number] => {
  // Convert handle to little-endian bytes
  const handleBuffer = Buffer.alloc(16);
  let h = handle;
  for (let i = 0; i < 16; i++) {
    handleBuffer[i] = Number(h & BigInt(0xff));
    h = h >> BigInt(8);
  }
  
  return PublicKey.findProgramAddressSync(
    [handleBuffer, allowedAddress.toBuffer()],
    INCO_LIGHTNING_PROGRAM_ID
  );
};

extractHandle

Extracts the encrypted handle from account data:
export const extractHandle = (data: Buffer): bigint => {
  // Handle is stored as u128 at offset 72-88 (little-endian)
  const bytes = data.slice(72, 88);
  let result = BigInt(0);
  for (let i = 15; i >= 0; i--) {
    result = result * BigInt(256) + BigInt(bytes[i]);
  }
  return result;
};
The offset 72-88 is specific to the Inco Token program’s IncoAccount structure. Adjust the offset based on your program’s account layout.

fetchUserMint

Finds the user’s mint account:
export const fetchUserMint = async (
  connection: Connection,
  wallet: PublicKey
): Promise<{ pubkey: PublicKey; data: Buffer } | null> => {
  const accounts = await connection.getProgramAccounts(PROGRAM_ID, {
    filters: [
      {
        memcmp: {
          offset: 0,
          bytes: bs58.encode(Buffer.from(INCO_MINT_DISCRIMINATOR)),
        },
      },
      { memcmp: { offset: 9, bytes: wallet.toBase58() } },
    ],
  });
  
  return accounts.length
    ? { pubkey: accounts[0].pubkey, data: accounts[0].account.data as Buffer }
    : null;
};

fetchUserTokenAccount

Finds the user’s token account for a specific mint:
export const fetchUserTokenAccount = async (
  connection: Connection,
  wallet: PublicKey,
  mint: PublicKey
): Promise<{ pubkey: PublicKey; data: Buffer } | null> => {
  const accounts = await connection.getProgramAccounts(PROGRAM_ID, {
    filters: [
      {
        memcmp: {
          offset: 0,
          bytes: bs58.encode(Buffer.from(INCO_ACCOUNT_DISCRIMINATOR)),
        },
      },
      { memcmp: { offset: 8, bytes: mint.toBase58() } },
      { memcmp: { offset: 40, bytes: wallet.toBase58() } },
    ],
  });
  
  return accounts.length
    ? { pubkey: accounts[0].pubkey, data: accounts[0].account.data as Buffer }
    : null;
};

getProgram

Creates an Anchor program instance:
export const getProgram = (connection: Connection, wallet: AnchorWallet) => {
  const provider = new AnchorProvider(connection, wallet, {
    commitment: "confirmed",
  });
  return new Program(idl as Idl, provider);
};

Next Steps

Learn how to set up Wallet Integration for your dApp.