|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project overview |
| 6 | + |
| 7 | +Two example apps demonstrating Privy wallet integration with Light Token on Solana devnet. Both apps show transfer, wrap, unwrap, load, and balance query flows using Privy-managed wallets for transaction signing. |
| 8 | + |
| 9 | +## Sub-projects |
| 10 | + |
| 11 | +### `nodejs/` — Server-side (Node.js) |
| 12 | + |
| 13 | +Backend scripts using `@privy-io/node` with server-side wallet signing via `TREASURY_AUTHORIZATION_KEY`. Each script is a standalone operation run with `tsx`. |
| 14 | + |
| 15 | +### `react/` — Client-side (React + Vite) — WIP |
| 16 | + |
| 17 | +Browser app using `@privy-io/react-auth` with client-side wallet signing via `useSignTransaction`. Privy creates embedded Solana wallets on login. Vite dev server with Tailwind CSS v4 and `vite-plugin-node-polyfills` for Buffer. |
| 18 | + |
| 19 | +## Build and run |
| 20 | + |
| 21 | +### Node.js scripts |
| 22 | + |
| 23 | +```bash |
| 24 | +cd nodejs |
| 25 | +npm install |
| 26 | +cp .env.example .env # fill in all values |
| 27 | + |
| 28 | +# App operations (Privy-signed server wallet) |
| 29 | +npm run transfer # Light-token ATA → ATA transfer (auto-loads cold balance) |
| 30 | +npm run wrap # SPL/T22 → light-token ATA |
| 31 | +npm run unwrap # Light-token ATA → SPL/T22 |
| 32 | +npm run load # Consolidate cold + SPL + T22 into light-token ATA |
| 33 | +npm run balances # Query balance breakdown (hot, cold, SPL/T22, SOL) |
| 34 | +npm run history # Transaction history for light-token interface ops |
| 35 | + |
| 36 | +# Setup helpers (use local filesystem keypair, not Privy) |
| 37 | +npm run mint:spl-and-wrap # Create mint + interface PDA + fund treasury |
| 38 | +npm run mint:spl # Mint SPL/T22 tokens to existing mint |
| 39 | +npm run register:spl-interface # Register interface PDA on existing mint |
| 40 | +npm run decompress # Decompress light-token ATA to T22 ATA |
| 41 | +``` |
| 42 | + |
| 43 | +### React app |
| 44 | + |
| 45 | +```bash |
| 46 | +cd react |
| 47 | +npm install |
| 48 | +cp .env.example .env # fill in VITE_PRIVY_APP_ID and VITE_HELIUS_RPC_URL |
| 49 | + |
| 50 | +npm run dev # start Vite dev server |
| 51 | +npm run build # production build |
| 52 | +``` |
| 53 | + |
| 54 | +## Architecture |
| 55 | + |
| 56 | +### Privy signing pattern |
| 57 | + |
| 58 | +Both apps follow the same flow: build an unsigned `Transaction`, serialize with `requireAllSignatures: false`, sign via Privy, deserialize, and send with `sendRawTransaction`. |
| 59 | + |
| 60 | +**Node.js** — signs via `privy.wallets().solana().signTransaction(walletId, { transaction, authorization_context })`. Requires `TREASURY_WALLET_ID` and `TREASURY_AUTHORIZATION_KEY`. |
| 61 | + |
| 62 | +**React** — signs via `useSignTransaction` hook: `signTransaction({ transaction, wallet, chain: 'solana:devnet' })`. Privy handles embedded wallet key management client-side. |
| 63 | + |
| 64 | +### Node.js modules (`nodejs/src/`) |
| 65 | + |
| 66 | +Standalone async functions, each creating their own `PrivyClient` and `createRpc`: |
| 67 | + |
| 68 | +- `transfer.ts` — `createTransferInterfaceInstruction`, auto-loads cold balance via `createLoadAtaInstructionsFromInterface` |
| 69 | +- `wrap.ts` — `createWrapInstruction` with SPL interface lookup via `getSplInterfaceInfos` |
| 70 | +- `unwrap.ts` — `createUnwrapInstruction` from `@lightprotocol/compressed-token/unified` |
| 71 | +- `load.ts` — `createLoadAtaInstructionsFromInterface` to consolidate cold + SPL + T22 into light-token ATA |
| 72 | +- `balances.ts` — queries hot (`getAtaInterface`), cold (`getCompressedTokenBalancesByOwnerV2`), SPL T22 (`getTokenAccountsByOwner` + raw data parsing) |
| 73 | +- `get-transaction-history.ts` — `getSignaturesForOwnerInterface` |
| 74 | +- `config.ts` — centralized env var exports with validation |
| 75 | + |
| 76 | +**Setup helpers** (`nodejs/src/helpers/`): |
| 77 | + |
| 78 | +- `mint-spl-and-wrap.ts` — `createMintInterface` + mint + wrap + transfer to treasury (filesystem wallet) |
| 79 | +- `mint-spl.ts` — `createMintToInstruction` to existing mint (filesystem wallet) |
| 80 | +- `register-spl-interface.ts` — `createSplInterface` on existing mint (filesystem wallet) |
| 81 | +- `decompress.ts` — `decompressInterface` from light-token ATA to T22 ATA (filesystem wallet) |
| 82 | + |
| 83 | +### Transaction routing (React `TransferForm`) |
| 84 | + |
| 85 | +The `TransferForm` component routes actions based on `TokenBalance.isLightToken`: |
| 86 | + |
| 87 | +- Light-token balance → `useTransfer` → `createTransferInterfaceInstruction` |
| 88 | +- SPL balance → `useWrap` → `createWrapInstruction` (wraps to own light-token ATA) |
| 89 | +- SOL → display only, no transfer action |
| 90 | + |
| 91 | +### React hooks (`react/src/hooks/`) |
| 92 | + |
| 93 | +Each hook returns `{ actionFn, isLoading }` and accepts `{ params, wallet, signTransaction }`: |
| 94 | + |
| 95 | +- `useTransfer` — light-token ATA to ATA transfer |
| 96 | +- `useWrap` — SPL to light-token (creates light-token ATA idempotently, verifies SPL balance) |
| 97 | +- `useUnwrap` — light-token to SPL T22 (creates T22 ATA if missing) |
| 98 | +- `useLightTokenBalances` — fetches SOL, SPL (Token Program), and light-token (T22) balances by parsing raw account data |
| 99 | +- `useTransactionHistory` — queries `getSignaturesForOwnerInterface` |
| 100 | + |
| 101 | +## Environment variables |
| 102 | + |
| 103 | +### Node.js (`nodejs/.env`) |
| 104 | + |
| 105 | +| Variable | Required | Purpose | |
| 106 | +|---|---|---| |
| 107 | +| `PRIVY_APP_ID` | Yes | Privy application ID | |
| 108 | +| `PRIVY_APP_SECRET` | Yes | Privy server-side secret | |
| 109 | +| `TREASURY_WALLET_ID` | Yes | Privy wallet ID for signing | |
| 110 | +| `TREASURY_WALLET_ADDRESS` | Yes | Public key of treasury wallet | |
| 111 | +| `TREASURY_AUTHORIZATION_KEY` | Yes | EC private key for ECDSA transaction authorization | |
| 112 | +| `HELIUS_RPC_URL` | Yes | Helius RPC endpoint (devnet) | |
| 113 | +| `TEST_MINT` | No | Token mint address for scripts | |
| 114 | +| `DEFAULT_TEST_RECIPIENT` | No | Defaults to `TREASURY_WALLET_ADDRESS` | |
| 115 | +| `DEFAULT_AMOUNT` | No | Defaults to `0.001` | |
| 116 | +| `DEFAULT_DECIMALS` | No | Defaults to `9` | |
| 117 | + |
| 118 | +### React (`react/.env`) |
| 119 | + |
| 120 | +| Variable | Required | Purpose | |
| 121 | +|---|---|---| |
| 122 | +| `VITE_PRIVY_APP_ID` | Yes | Privy application ID | |
| 123 | +| `VITE_HELIUS_RPC_URL` | Yes | Helius RPC endpoint (devnet) | |
| 124 | + |
| 125 | +## Key dependencies |
| 126 | + |
| 127 | +- `@privy-io/node` ^0.1.0-alpha.2 — server-side Privy SDK |
| 128 | +- `@privy-io/react-auth` ^3.9.1 — client-side Privy SDK |
| 129 | +- `@lightprotocol/compressed-token` beta — light-token instructions (also exports `/unified` subpath for unwrap, load, balances) |
| 130 | +- `@lightprotocol/stateless.js` beta — RPC client (`createRpc`) |
| 131 | +- `@solana/web3.js` 1.98.4 — Solana web3 v1 |
| 132 | +- `@solana/spl-token` ^0.4.13 — SPL token operations (T22 ATA creation, balance checks) |
| 133 | +- `@solana/kit` ^5.5.1 — Solana RPC for Privy provider config (React only) |
| 134 | + |
| 135 | +## Important patterns |
| 136 | + |
| 137 | +- Wrap and unwrap require `ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 })`. Transfer does not. |
| 138 | +- Wrap calls `getSplInterfaceInfos` to find the initialized SPL interface and its `tokenProgram`. |
| 139 | +- Unwrap imports from `@lightprotocol/compressed-token/unified` (not the main export). |
| 140 | +- Helper scripts use filesystem wallet (`~/.config/solana/id.json`), not Privy, because they need a keypair signer for mint authority. |
| 141 | +- The React app derives WebSocket URL from RPC URL by replacing `https://` with `wss://`. |
| 142 | +- Both apps target `solana:devnet` (CAIP-2 chain ID `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`). |
0 commit comments