Skip to content

Commit da23591

Browse files
tilo-14tilo-14
andauthored
Add Privy wallet integration toolkit (#25)
* unifyt * privy nodejs * feat: privy wallet integration (nodejs + react) Server-side (nodejs) and client-side (react) Privy wallet examples for light-token transfers, wrap, unwrap, load, and balances. Restructured privy/ layout: - nodejs/: server wallet signing via @privy-io/node - react/: embedded wallet signing via @privy-io/react-auth - scripts/: setup helpers (mint creation, SPL interface registration) Transfer creates destination associated token account idempotently before transferring. * rm shared sig * refactor: use unified SDK actions for privy hooks, multi-mint balances React hooks (useTransfer, useUnwrap) now call high-level SDK functions (createTransferInterfaceInstructions, createUnwrapInstructions) instead of manually assembling instructions. Shared signAndSendBatches helper handles multi-tx flows from batched instruction arrays. Balance hooks (react + nodejs) refactored to per-mint accumulator pattern that queries all mints in one pass instead of single-mint. TokenBalance now exposes hot/cold/spl/t22/unified breakdown. Other changes: - useWrap: imports from /unified subpath, uses idempotent ATA create - unwrap (nodejs + react): auto-detect token program from mint owner - mint-light-token script: --spl flag for SPL token program mints - mint-spl script: auto-detect token program from mint account - Remove standalone load.ts (load is handled by transfer/unwrap SDK) * final 1 * Separate Send and Unify actions in TransferForm Remove hidden wrap-then-transfer flow from Send button. Add explicit Unify button for wrapping SPL/T22 into unified balance. Send only enabled when unified balance > 0. * Remove wrap/unify from TransferForm, send-only for now * Use beta dist-tag for Light Protocol dependencies * fix comments * update readme * update readme * fix devins * fix devins * fix comments --------- Co-authored-by: tilo-14 <tilo@luminouslabs.com>
1 parent 4cdf80d commit da23591

58 files changed

Lines changed: 3578 additions & 13 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ cargo test-sbf -p <package-name> -- --test-threads=1
5858
### Privy Node.js (devnet)
5959

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

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

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

8282
```bash
83-
cd privy/react
83+
cd toolkits/sign-with-privy/react
8484
npm install
8585
cp .env.example .env # fill in VITE_PRIVY_APP_ID and VITE_HELIUS_RPC_URL
8686

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

103103
### Workspace layout
104104

105-
Root `package.json` defines npm workspaces: `typescript-client`, `toolkits/payments-and-wallets`, `privy/nodejs`, `privy/react`, and `privy/scripts`. Dependencies are hoisted to root.
105+
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.
106106

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

120120
### TypeScript: actions vs instructions
121121

@@ -162,7 +162,7 @@ const payer = Keypair.fromSecretKey(
162162

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

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

167167
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**.
168168

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

173173
**Two workspaces, two signing modes**:
174174

175-
- **App operations** (`privy/nodejs/src/*.ts`): Privy server wallet signing. Six scripts: `transfer`, `wrap`, `unwrap`, `load`, `balances`, `get-transaction-history`.
176-
- **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`.
175+
- **App operations** (`toolkits/sign-with-privy/nodejs/src/*.ts`): Privy server wallet signing. Six scripts: `transfer`, `wrap`, `unwrap`, `load`, `balances`, `get-transaction-history`.
176+
- **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`.
177177

178178
**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).
179179

README.md

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

1617
## Client Examples
1718

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"workspaces": [
55
"typescript-client",
66
"toolkits/payments-and-wallets",
7-
"privy/react",
8-
"privy/nodejs",
9-
"privy/scripts"
7+
"toolkits/sign-with-privy/react",
8+
"toolkits/sign-with-privy/nodejs",
9+
"toolkits/sign-with-privy/scripts"
1010
],
1111
"scripts": {
1212
"toolkit:payments": "npm run -w toolkits/payments-and-wallets"

toolkits/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ Your users hold and receive tokens of the same mints, just stored more efficient
1212
- **[wrap](payments-and-wallets/wrap.ts)** - Wrap SPL/T22 to light-token
1313
- **[unwrap](payments-and-wallets/unwrap.ts)** - Unwrap light-token to SPL/T22
1414

15+
### Sign with Privy
16+
17+
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.
18+
- **[Node.js](sign-with-privy/nodejs/)** — Server-side scripts using `@privy-io/node` with server wallet signing
19+
- **[React](sign-with-privy/react/)** — Browser app using `@privy-io/react-auth` with embedded wallet signing
20+
- **[Setup scripts](sign-with-privy/scripts/)** — Create test mints and fund wallets on devnet
21+
1522
### Streaming Tokens
1623

1724
[Rust program example to stream mint events](streaming-tokens/) of the Light-Token Program.

toolkits/sign-with-privy/CLAUDE.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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`).

toolkits/sign-with-privy/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Privy + Light Token
2+
3+
Transfer, wrap, unwrap, and query light-tokens signed with Privy wallets. Learn more in the README of the respective examples.
4+
5+
- **[Node.js](nodejs/)** — Server-side scripts using `@privy-io/node` with server wallet signing
6+
- **[React](react/)** — Browser app using `@privy-io/react-auth` with embedded wallet signing
7+
- **[Setup scripts](scripts/)** — Create test mints and fund wallets on devnet (local keypair, no Privy needed)
8+
9+
| Creation cost | SPL | Light Token |
10+
| :---------------- | :------------------ | :------------------- |
11+
| **Token account** | ~2,000,000 lamports | ~**11,000** lamports |
12+
13+
Privy handles user authentication and wallet management. You build transactions with light-token and Privy signs them:
14+
15+
1. Authenticate with Privy
16+
2. Build unsigned transaction with light-token instructions
17+
3. Sign transaction using Privy's wallet provider
18+
4. Send signed transaction to RPC
19+
20+
## Operations
21+
22+
| | SPL | Light Token |
23+
| --- | --- | --- |
24+
| **Transfer** | `createTransferInstruction()` | `createTransferInterfaceInstructions()` |
25+
| **Receive / Load** | `getOrCreateAssociatedTokenAccount()` | `createLoadAtaInstructions()` |
26+
| **Wrap (SPL → Light)** | N/A | `createWrapInstruction()` |
27+
| **Unwrap (Light → SPL)** | N/A | `createUnwrapInstructions()` |
28+
| **Get balance** | `getAccount()` | `getAtaInterface()` |
29+
| **Transaction history** | `getSignaturesForAddress()` | `getSignaturesForOwnerInterface()` |
30+
31+
### Transfer and Loading Balance
32+
33+
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.
34+
35+
`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).
36+
37+
`transfer` and `unwrap` auto-load before executing — explicit `loadAta` is only needed when receiving payments.
38+
39+
APIs return `TransactionInstruction[][]` — each inner array is one transaction. Almost always one. The same loop handles multi-transaction cases.
40+
41+
## Documentation
42+
43+
- [Light Token with Privy wallets](https://www.zkcompression.com/light-token/toolkits/for-privy)
44+
- [Toolkit for stablecoin payments](https://www.zkcompression.com/light-token/toolkits/for-payments)
45+
- [Light Token overview](https://www.zkcompression.com/light-token/welcome)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
PRIVY_APP_ID=
2+
PRIVY_APP_SECRET=
3+
TREASURY_WALLET_ID=
4+
TREASURY_WALLET_ADDRESS=
5+
TREASURY_AUTHORIZATION_KEY=
6+
HELIUS_RPC_URL=
7+
TEST_MINT=
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env
2+
node_modules/

0 commit comments

Comments
 (0)