Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ cargo test-sbf -p <package-name> -- --test-threads=1
### Privy Node.js (devnet)

```bash
cd privy/nodejs
cd toolkits/sign-with-privy/nodejs
npm install
cp .env.example .env # fill in Privy credentials + Helius RPC URL

Expand All @@ -70,7 +70,7 @@ npm run load # Consolidate cold + SPL + T22 into light-token ATA
npm run balances # Query balance breakdown (hot, cold, SPL/T22, SOL)
npm run history # Transaction history for light-token interface ops

# Setup helpers live in privy/scripts/ (separate workspace, uses local keypair)
# Setup helpers live in toolkits/sign-with-privy/scripts/ (separate workspace, uses local keypair)
cd ../scripts
npm run mint:spl-and-wrap <recipient> # Create mint + interface PDA + fund treasury
npm run mint:spl <mint> <recipient> # Mint SPL/T22 tokens to existing mint
Expand All @@ -80,7 +80,7 @@ npm run register:spl-interface <mint> # Register interface PDA on existing min
### Privy React (devnet — WIP)

```bash
cd privy/react
cd toolkits/sign-with-privy/react
npm install
cp .env.example .env # fill in VITE_PRIVY_APP_ID and VITE_HELIUS_RPC_URL

Expand All @@ -102,7 +102,7 @@ curl http://127.0.0.1:8784/health

### Workspace layout

Root `package.json` defines npm workspaces: `typescript-client`, `toolkits/payments-and-wallets`, `privy/nodejs`, `privy/react`, and `privy/scripts`. Dependencies are hoisted to root.
Root `package.json` defines npm workspaces: `typescript-client`, `toolkits/payments-and-wallets`, `toolkits/sign-with-privy/nodejs`, `toolkits/sign-with-privy/react`, and `toolkits/sign-with-privy/scripts`. Dependencies are hoisted to root.

- `typescript-client/` — Core examples: `actions/` (high-level) and `instructions/` (low-level)
- `rust-client/` — Rust client examples as cargo examples
Expand All @@ -113,9 +113,9 @@ Root `package.json` defines npm workspaces: `typescript-client`, `toolkits/payme
- `toolkits/` — Domain-specific implementations
- `payments-and-wallets/` — Wallet integration patterns (uses `@lightprotocol/compressed-token/unified` subpath)
- `streaming-tokens/` — Laserstream-based token indexing
- `privy/` — Privy wallet integration examples (devnet)
- `nodejs/` — Server-side scripts using `@privy-io/node` with server wallet signing
- `react/` — Browser app using `@privy-io/react-auth` with embedded wallet signing (WIP)
- `sign-with-privy/` — Privy wallet integration examples (devnet)
- `nodejs/` — Server-side scripts using `@privy-io/node` with server wallet signing
- `react/` — Browser app using `@privy-io/react-auth` with embedded wallet signing (WIP)

### TypeScript: actions vs instructions

Expand Down Expand Up @@ -162,7 +162,7 @@ const payer = Keypair.fromSecretKey(

**CPI pattern** (`basic-instructions/`): Explicit CPI calls like `TransferInterfaceCpi`.

### Privy Node.js architecture (`privy/nodejs/`)
### Privy Node.js architecture (`toolkits/sign-with-privy/nodejs/`)

Server-side scripts that sign transactions via Privy's wallet API instead of a local keypair. Each script in `src/` is standalone and runnable with `tsx`. All scripts target **devnet**.

Expand All @@ -172,8 +172,8 @@ Server-side scripts that sign transactions via Privy's wallet API instead of a l

**Two workspaces, two signing modes**:

- **App operations** (`privy/nodejs/src/*.ts`): Privy server wallet signing. Six scripts: `transfer`, `wrap`, `unwrap`, `load`, `balances`, `get-transaction-history`.
- **Setup helpers** (`privy/scripts/src/*.ts`): Separate npm workspace. Uses local filesystem keypair (`~/.config/solana/id.json`), not Privy. Scripts: `mint-spl-and-wrap`, `mint-spl`, `register-spl-interface`.
- **App operations** (`toolkits/sign-with-privy/nodejs/src/*.ts`): Privy server wallet signing. Six scripts: `transfer`, `wrap`, `unwrap`, `load`, `balances`, `get-transaction-history`.
- **Setup helpers** (`toolkits/sign-with-privy/scripts/src/*.ts`): Separate npm workspace. Uses local filesystem keypair (`~/.config/solana/id.json`), not Privy. Scripts: `mint-spl-and-wrap`, `mint-spl`, `register-spl-interface`.

**Config**: All env vars centralized in `src/config.ts` with validation. Exports `TREASURY_WALLET_ID`, `TREASURY_WALLET_ADDRESS`, `TREASURY_AUTHORIZATION_KEY`, `HELIUS_RPC_URL`, `TEST_MINT`, and convenience defaults (`DEFAULT_TEST_RECIPIENT`, `DEFAULT_AMOUNT`, `DEFAULT_DECIMALS`). Scripts import from `./config.js` (ESM `.js` extension required).

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Light token is a high-performance token standard that reduces the cost of mint a
|---------|-------------|
| [Payments and Wallets](toolkits/payments-and-wallets/) | All you need for wallet integrations and payment flows. Minimal API differences to SPL. |
| [Streaming Tokens](toolkits/streaming-tokens/) | Stream mint events using Laserstream |
| [Sign with Privy](toolkits/sign-with-privy/) | Light-token operations signed with Privy wallets (Node.js + React) |

## Client Examples

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"workspaces": [
"typescript-client",
"toolkits/payments-and-wallets",
"privy/react",
"privy/nodejs",
"privy/scripts"
"toolkits/sign-with-privy/react",
"toolkits/sign-with-privy/nodejs",
"toolkits/sign-with-privy/scripts"
],
"scripts": {
"toolkit:payments": "npm run -w toolkits/payments-and-wallets"
Expand Down
7 changes: 7 additions & 0 deletions toolkits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ Your users hold and receive tokens of the same mints, just stored more efficient
- **[wrap](payments-and-wallets/wrap.ts)** - Wrap SPL/T22 to light-token
- **[unwrap](payments-and-wallets/unwrap.ts)** - Unwrap light-token to SPL/T22

### Sign with Privy

Light-token operations signed with [Privy](https://privy.io) wallets. Server-side (Node.js) and client-side (React) examples for transfer, wrap, unwrap, load, and balance queries on devnet.
- **[Node.js](sign-with-privy/nodejs/)** — Server-side scripts using `@privy-io/node` with server wallet signing
- **[React](sign-with-privy/react/)** — Browser app using `@privy-io/react-auth` with embedded wallet signing
- **[Setup scripts](sign-with-privy/scripts/)** — Create test mints and fund wallets on devnet

### Streaming Tokens

[Rust program example to stream mint events](streaming-tokens/) of the Light-Token Program.
Expand Down
142 changes: 142 additions & 0 deletions toolkits/sign-with-privy/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project overview

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.

## Sub-projects

### `nodejs/` — Server-side (Node.js)

Backend scripts using `@privy-io/node` with server-side wallet signing via `TREASURY_AUTHORIZATION_KEY`. Each script is a standalone operation run with `tsx`.

### `react/` — Client-side (React + Vite) — WIP

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.

## Build and run

### Node.js scripts

```bash
cd nodejs
npm install
cp .env.example .env # fill in all values

# App operations (Privy-signed server wallet)
npm run transfer # Light-token ATA → ATA transfer (auto-loads cold balance)
npm run wrap # SPL/T22 → light-token ATA
npm run unwrap # Light-token ATA → SPL/T22
npm run load # Consolidate cold + SPL + T22 into light-token ATA
npm run balances # Query balance breakdown (hot, cold, SPL/T22, SOL)
npm run history # Transaction history for light-token interface ops

# Setup helpers (use local filesystem keypair, not Privy)
npm run mint:spl-and-wrap # Create mint + interface PDA + fund treasury
npm run mint:spl # Mint SPL/T22 tokens to existing mint
npm run register:spl-interface # Register interface PDA on existing mint
npm run decompress # Decompress light-token ATA to T22 ATA
```

### React app

```bash
cd react
npm install
cp .env.example .env # fill in VITE_PRIVY_APP_ID and VITE_HELIUS_RPC_URL

npm run dev # start Vite dev server
npm run build # production build
```

## Architecture

### Privy signing pattern

Both apps follow the same flow: build an unsigned `Transaction`, serialize with `requireAllSignatures: false`, sign via Privy, deserialize, and send with `sendRawTransaction`.

**Node.js** — signs via `privy.wallets().solana().signTransaction(walletId, { transaction, authorization_context })`. Requires `TREASURY_WALLET_ID` and `TREASURY_AUTHORIZATION_KEY`.

**React** — signs via `useSignTransaction` hook: `signTransaction({ transaction, wallet, chain: 'solana:devnet' })`. Privy handles embedded wallet key management client-side.

### Node.js modules (`nodejs/src/`)

Standalone async functions, each creating their own `PrivyClient` and `createRpc`:

- `transfer.ts` — `createTransferInterfaceInstruction`, auto-loads cold balance via `createLoadAtaInstructionsFromInterface`
- `wrap.ts` — `createWrapInstruction` with SPL interface lookup via `getSplInterfaceInfos`
- `unwrap.ts` — `createUnwrapInstruction` from `@lightprotocol/compressed-token/unified`
- `load.ts` — `createLoadAtaInstructionsFromInterface` to consolidate cold + SPL + T22 into light-token ATA
- `balances.ts` — queries hot (`getAtaInterface`), cold (`getCompressedTokenBalancesByOwnerV2`), SPL T22 (`getTokenAccountsByOwner` + raw data parsing)
- `get-transaction-history.ts` — `getSignaturesForOwnerInterface`
- `config.ts` — centralized env var exports with validation

**Setup helpers** (`nodejs/src/helpers/`):

- `mint-spl-and-wrap.ts` — `createMintInterface` + mint + wrap + transfer to treasury (filesystem wallet)
- `mint-spl.ts` — `createMintToInstruction` to existing mint (filesystem wallet)
- `register-spl-interface.ts` — `createSplInterface` on existing mint (filesystem wallet)
- `decompress.ts` — `decompressInterface` from light-token ATA to T22 ATA (filesystem wallet)

### Transaction routing (React `TransferForm`)

The `TransferForm` component routes actions based on `TokenBalance.isLightToken`:

- Light-token balance → `useTransfer` → `createTransferInterfaceInstruction`
- SPL balance → `useWrap` → `createWrapInstruction` (wraps to own light-token ATA)
- SOL → display only, no transfer action

### React hooks (`react/src/hooks/`)

Each hook returns `{ actionFn, isLoading }` and accepts `{ params, wallet, signTransaction }`:

- `useTransfer` — light-token ATA to ATA transfer
- `useWrap` — SPL to light-token (creates light-token ATA idempotently, verifies SPL balance)
- `useUnwrap` — light-token to SPL T22 (creates T22 ATA if missing)
- `useLightTokenBalances` — fetches SOL, SPL (Token Program), and light-token (T22) balances by parsing raw account data
- `useTransactionHistory` — queries `getSignaturesForOwnerInterface`

## Environment variables

### Node.js (`nodejs/.env`)

| Variable | Required | Purpose |
|---|---|---|
| `PRIVY_APP_ID` | Yes | Privy application ID |
| `PRIVY_APP_SECRET` | Yes | Privy server-side secret |
| `TREASURY_WALLET_ID` | Yes | Privy wallet ID for signing |
| `TREASURY_WALLET_ADDRESS` | Yes | Public key of treasury wallet |
| `TREASURY_AUTHORIZATION_KEY` | Yes | EC private key for ECDSA transaction authorization |
| `HELIUS_RPC_URL` | Yes | Helius RPC endpoint (devnet) |
| `TEST_MINT` | No | Token mint address for scripts |
| `DEFAULT_TEST_RECIPIENT` | No | Defaults to `TREASURY_WALLET_ADDRESS` |
| `DEFAULT_AMOUNT` | No | Defaults to `0.001` |
| `DEFAULT_DECIMALS` | No | Defaults to `9` |

### React (`react/.env`)

| Variable | Required | Purpose |
|---|---|---|
| `VITE_PRIVY_APP_ID` | Yes | Privy application ID |
| `VITE_HELIUS_RPC_URL` | Yes | Helius RPC endpoint (devnet) |

## Key dependencies

- `@privy-io/node` ^0.1.0-alpha.2 — server-side Privy SDK
- `@privy-io/react-auth` ^3.9.1 — client-side Privy SDK
- `@lightprotocol/compressed-token` beta — light-token instructions (also exports `/unified` subpath for unwrap, load, balances)
- `@lightprotocol/stateless.js` beta — RPC client (`createRpc`)
- `@solana/web3.js` 1.98.4 — Solana web3 v1
- `@solana/spl-token` ^0.4.13 — SPL token operations (T22 ATA creation, balance checks)
- `@solana/kit` ^5.5.1 — Solana RPC for Privy provider config (React only)

## Important patterns

- Wrap and unwrap require `ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 })`. Transfer does not.
- Wrap calls `getSplInterfaceInfos` to find the initialized SPL interface and its `tokenProgram`.
- Unwrap imports from `@lightprotocol/compressed-token/unified` (not the main export).
- Helper scripts use filesystem wallet (`~/.config/solana/id.json`), not Privy, because they need a keypair signer for mint authority.
- The React app derives WebSocket URL from RPC URL by replacing `https://` with `wss://`.
- Both apps target `solana:devnet` (CAIP-2 chain ID `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`).
45 changes: 45 additions & 0 deletions toolkits/sign-with-privy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Privy + Light Token

Transfer, wrap, unwrap, and query light-tokens signed with Privy wallets. Learn more in the README of the respective examples.

- **[Node.js](nodejs/)** — Server-side scripts using `@privy-io/node` with server wallet signing
- **[React](react/)** — Browser app using `@privy-io/react-auth` with embedded wallet signing
- **[Setup scripts](scripts/)** — Create test mints and fund wallets on devnet (local keypair, no Privy needed)

| Creation cost | SPL | Light Token |
| :---------------- | :------------------ | :------------------- |
| **Token account** | ~2,000,000 lamports | ~**11,000** lamports |

Privy handles user authentication and wallet management. You build transactions with light-token and Privy signs them:

1. Authenticate with Privy
2. Build unsigned transaction with light-token instructions
3. Sign transaction using Privy's wallet provider
4. Send signed transaction to RPC

## Operations

| | SPL | Light Token |
| --- | --- | --- |
| **Transfer** | `createTransferInstruction()` | `createTransferInterfaceInstructions()` |
| **Receive / Load** | `getOrCreateAssociatedTokenAccount()` | `createLoadAtaInstructions()` |
| **Wrap (SPL → Light)** | N/A | `createWrapInstruction()` |
| **Unwrap (Light → SPL)** | N/A | `createUnwrapInstructions()` |
| **Get balance** | `getAccount()` | `getAtaInterface()` |
| **Transaction history** | `getSignaturesForAddress()` | `getSignaturesForOwnerInterface()` |

### Transfer and Loading Balance

Light Token accounts exist in two states: **hot** (active on-chain with rent-exempt balance) and **cold** (compressed after extended inactivity, `is_initialized: false`). Programs interact only with hot accounts.

`loadAta` reinstates a cold account back to active on-chain state. It unifies balances from compressed tokens, SPL, and Token 2022 into a single Light Token associated token account. Creates the ATA if it doesn't exist. Returns `null` if there's nothing to load (idempotent).

`transfer` and `unwrap` auto-load before executing — explicit `loadAta` is only needed when receiving payments.

APIs return `TransactionInstruction[][]` — each inner array is one transaction. Almost always one. The same loop handles multi-transaction cases.

## Documentation

- [Light Token with Privy wallets](https://www.zkcompression.com/light-token/toolkits/for-privy)
- [Toolkit for stablecoin payments](https://www.zkcompression.com/light-token/toolkits/for-payments)
- [Light Token overview](https://www.zkcompression.com/light-token/welcome)
7 changes: 7 additions & 0 deletions toolkits/sign-with-privy/nodejs/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
PRIVY_APP_ID=
PRIVY_APP_SECRET=
TREASURY_WALLET_ID=
TREASURY_WALLET_ADDRESS=
TREASURY_AUTHORIZATION_KEY=
HELIUS_RPC_URL=
TEST_MINT=
2 changes: 2 additions & 0 deletions toolkits/sign-with-privy/nodejs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
node_modules/
Loading
Loading