diff --git a/.gitignore b/.gitignore index f6f2388f..4c892c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ node_modules/ stash docs-audit.md -docs-audit.txt \ No newline at end of file +docs-audit.txt +docs-anchor.json +product-docs.json +tabs-docs.json \ No newline at end of file diff --git a/.mintlifyignore b/.mintlifyignore index c2658d7d..b6165b8f 100644 --- a/.mintlifyignore +++ b/.mintlifyignore @@ -1 +1,6 @@ node_modules/ +ai-tools/skills/ +ai-tools/prompts/ +ai-tools/.claude-plugin/ +ai-tools/scripts/ +ai-tools/README.md diff --git a/ai-tools/guide.mdx b/ai-tools/guide.mdx new file mode 100644 index 00000000..20a75230 --- /dev/null +++ b/ai-tools/guide.mdx @@ -0,0 +1,225 @@ +--- +title: "AI Tools Guide" +description: "Guidance to AI tools when working with ZK Compression. Includes Agent Skill, MCP server, DeepWiki, and AI Search guides." +--- + +# For Docs + +- **Docs AI Search** - Search documentation with AI in the search bar. +- **Markdown Export** - Append `.md` to any page URL for raw markdown. +* **View [`/llms.txt`](https://zkcompression.com/llms.txt)** for an index of the docs - It lists key pages with descriptions +so agents can navigate to answers quickly. + +# For Development + +## Agent Skills + +View or install [`/skill.md`](https://zkcompression.com/skill.md), a structured capability file that tells agents what they can do +with Light Protocol and ZK Compression. If you're building with agents, start here. + +```bash +npx skills add https://zkcompression.com +``` + +Install or view dedicated agent skills: + +| Use case | Skill | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| Build DeFi programs (AMMs, vaults, lending) with Anchor or Pinocchio | [defi-program](https://github.com/Lightprotocol/skills/tree/main/skills/defi-program) | +| Integrate rent-free markets into routers and aggregators | [defi-router](https://github.com/Lightprotocol/skills/tree/main/skills/defi-router) | +| Stream account state via Laserstream gRPC | [data-streaming](https://github.com/Lightprotocol/skills/tree/main/skills/data-streaming) | +| Wallets and payment flows with light-token. Includes privy, wallet adapter, mobile wallet adapter signing. Optional nullifier to prevent your onchain instruction from being executed more than once. | [payments-and-wallets](https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets) | +| Airdrops, DePIN, token distribution | [airdrop](https://github.com/Lightprotocol/skills/tree/main/skills/airdrop) | +| Anti-double-spend nullifiers for Privacy-preserving ZK programs | [zk-nullifier](https://github.com/Lightprotocol/skills/tree/main/skills/zk-nullifier) | +| Testing programs and clients on localnet, devnet, mainnet | [testing](https://github.com/Lightprotocol/skills/tree/main/skills/testing) | +| Help with Debugging and Questions via DeepWiki MCP | [ask-mcp](https://github.com/Lightprotocol/skills/tree/main/skills/ask-mcp) | + +> View all skills here: https://github.com/Lightprotocol/skills. + +All skills are included and are auto-discovered based on context. Ask about light-token, defi, payments, or program migration and the agent uses the relevant skill automatically. + + + +Add the marketplace and install: + +``` +/plugin marketplace add Lightprotocol/skills +/plugin install solana-rent-free-dev +``` + + +1. Open Settings (**Cmd+Shift+J** / **Ctrl+Shift+J**) +2. Navigate to **Rules & Commands** → **Project Rules** → **Add Rule** → **Remote Rule (GitHub)** +3. Enter: `https://github.com/Lightprotocol/skills.git` + + + +``` +npx skills add Lightprotocol/skills +``` + + + +## MCP + +The Model Context Protocol (MCP) is an open standard to connect AI apps to data sources and tools. The DeepWiki MCP server provides access to the Light Protocol repository with its search capabilities (AskDevin). + +### Installation + + + + +```bash +claude mcp add -s user -t http deepwiki https://mcp.deepwiki.com/mcp +claude mcp add -s user -t http zkcompression https://www.zkcompression.com/mcp +``` + + +**We recommend to create a command** that includes the tools listed below.
Simply **copy paste this snippet**. +
+ +```markdown expandable +--- +argument-hint: +description: Query Light Protocol repository and Solana resources via DeepWiki MCP for precise technical answers and help with debugging +allowed-tools: mcp__deepwiki__* +--- + +# /ask-deepwiki + +Answer: $ARGUMENTS + +Use DeepWiki MCP to provide precise technical answers. + +## Step 1: Identify Repository Scope + +1. **State understanding and plan** + +2. **If question is vague, ask for clarification:** +- What specific component or feature? +- What problem are you solving? +- What level of detail needed (overview vs implementation)? + +3. **Repository mapping:** +- ZK Compression/Light Protocol: `Lightprotocol/light-protocol` +- Solana fundamentals: `solana-labs/solana` +- Anchor framework: `solana-foundation/anchor` +- Complex questions: Query multiple repositories + +4. **Refine question to use:** +- Exact component names: `CompressedAccountMeta` not "account metadata" +- Specific operations: "verifies proof" not "handles proof" +- Concrete function names or error messages when available + +## Step 2: Query DeepWiki + +For the identified repository, call in sequence: + +1. `mcp__deepwiki__read_wiki_structure("repo-owner/repo-name")` +2. `mcp__deepwiki__read_wiki_contents("repo-owner/repo-name")` +3. `mcp__deepwiki__ask_question("repo-owner/repo-name", refined_question)` + +Query multiple repositories if question spans different systems. + +## Step 3: Format Response + +**Structure:** +1. Direct answer with technical explanation +2. Specific implementations and data structures +3. Code examples with inline comments +4. Source references (`file:line` from DeepWiki) +5. Related concepts if relevant + +**Language precision:** + +NEVER use vague verbs: +- "handles", "manages", "processes", "enables", "provides" + +ALWAYS use exact names: +- Functions: `LightAccount::new_init()`, `derive_address()` +- Types: `CompressedAccountMeta`, `ValidityProof`, `PackedAddressTreeInfo` +- Operations: "nullifies hash", "appends to state tree", "verifies proof" +- Fields: `tree_info`, `address`, `output_state_tree_index` + +Include `file:line` references from DeepWiki responses. + +## Notes + +- Always include source file references from DeepWiki responses +- Provide runnable code examples for implementation questions +- Ask follow-up questions to DeepWiki for clarification when needed +``` + +
+ + + +```bash +codex mcp add zkcompression -- npx -y mcp-remote@latest https://www.zkcompression.com/mcp +codex mcp add deepwiki -- npx -y mcp-remote@latest https://mcp.deepwiki.com/mcp +``` + + + + +```json +{ + "mcpServers": { + "deepwiki": { + "serverUrl": "https://mcp.deepwiki.com/sse" + }, + "zkcompression": { + "command": "npx", + "args": ["-y", "mcp-remote@latest", "https://www.zkcompression.com/mcp"] + } + } +} +``` + +
+ +### DeepWiki MCP-Tools + +- **read\_wiki\_structure** - Get a list of documentation topics for a GitHub repository + +```bash +mcp__deepwiki__read_wiki_structure("Lightprotocol/light-protocol") +``` + +- **read\_wiki\_contents** - View documentation about a GitHub repository + +```bash +mcp__deepwiki__read_wiki_contents("Lightprotocol/light-protocol") +``` + +- **ask\_question** - Ask any question about the GitHub repository and get a context-grounded response + +```bash +mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "your question") +``` + + +You can specify any public GitHub repo that is indexed with DeepWiki. When you connect to the MCP, you specify the repository when calling the tools. Learn more [here](https://docs.devin.ai/work-with-devin/deepwiki-mcp). + + +
+

Alternative: DeepWiki Web

+ + Ask DeepWiki + +
+ +If you don't have the MCP installed we recommend to use AskDevin in your browser. + + + + diff --git a/ai-tools/skills/airdrop/SKILL.md b/ai-tools/skills/airdrop/SKILL.md deleted file mode 100644 index 65e513d6..00000000 --- a/ai-tools/skills/airdrop/SKILL.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -name: solana-airdrop-client -description: "Distribute SPL tokens with 5000x cheaper cost. For Airdrops, Depins, or Token Distribution. Can implement custom claim for advanced usage." ---- - -# Airdrop - -Distribute compressed tokens to multiple recipients using TypeScript client. - -> **Disclaimer:** This guide demonstrates efficient token distribution on Solana using ZK compression. It does not constitute financial advice and does not endorse any specific token or project. - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [distribution approaches](#distribution-via-client) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Distribution via Client - -| Scale | Approach | -|-------|----------| -| <10,000 recipients | Single transaction - see [simple-airdrop.md](references/simple-airdrop.md) | -| 10,000+ recipients | Batched with retry - see [batched-airdrop.md](references/batched-airdrop.md) | -| No-code | [Airship by Helius](https://airship.helius.dev/) (up to 200k) | - -### Cost Comparison - -| Creation | Solana | Compressed | -| :---------------- | :------------------ | :----------------- | -| **Token Account** | ~2,000,000 lamports | **5,000** lamports | - -## Claim Program Reference Implementations - -Customize token distribution and let users claim. - -Simple Implementation: [simple-claim](./simple-claim) - Distributes compressed tokens that get decompressed on claim. - -Advanced Implementation: [distributor](https://github.com/Lightprotocol/distributor/tree/master) - Distributes SPL tokens, uses compressed PDAs to track claims. Based on jito Merkle distributor. - - - -| | distributor | simple-claim | -|--|-------------|--------------| -| Vesting | Linear Vesting | Cliff at Slot X | -| Partial claims | Yes | No | -| Clawback | Yes | No | -| Frontend | REST API + CLI | None | - -The programs are reference implementations and not audited. The Light Protocol Programs are audited and live on Solana Mainnet. - -### Cost - -| | Per-claim | 100k claims | -|--------------------------|-------------:|------------:| -| simple-claim | ~0.00001 SOL | ~1 SOL | -| distributor (compressed) | ~0.00005 SOL | ~5 SOL | -| distributor (original) | ~0.002 SOL | ~200 SOL | - -## Core Pattern - -```typescript -import { CompressedTokenProgram, getTokenPoolInfos, selectTokenPoolInfo } from "@lightprotocol/compressed-token"; -import { bn, createRpc, selectStateTreeInfo, buildAndSignTx, sendAndConfirmTx } from "@lightprotocol/stateless.js"; -import { ComputeBudgetProgram } from "@solana/web3.js"; - -const rpc = createRpc(RPC_ENDPOINT); - -// 1. Get infrastructure -const treeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos()); -const tokenPoolInfo = selectTokenPoolInfo(await getTokenPoolInfos(rpc, mint)); - -// 2. Build compress instruction (SPL → compressed to multiple recipients) -const ix = await CompressedTokenProgram.compress({ - payer: payer.publicKey, - owner: payer.publicKey, - source: sourceAta.address, // SPL ATA holding tokens - toAddress: recipients, // PublicKey[] - amount: recipients.map(() => bn(amount)), - mint, - tokenPoolInfo, - outputStateTreeInfo: treeInfo, -}); - -// 3. Send with compute budget (120k CU per recipient) -const instructions = [ - ComputeBudgetProgram.setComputeUnitLimit({ units: 120_000 * recipients.length }), - ix, -]; -const { blockhash } = await rpc.getLatestBlockhash(); -const tx = buildAndSignTx(instructions, payer, blockhash, []); -await sendAndConfirmTx(rpc, tx); -``` - -## Setup: Create Mint - -```typescript -import { createMint } from "@lightprotocol/compressed-token"; -import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token"; - -const { mint } = await createMint(rpc, payer, payer.publicKey, 9); -const ata = await getOrCreateAssociatedTokenAccount(rpc, payer, mint, payer.publicKey); -await mintTo(rpc, payer, mint, ata.address, payer.publicKey, 100_000_000_000); -``` - -## Compute Units - -| Recipients/instruction | CU | -|----------------------|-----| -| 1 | 120,000 | -| 5 | 170,000 | -| Batched tx | 500,000 | - -## Lookup Tables - -Reduce transaction size: - -| Network | Address | -|---------|---------| -| Mainnet | `9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ` | -| Devnet | `qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V` | - -## Advanced: Claim-Based - -For vesting, clawback, or user-initiated claims: - -| Implementation | Features | -|---------------|----------| -| [Merkle Distributor](https://github.com/Lightprotocol/distributor) | Linear vesting, partial claims, clawback, REST API | -| [Simple Claim](https://github.com/Lightprotocol/program-examples/tree/main/airdrop-implementations/simple-claim) | Cliff vesting at slot X | - -## Resources - -- **Docs**: [Airdrop Guide](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop) -- **Docs**: [Claim Implementations](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop#claim-reference-implementations) -- **Code**: [example-token-distribution](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution) -- **Tool**: [Airship by Helius](https://airship.helius.dev/) diff --git a/ai-tools/skills/airdrop/references/batched-airdrop.md b/ai-tools/skills/airdrop/references/batched-airdrop.md deleted file mode 100644 index 0471e8a0..00000000 --- a/ai-tools/skills/airdrop/references/batched-airdrop.md +++ /dev/null @@ -1,285 +0,0 @@ -# Batched Airdrop (10,000+ recipients) - -For large-scale distributions with retry logic and blockhash management. - -## Create Instruction Batches - -```typescript -import { - CompressedTokenProgram, - TokenPoolInfo, - selectTokenPoolInfo, -} from "@lightprotocol/compressed-token"; -import { - bn, - selectStateTreeInfo, - StateTreeInfo, -} from "@lightprotocol/stateless.js"; -import { - ComputeBudgetProgram, - TransactionInstruction, - PublicKey, -} from "@solana/web3.js"; - -interface CreateAirdropParams { - amount: number | bigint; - recipients: PublicKey[]; - payer: PublicKey; - sourceTokenAccount: PublicKey; - mint: PublicKey; - stateTreeInfos: StateTreeInfo[]; - tokenPoolInfos: TokenPoolInfo[]; - maxRecipientsPerInstruction?: number; // default: 5 - maxInstructionsPerTransaction?: number; // default: 3 - computeUnitLimit?: number; // default: 500_000 - computeUnitPrice?: number; -} - -export async function createAirdropInstructions({ - amount, - recipients, - payer, - sourceTokenAccount, - mint, - stateTreeInfos, - tokenPoolInfos, - maxRecipientsPerInstruction = 5, - maxInstructionsPerTransaction = 3, - computeUnitLimit = 500_000, - computeUnitPrice, -}: CreateAirdropParams): Promise { - const batches: TransactionInstruction[][] = []; - const amountBn = bn(amount.toString()); - - for ( - let i = 0; - i < recipients.length; - i += maxRecipientsPerInstruction * maxInstructionsPerTransaction - ) { - const instructions: TransactionInstruction[] = []; - - instructions.push( - ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }) - ); - if (computeUnitPrice) { - instructions.push( - ComputeBudgetProgram.setComputeUnitPrice({ microLamports: computeUnitPrice }) - ); - } - - const treeInfo = selectStateTreeInfo(stateTreeInfos); - const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos); - - for (let j = 0; j < maxInstructionsPerTransaction; j++) { - const startIdx = i + j * maxRecipientsPerInstruction; - const recipientBatch = recipients.slice( - startIdx, - startIdx + maxRecipientsPerInstruction - ); - - if (recipientBatch.length === 0) break; - - instructions.push( - await CompressedTokenProgram.compress({ - payer, - owner: payer, - source: sourceTokenAccount, - toAddress: recipientBatch, - amount: recipientBatch.map(() => amountBn), - mint, - tokenPoolInfo, - outputStateTreeInfo: treeInfo, - }) - ); - } - - if (instructions.length > (computeUnitPrice ? 2 : 1)) { - batches.push(instructions); - } - } - - return batches; -} -``` - -## Background Blockhash Updater - -```typescript -import { Rpc } from "@lightprotocol/stateless.js"; - -export let currentBlockhash: string; - -export async function updateBlockhash( - connection: Rpc, - signal: AbortSignal -): Promise { - const { blockhash } = await connection.getLatestBlockhash(); - currentBlockhash = blockhash; - - (function updateInBackground() { - if (signal.aborted) return; - const timeoutId = setTimeout(async () => { - if (signal.aborted) return; - try { - const { blockhash } = await connection.getLatestBlockhash(); - currentBlockhash = blockhash; - } catch (error) { - console.error("Failed to update blockhash:", error); - } - updateInBackground(); - }, 30_000); - - signal.addEventListener("abort", () => clearTimeout(timeoutId)); - })(); -} -``` - -## Sign and Send with Retry - -```typescript -import { Rpc, sendAndConfirmTx } from "@lightprotocol/stateless.js"; -import { - Keypair, - PublicKey, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; -import { currentBlockhash, updateBlockhash } from "./update-blockhash"; - -export enum BatchResultType { - Success = "success", - Error = "error", -} - -export type BatchResult = - | { type: BatchResultType.Success; index: number; signature: string } - | { type: BatchResultType.Error; index: number; error: string }; - -export async function* signAndSendAirdropBatches( - batches: TransactionInstruction[][], - payer: Keypair, - connection: Rpc, - maxRetries = 3 -): AsyncGenerator { - const abortController = new AbortController(); - await updateBlockhash(connection, abortController.signal); - - // Lookup table for your network - const lookupTableAddress = new PublicKey( - "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" // mainnet - // "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" // devnet - ); - const lookupTableAccount = ( - await connection.getAddressLookupTable(lookupTableAddress) - ).value!; - - const statusMap = new Array(batches.length).fill(0); // 0 = pending - - while (statusMap.includes(0)) { - const sends = statusMap.map(async (status, index) => { - if (status !== 0) return; - - let retries = 0; - while (retries < maxRetries && statusMap[index] === 0) { - if (!currentBlockhash) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - continue; - } - - try { - const tx = new VersionedTransaction( - new TransactionMessage({ - payerKey: payer.publicKey, - recentBlockhash: currentBlockhash, - instructions: batches[index], - }).compileToV0Message([lookupTableAccount]) - ); - tx.sign([payer]); - - const confirmedSig = await sendAndConfirmTx(connection, tx, { - skipPreflight: true, - commitment: "confirmed", - }); - - if (confirmedSig) { - statusMap[index] = 1; - return { type: BatchResultType.Success, index, signature: confirmedSig }; - } - } catch (e) { - retries++; - if (retries >= maxRetries) { - statusMap[index] = `err: ${(e as Error).message}`; - return { type: BatchResultType.Error, index, error: (e as Error).message }; - } - } - } - }); - - const results = await Promise.all(sends); - for (const result of results) { - if (result) yield result as BatchResult; - } - } - - abortController.abort(); -} -``` - -## Main Airdrop Script - -```typescript -import { Keypair, PublicKey } from "@solana/web3.js"; -import { calculateComputeUnitPrice, createRpc } from "@lightprotocol/stateless.js"; -import { createMint, getTokenPoolInfos } from "@lightprotocol/compressed-token"; -import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token"; -import { createAirdropInstructions } from "./create-instructions"; -import { BatchResultType, signAndSendAirdropBatches } from "./sign-and-send"; - -const RPC_ENDPOINT = `https://mainnet.helius-rpc.com?api-key=${process.env.HELIUS_API_KEY}`; -const connection = createRpc(RPC_ENDPOINT); -const PAYER = Keypair.fromSecretKey(/* your key */); - -const recipients = [ - // ... array of PublicKey addresses -].map((addr) => new PublicKey(addr)); - -(async () => { - // Create mint - const { mint } = await createMint(connection, PAYER, PAYER.publicKey, 9); - - // Mint supply - const ata = await getOrCreateAssociatedTokenAccount( - connection, PAYER, mint, PAYER.publicKey - ); - await mintTo(connection, PAYER, mint, ata.address, PAYER.publicKey, 10e9 * 1e9); - - // Get infrastructure - const stateTreeInfos = await connection.getStateTreeInfos(); - const tokenPoolInfos = await getTokenPoolInfos(connection, mint); - - // Create batches - const batches = await createAirdropInstructions({ - amount: 1e6, - recipients, - payer: PAYER.publicKey, - sourceTokenAccount: ata.address, - mint, - stateTreeInfos, - tokenPoolInfos, - computeUnitPrice: calculateComputeUnitPrice(10_000, 500_000), - }); - - // Execute - for await (const result of signAndSendAirdropBatches(batches, PAYER, connection)) { - if (result.type === BatchResultType.Success) { - console.log(`Batch ${result.index}: ${result.signature}`); - } else { - console.error(`Batch ${result.index} failed: ${result.error}`); - } - } -})(); -``` - -## Source - -- [example-token-distribution/optimized-airdrop](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution/src/optimized-airdrop) diff --git a/ai-tools/skills/airdrop/references/simple-airdrop.md b/ai-tools/skills/airdrop/references/simple-airdrop.md deleted file mode 100644 index a19b40ed..00000000 --- a/ai-tools/skills/airdrop/references/simple-airdrop.md +++ /dev/null @@ -1,112 +0,0 @@ -# Simple Airdrop (<10,000 recipients) - -Single transaction approach for small distributions. - -## Full Example - -```typescript -import { - CompressedTokenProgram, - getTokenPoolInfos, - selectTokenPoolInfo, -} from "@lightprotocol/compressed-token"; -import { - bn, - buildAndSignTx, - calculateComputeUnitPrice, - createRpc, - dedupeSigner, - selectStateTreeInfo, - sendAndConfirmTx, -} from "@lightprotocol/stateless.js"; -import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js"; -import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; - -const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=YOUR_KEY"; -const rpc = createRpc(RPC_ENDPOINT); -const mint = new PublicKey("YOUR_MINT_ADDRESS"); - -// Define recipients and amounts -const recipients = [ - new PublicKey("..."), - new PublicKey("..."), - new PublicKey("..."), -]; - -const amounts = [ - bn(20_000_000_000), // 20 tokens (9 decimals) - bn(30_000_000_000), // 30 tokens - bn(40_000_000_000), // 40 tokens -]; - -// Get infrastructure -const treeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos()); -const tokenPoolInfo = selectTokenPoolInfo(await getTokenPoolInfos(rpc, mint)); -const sourceAta = await getOrCreateAssociatedTokenAccount(rpc, payer, mint, payer.publicKey); - -// Build transaction -const units = 120_000 * recipients.length; -const instructions = [ - ComputeBudgetProgram.setComputeUnitLimit({ units }), - ComputeBudgetProgram.setComputeUnitPrice({ - microLamports: calculateComputeUnitPrice(20_000, units), - }), - await CompressedTokenProgram.compress({ - payer: payer.publicKey, - owner: payer.publicKey, - source: sourceAta.address, - toAddress: recipients, - amount: amounts, - mint, - tokenPoolInfo, - outputStateTreeInfo: treeInfo, - }), -]; - -// Use lookup table to reduce tx size -const lut = new PublicKey("9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"); // mainnet -// const lut = new PublicKey("qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V"); // devnet -const lookupTable = (await rpc.getAddressLookupTable(lut)).value!; - -const { blockhash } = await rpc.getLatestBlockhash(); -const tx = buildAndSignTx( - instructions, - payer, - blockhash, - dedupeSigner(payer, []), - [lookupTable] -); -const txId = await sendAndConfirmTx(rpc, tx); -console.log(`Airdrop complete: ${txId}`); -``` - -## Verify Distribution - -```typescript -for (const recipient of recipients) { - const accounts = await rpc.getCompressedTokenAccountsByOwner(recipient, { mint }); - const balance = accounts.items.reduce((sum, acc) => sum + Number(acc.parsed.amount), 0); - console.log(`${recipient}: ${balance / 1e9} tokens`); -} -``` - -## Same Amount to All Recipients - -```typescript -const amount = bn(1_000_000_000); // 1 token each - -const ix = await CompressedTokenProgram.compress({ - payer: payer.publicKey, - owner: payer.publicKey, - source: sourceAta.address, - toAddress: recipients, - amount: recipients.map(() => amount), // Same amount for all - mint, - tokenPoolInfo, - outputStateTreeInfo: treeInfo, -}); -``` - -## Source - -- [example-token-distribution/simple-airdrop](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution/src/simple-airdrop) diff --git a/ai-tools/skills/ask-mcp/SKILL.md b/ai-tools/skills/ask-mcp/SKILL.md deleted file mode 100644 index 667fe360..00000000 --- a/ai-tools/skills/ask-mcp/SKILL.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -name: research-deepwiki -description: Query Light Protocol and related repositories via DeepWiki MCP. Use when answering questions about compressed accounts, Light SDK, Solana development, Claude Code features, or agent skills. Triggers on technical questions requiring repository context. ---- - -# DeepWiki Research - -Query repositories via DeepWiki MCP to answer technical questions with precise, source-backed answers. - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [execution steps](#execution-steps) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Execution Steps - -### 1. Read Required Context in Local Repo - -Use `/init` skill and index current repository, if you have not already - -### 2. Identify Question Scope - -Determine the domain: -- Programs, client SDKs, architecture, implementation details -- Specific components (LightAccount, ValidityProof, CPI, etc.) - -### 2. Fetch Repository Context - -Select the appropriate repository based on question scope: - -**Light Protocol (compressed accounts, state trees, ZK compression, Light SDK)** -``` -mcp__deepwiki__read_wiki_structure("Lightprotocol/light-protocol") -mcp__deepwiki__read_wiki_contents("Lightprotocol/light-protocol") -mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "your question") -``` - -**Solana Development (programs, accounts, general Solana)** -``` -mcp__deepwiki__read_wiki_structure("blueshift-gg/blueshift-dashboard") -mcp__deepwiki__read_wiki_contents("blueshift-gg/blueshift-dashboard") -mcp__deepwiki__ask_question("blueshift-gg/blueshift-dashboard", "your question") -``` - -**Claude Code (CLI features, commands, configuration)** -``` -mcp__deepwiki__read_wiki_structure("anthropics/claude-code") -mcp__deepwiki__read_wiki_contents("anthropics/claude-code") -mcp__deepwiki__ask_question("anthropics/claude-code", "your question") -``` - -**Agent Skills (skills, subagents, patterns)** -``` -mcp__deepwiki__read_wiki_structure("anthropics/skills") -mcp__deepwiki__read_wiki_contents("anthropics/skills") -mcp__deepwiki__ask_question("anthropics/skills", "your question") - -### 3. Apply Precision Rules - -**AVOID:** -- Vague verbs: "handles", "manages", "processes", "enables", "provides" -- Abstract nouns: "operations", "management", "coordination" -- -ing endings when a precise verb exists -- Marketing language: "comprehensive", "rich", "seamless", "enables", "provides capability" -- ZK terminology in user-facing docs: "inclusion proof", "non-inclusion proof" → Instead describe what it does: "prove the address doesn't exist", "prove the account hash exists" -- Generic descriptions: "account metadata" instead of "CompressedAccountMeta" - - -**USE:** -- Exact function/method names -- Concrete data structures -- Specific operations: "sets discriminator", "creates hash", "nullifies account" -- File:line references -- "reference" when describing u8 indices that reference accounts in the accounts array (standard Solana terminology) -- "CPI" as a verb (e.g., "CPIs the Account Compression Program") -- Technical method/function names used as verbs when contextually clear - -`Describe exactly what happens.` - -### 4. Format Response - -Structure answers with: -- Technical precision -- Code examples with inline comments -- Source references (file paths, line numbers) - -## Example Output - -**Question:** What do you include in instruction data for compressed account creation? - -**Answer:** - ---- - -## Compressed Account Creation Instruction Data - -Instruction data contains three components for proof verification and tree coordination. - -**Components:** - -```rust -pub struct InstructionData { - proof: ValidityProof, - address_tree_info: PackedAddressTreeInfo, - output_state_tree_index: u8, - // + custom account data -} -``` - -**1. Non-inclusion proof** -- `ValidityProof` proves the derived address does not exist in the specified address tree -- Client generates via `getValidityProof()` RPC call - -**2. Tree location parameters** -- `PackedAddressTreeInfo`: Specifies which address tree registers the address (u8 index, not pubkey) -- `output_state_tree_index`: Specifies which state tree stores the compressed account hash - -**3. Custom account data** -- Program-specific fields (e.g., `message: String`, user data) - -**Execution flow:** - -1. Address tree proves uniqueness, stores address -2. State tree stores account hash -3. ValidityProof cryptographically guarantees address is unused - -**Why separate trees:** - -Compressed accounts require client-generated cryptographic proof that address doesn't exist (unlike regular Solana where runtime checks PDA existence). Address trees enforce uniqueness; state trees store account hashes. - -**Packed structs** use `u8` indices to reference accounts in `remaining_accounts`, reducing transaction size. \ No newline at end of file diff --git a/ai-tools/skills/data-streaming/SKILL.md b/ai-tools/skills/data-streaming/SKILL.md deleted file mode 100644 index c82e0376..00000000 --- a/ai-tools/skills/data-streaming/SKILL.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -name: data-streaming -description: "Stream Light Protocol account state via Laserstream gRPC. Covers token accounts, mint accounts, and compressible PDAs with hot/cold lifecycle tracking. Use when building custom data pipelines, aggregators, or indexers." ---- - -# Data Streaming - -Stream Light Protocol account state transitions via Laserstream gRPC. - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [domain references](#domain-references) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## When NOT to use - -For simple account lookups, call `get_account_interface` from `light-client`. It races hot and cold lookups automatically. - -This skill is for continuous data pipelines: aggregators, market makers, and indexers that need real-time state change notifications rather than point queries. - -## Domain references - -| Audience | Reference | -| -------- | --------- | -| All — shared architecture (read first) | [references/shared.md](references/shared.md) | -| Token accounts (SPL-compatible, 165 bytes) | [references/token-accounts.md](references/token-accounts.md) | -| Mint accounts (borsh-deserialized, metadata) | [references/mint-accounts.md](references/mint-accounts.md) | -| Compressible PDAs (per-program, discriminator check) | [references/pdas.md](references/pdas.md) | - -## Account type decision - -| Streaming... | Account type | Key difference | -|---|---|---| -| SPL-compatible token balances | Token accounts | `PodAccount` parsing, 165-byte layout | -| Mint supply, metadata, authorities | Mint accounts | `Mint::deserialize`, borsh layout | -| Your program's PDA state | Compressible PDAs | 8-byte discriminator check, per-program filter | - -## Program addresses - -| Program | Address | -|---------|---------| -| Light Token Program | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` | -| Light System Program | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` | - -## External references - -| Resource | Link | -|----------|------| -| Photon indexer | [github.com/helius-labs/photon](https://github.com/helius-labs/photon) | diff --git a/ai-tools/skills/data-streaming/references/mint-accounts.md b/ai-tools/skills/data-streaming/references/mint-accounts.md deleted file mode 100644 index 6bfbb1f3..00000000 --- a/ai-tools/skills/data-streaming/references/mint-accounts.md +++ /dev/null @@ -1,187 +0,0 @@ -# Streaming Mint Accounts - -Stream light-mint accounts and metadata via Laserstream gRPC. Mint accounts are borsh-deserialized `Mint` structs containing base mint data, metadata, and optional extensions (including `TokenMetadata` for name/symbol/URI). - -Events: new mints (raw data), mint updates (supply/authority changes), `TokenMetadata` (name, symbol, URI, additional_metadata), cold/hot transitions. - -For simple lookups, use `get_account_interface` instead of streaming (see `shared.md`). - -## Subscribe filters - -Constants: - -```rust -const LIGHT_TOKEN_PROGRAM_ID: &str = "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m"; -const ACCOUNT_TYPE_OFFSET: u64 = 165; -const ACCOUNT_TYPE_MINT: u8 = 1; -``` - -Additional imports beyond base streaming imports (see `shared.md`): - -```rust -use borsh::BorshDeserialize; -use helius_laserstream::grpc::subscribe_request_filter_accounts_filter::Filter; -use helius_laserstream::grpc::subscribe_request_filter_accounts_filter_memcmp::Data; -use helius_laserstream::grpc::{ - SubscribeRequestFilterAccountsFilter, - SubscribeRequestFilterAccountsFilterMemcmp, -}; -use light_token_interface::state::{ExtensionStruct, Mint}; -``` - -| Subscription | Detects | How | -|:-------------|:--------|:----| -| Account sub (`owner: cToken...`, `account_type == 1`) | Hot state + cold-to-hot | Pubkey cache lookup | -| Transaction sub (`account_include: cToken...`) | Hot-to-cold | Balance heuristic (`pre > 0, post == 0`) | - -```rust -let mut request = helius_laserstream::grpc::SubscribeRequest::default(); - -// 1. Account sub: mint state tracking + cold-to-hot detection. -// account_type == 1 (Mint) at byte offset 165. -request.accounts.insert( - "light_mints".to_string(), - SubscribeRequestFilterAccounts { - owner: vec![LIGHT_TOKEN_PROGRAM_ID.to_string()], - filters: vec![SubscribeRequestFilterAccountsFilter { - filter: Some(Filter::Memcmp(SubscribeRequestFilterAccountsFilterMemcmp { - offset: ACCOUNT_TYPE_OFFSET, - data: Some(Data::Bytes(vec![ACCOUNT_TYPE_MINT])), - })), - }], - nonempty_txn_signature: Some(true), - ..Default::default() - }, -); - -// 2. Transaction sub: hot-to-cold detection. -request.transactions.insert( - "light_token_txns".to_string(), - SubscribeRequestFilterTransactions { - vote: Some(false), - failed: Some(false), - account_include: vec![LIGHT_TOKEN_PROGRAM_ID.to_string()], - ..Default::default() - }, -); - -let (stream, _handle) = subscribe(config, request); -tokio::pin!(stream); -``` - -Connection setup (`LaserstreamConfig`, mainnet/devnet URLs) is in `shared.md`. - -## Deserialize mint accounts - -Match on `UpdateOneof::Account`, borsh-deserialize as `Mint`, update cache, remove from `cold_cache`: - -```rust -use helius_laserstream::grpc::subscribe_update::UpdateOneof; - -Some(UpdateOneof::Account(account_update)) => { - if let Some(account_info) = account_update.account { - let pubkey: [u8; 32] = account_info.pubkey.as_slice().try_into().unwrap(); - - match Mint::deserialize(&mut account_info.data.as_slice()) { - Ok(mint) => { - cold_cache.remove(&pubkey); // no longer cold - cache.insert(pubkey, mint); - } - Err(e) => { - eprintln!( - "Failed to deserialize mint {}: {}", - bs58::encode(&pubkey).into_string(), - e - ); - } - } - } -} -``` - -## Hot-to-cold - -Uses the shared `find_closed_accounts` function directly. No discriminator check is needed for mints -- `CompressAndCloseMint` always drains lamports to zero, and `cache.remove` filters unrelated closures. See `shared.md` for the full `find_closed_accounts` implementation and transaction handler pattern. - -## TokenMetadata extraction - -Iterate mint extensions and match `ExtensionStruct::TokenMetadata`: - -```rust -fn extract_metadata(mint: &Mint) -> Option<(String, String, String)> { - let extensions = mint.extensions.as_ref()?; - - for ext in extensions { - if let ExtensionStruct::TokenMetadata(m) = ext { - let name = String::from_utf8_lossy(&m.name).to_string(); - let symbol = String::from_utf8_lossy(&m.symbol).to_string(); - let uri = String::from_utf8_lossy(&m.uri).to_string(); - return Some((name, symbol, uri)); - } - } - None -} -``` - -## Data layouts - -### Mint - -```rust -#[repr(C)] -pub struct Mint { - pub base: BaseMint, - pub metadata: MintMetadata, - pub reserved: [u8; 16], - pub account_type: u8, - pub compression: CompressionInfo, - pub extensions: Option>, -} - -#[repr(C)] -pub struct BaseMint { - pub mint_authority: Option, - pub supply: u64, - pub decimals: u8, - pub is_initialized: bool, - pub freeze_authority: Option, -} - -#[repr(C)] -pub struct MintMetadata { - pub version: u8, - pub mint_decompressed: bool, - pub mint: Pubkey, - pub mint_signer: [u8; 32], - pub bump: u8, -} -``` - -### TokenMetadata - -```rust -#[repr(C)] -pub struct TokenMetadata { - pub update_authority: Pubkey, // [0u8; 32] = immutable - pub mint: Pubkey, - pub name: Vec, - pub symbol: Vec, - pub uri: Vec, - pub additional_metadata: Vec, -} - -pub struct AdditionalMetadata { - pub key: Vec, - pub value: Vec, -} -``` - -## Additional dependencies - -Add to `Cargo.toml` alongside the shared streaming dependencies (see `shared.md`): - -```toml -[dependencies] -borsh = "0.10" -light-token-interface = "0.3.0" -``` diff --git a/ai-tools/skills/data-streaming/references/pdas.md b/ai-tools/skills/data-streaming/references/pdas.md deleted file mode 100644 index 86d95f1a..00000000 --- a/ai-tools/skills/data-streaming/references/pdas.md +++ /dev/null @@ -1,178 +0,0 @@ -# Streaming Compressible PDAs - -Per-program streaming for compressible PDAs. Programs built with the Light SDK expose standardized instruction discriminators: `compress_accounts_idempotent` and `decompress_accounts_idempotent`. The SDK generates these at compile time. - -Programs that do not implement these discriminators are not indexable with this approach. For indexing unknown programs, see [Universal indexing](#universal-indexing-unknown-programs) below. - -| Subscription | Detects | How | -|:-------------|:--------|:----| -| Account sub (`owner: ProgramX`) | Hot state + cold-to-hot | Pubkey cache lookup | -| Transaction sub (`account_include: ProgramX`) | Hot-to-cold | 8-byte discriminator check + balance heuristic | - -## Subscribe filters - -```rust -const PROGRAM_ID: &str = "YourProgramId1111111111111111111111111111111"; - -let mut request = helius_laserstream::grpc::SubscribeRequest::default(); - -// 1. Account sub: hot state tracking + cold-to-hot detection. -request.accounts.insert( - "program_pdas".to_string(), - SubscribeRequestFilterAccounts { - owner: vec![PROGRAM_ID.to_string()], - nonempty_txn_signature: Some(true), - ..Default::default() - }, -); - -// 2. Transaction sub: hot-to-cold detection. -// Catches all transactions involving the program. -// 99.999% are regular operations -- filtered out by an 8-byte discriminator check. -request.transactions.insert( - "program_txns".to_string(), - SubscribeRequestFilterTransactions { - vote: Some(false), - failed: Some(false), - account_include: vec![PROGRAM_ID.to_string()], - ..Default::default() - }, -); - -let (stream, _handle) = subscribe(config, request); -tokio::pin!(stream); -``` - -Replace `PROGRAM_ID` with your program's base58 public key. Connection setup and imports are in `shared.md`. - -## Hot-to-cold: discriminator check - -The Light SDK generates a standardized `compress_accounts_idempotent` instruction on every conformant program. Its discriminator is the first 8 bytes of `sha256("global:compress_accounts_idempotent")`: - -```rust -const COMPRESS_DISCRIMINATOR: [u8; 8] = [70, 236, 171, 120, 164, 93, 113, 181]; -``` - -### Transaction handler - -For each transaction update, check for the compress discriminator. If absent, exit immediately. If present, call `find_closed_accounts` (defined in `shared.md`) to identify which PDAs were closed, then filter against your cache: - -```rust -use helius_laserstream::grpc::subscribe_update::UpdateOneof; - -Some(UpdateOneof::Transaction(tx_update)) => { - if let Some(ref tx_info) = tx_update.transaction { - if !has_compress_instruction(tx_info) { - return; // 99.999% of transactions exit here. - } - for pubkey in find_closed_accounts(tx_info) { - // Only process accounts we're tracking. - // This filters out unrelated accounts that went to 0 in the same tx. - if let Some(last_hot_state) = cache.remove(&pubkey) { - cold_cache.insert(pubkey, last_hot_state); - } - } - } -} -``` - -### `has_compress_instruction` - -Checks both outer instructions and inner instructions (CPI-wrapped calls) for the 8-byte discriminator: - -```rust -fn has_compress_instruction( - tx_info: &helius_laserstream::grpc::SubscribeUpdateTransactionInfo, -) -> bool { - let tx = match tx_info.transaction.as_ref() { - Some(t) => t, - None => return false, - }; - let meta = match tx_info.meta.as_ref() { - Some(m) => m, - None => return false, - }; - let msg = match tx.message.as_ref() { - Some(m) => m, - None => return false, - }; - - // Check outer instructions. - for ix in &msg.instructions { - if ix.data.len() >= 8 && ix.data[..8] == COMPRESS_DISCRIMINATOR { - return true; - } - } - // Check inner instructions (covers CPI-wrapped calls). - for inner in &meta.inner_instructions { - for ix in &inner.instructions { - if ix.data.len() >= 8 && ix.data[..8] == COMPRESS_DISCRIMINATOR { - return true; - } - } - } - false -} -``` - -> **Open question**: The `COMPRESS_DISCRIMINATOR` is currently hardcoded. There is an open TODO to expose `compress_accounts_idempotent` / `decompress_accounts_idempotent` discriminators via the `LightProgramInterface` trait so indexers can discover them programmatically. - -### Why this is tight - -- **Discriminator check** -- only inspect compress transactions. No false positives from unrelated transactions. -- **Balance heuristic** -- only flag accounts that actually closed. Idempotent no-ops produce no balance changes. -- **`cache.remove` filter** -- only process accounts you are tracking. Ignores unrelated closures in the same transaction. - -## Edge case: same-slot compress and decompress - -If `compress_accounts_idempotent` and `decompress_accounts_idempotent` execute in the same slot for the same PDA, the account sub may deliver hot state before the tx sub delivers the cold event. The cold handler would then override the correct hot state. - -Resolve with slot tracking: record the slot when marking cold, ignore cold events older than the latest hot event. - -In practice this does not happen. The forester compresses idle accounts, decompression is user-initiated, and both in the same slot is not a realistic scenario. - -## Cold-to-hot detection - -The account subscription delivers the re-created PDA. Match the pubkey against your cold cache and parse with your program's deserializer: - -```rust -Some(UpdateOneof::Account(account_update)) => { - if let Some(account) = account_update.account { - let pubkey: [u8; 32] = account.pubkey.as_slice().try_into().unwrap(); - - if cold_cache.remove(&pubkey).is_some() { - // Was cold, now hot. Parse with your program's deserializer. - cache.upsert_hot(pubkey, &account.data); - } else { - // New account or hot state update. - cache.upsert(pubkey, &account.data); - } - } -} -``` - -PDA pubkeys are deterministic. Same seeds, same program, same pubkey across hot/cold cycles. If the pubkey is in your cold cache, it was cold. - -## Universal indexing (unknown programs) - -For indexing all programs (not just yours), subscribe to **Light System Program** (`SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7`) transactions instead. All compression events CPI through it. - -Use `light-event` to parse compression events from inner instructions: - -```rust -use light_event::parse::event_from_light_transaction; -use light_compressible::DECOMPRESSED_PDA_DISCRIMINATOR; -``` - -### Going cold - -Output accounts with `discriminator != DECOMPRESSED_PDA_DISCRIMINATOR` are real data being written to the Merkle tree. These represent PDA state that has been compressed. - -### Going hot - -Output accounts with `discriminator == DECOMPRESSED_PDA_DISCRIMINATOR` are shell placeholders. `data[..32]` contains the PDA pubkey. These represent PDAs that have been decompressed back to on-chain state. - -### Notes - -- Requires borsh deserialization and CPI pattern matching. -- Same pipeline used by the [Photon indexer](https://github.com/helius-labs/photon). diff --git a/ai-tools/skills/data-streaming/references/shared.md b/ai-tools/skills/data-streaming/references/shared.md deleted file mode 100644 index 23979afa..00000000 --- a/ai-tools/skills/data-streaming/references/shared.md +++ /dev/null @@ -1,162 +0,0 @@ -# Shared Streaming Architecture - -## Two-subscription architecture - -Two gRPC subscriptions per program or token program. Nothing else. - -| Subscription | Detects | How | -|:-------------|:--------|:----| -| Account sub (filter by `owner`) | Hot state + cold-to-hot | Pubkey cache lookup | -| Transaction sub (filter by `account_include`) | Hot-to-cold | Balance heuristic (`pre > 0, post == 0`) | - -Two data structures track state: - -- `cache: HashMap<[u8; 32], T>` -- hot account state (for quoting/routing) -- `cold_cache: HashMap<[u8; 32], AccountInterface>` -- cold accounts with `ColdContext` (for building load instructions) - -**Why two subscriptions?** Owner-filtered account subscriptions miss close events. When `compress_accounts_idempotent` or `compress_and_close` closes an account, the owner changes to System Program. The account sub stops matching. The transaction sub catches it because the program is still in the transaction's account list. - -## Laserstream connection - -### Dependencies - -```toml -[dependencies] -helius-laserstream = "0.1" -tokio = { version = "1", features = ["full"] } -futures = "0.3" -bs58 = "0.5" -``` - -### Imports - -```rust -use futures::StreamExt; -use helius_laserstream::grpc::{ - SubscribeRequestFilterAccounts, SubscribeRequestFilterTransactions, -}; -use helius_laserstream::{subscribe, LaserstreamConfig}; -``` - -### Connect - -```rust -// Mainnet: -let config = LaserstreamConfig::new( - "https://laserstream-mainnet-ewr.helius-rpc.com".to_string(), - std::env::var("HELIUS_API_KEY")?, -); - -// Devnet: -let config = LaserstreamConfig::new( - "https://laserstream-devnet-ewr.helius-rpc.com".to_string(), - std::env::var("HELIUS_API_KEY")?, -); -``` - -## `find_closed_accounts` function - -Identifies accounts closed in a transaction using a balance heuristic: `pre_balances[i] > 0 && post_balances[i] == 0`. Builds the full account key list from static keys and loaded ALT addresses. - -```rust -fn find_closed_accounts( - tx_info: &helius_laserstream::grpc::SubscribeUpdateTransactionInfo, -) -> Vec<[u8; 32]> { - let meta = match &tx_info.meta { - Some(m) => m, - None => return vec![], - }; - let msg = match tx_info.transaction.as_ref().and_then(|t| t.message.as_ref()) { - Some(m) => m, - None => return vec![], - }; - - let mut all_keys: Vec<&[u8]> = msg.account_keys.iter().map(|k| k.as_slice()).collect(); - all_keys.extend(meta.loaded_writable_addresses.iter().map(|k| k.as_slice())); - all_keys.extend(meta.loaded_readonly_addresses.iter().map(|k| k.as_slice())); - - let mut closed = Vec::new(); - for (i, key) in all_keys.iter().enumerate() { - if key.len() == 32 - && meta.pre_balances.get(i).copied().unwrap_or(0) > 0 - && meta.post_balances.get(i).copied().unwrap_or(1) == 0 - { - closed.push(<[u8; 32]>::try_from(*key).unwrap()); - } - } - closed -} -``` - -### Cache handler - -When a transaction closes accounts, check each against `cache`. `cache.remove` filters out unrelated closures in the same transaction. For each removed account, spawn an async fetch of `AccountInterface` (which includes `ColdContext`) and insert into `cold_cache`. - -```rust -use helius_laserstream::grpc::subscribe_update::UpdateOneof; - -Some(UpdateOneof::Transaction(tx_update)) => { - if let Some(ref tx_info) = tx_update.transaction { - for pubkey in find_closed_accounts(tx_info) { - if cache.remove(&pubkey).is_some() { - let rpc = rpc.clone(); - let cold_cache = cold_cache.clone(); - tokio::spawn(async move { - if let Ok(Some(iface)) = rpc.get_account_interface(&pubkey, None).await { - cold_cache.insert(pubkey, iface); - } - }); - } - } - } -} -``` - -For PDAs, the hot-to-cold handler differs in two ways: (1) an 8-byte discriminator check (`has_compress_instruction`) wraps the handler to filter non-compression transactions, and (2) the `cold_cache` stores `last_hot_state` directly instead of async-fetching `AccountInterface`. See `pdas.md`. - -## Cold-to-hot detection pattern - -The account subscription delivers the re-created account. Remove the pubkey from `cold_cache` (if present) and insert parsed data into `cache`. Tokens and mints use `cache.insert` after a simple `cold_cache.remove`. PDAs branch on the remove result -- see `pdas.md` for the `upsert_hot`/`upsert` variant. - -Token/mint pattern: - -```rust -Some(UpdateOneof::Account(account_update)) => { - if let Some(account) = account_update.account { - let pubkey: [u8; 32] = account.pubkey.as_slice().try_into().unwrap(); - - cold_cache.remove(&pubkey); // no longer cold - cache.insert(pubkey, parsed_data); - } -} -``` - -PDA pubkeys are deterministic. Same seeds, same program, same pubkey across hot/cold cycles. If the pubkey is in `cold_cache`, it was cold. - -## Point queries - -`getAccountInfo` returns null for cold accounts. `get_account_interface()` races hot and cold lookups: - -```rust -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; - -let config = LightClientConfig::new( - "https://api.devnet.solana.com".to_string(), - Some("https://photon.helius.com?api-key=YOUR_KEY".to_string()), -); -let client = LightClient::new(config).await?; -let result = client.get_account_interface(&pubkey, None).await?; - -if let Some(account) = result.value { - let data = account.data(); - if account.is_cold() { - // Compressed. data() returns full account bytes. - } -} -``` - -## Hot/cold lifecycle - -```text -created (hot) --> compress (cold, Merkle tree) --> decompress (hot, same pubkey) -``` diff --git a/ai-tools/skills/data-streaming/references/token-accounts.md b/ai-tools/skills/data-streaming/references/token-accounts.md deleted file mode 100644 index 88d40aec..00000000 --- a/ai-tools/skills/data-streaming/references/token-accounts.md +++ /dev/null @@ -1,171 +0,0 @@ -# Streaming Token Accounts - -Light token accounts share the same 165-byte base layout as SPL Token. Existing SPL Token parsers work without modification. Two gRPC subscriptions target the Light Token Program: an account subscription for hot state and cold-to-hot detection, and a transaction subscription for hot-to-cold detection. - -For simple account lookups, use `get_account_interface` instead of streaming. See `shared.md` for connection setup, `find_closed_accounts`, cold-to-hot base pattern, and point queries. - -## Parsing - -`PodAccount` from `spl_token_2022_interface` parses SPL-token, SPL-token-2022, and Light-token accounts identically. For accounts with extensions, truncate to 165 bytes before parsing. - -```rust -use spl_pod::bytemuck::pod_from_bytes; -use spl_token_2022_interface::pod::PodAccount; - -let parsed: &PodAccount = pod_from_bytes(&data[..165])?; -``` - -## Subscribe filters - -Constants: - -```rust -const LIGHT_TOKEN_PROGRAM_ID: &str = "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m"; -const TOKEN_ACCOUNT_SIZE: u64 = 165; -const ACCOUNT_TYPE_OFFSET: u64 = 165; -const ACCOUNT_TYPE_TOKEN: u8 = 2; -``` - -Additional imports beyond base streaming imports (see `shared.md`): - -```rust -use helius_laserstream::grpc::subscribe_request_filter_accounts_filter::Filter; -use helius_laserstream::grpc::subscribe_request_filter_accounts_filter_memcmp::Data; -use helius_laserstream::grpc::{ - SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter, - SubscribeRequestFilterAccountsFilterMemcmp, SubscribeRequestFilterTransactions, -}; -``` - -Full `SubscribeRequest` construction: - -```rust -let mut request = helius_laserstream::grpc::SubscribeRequest::default(); - -// 1. Account sub: hot state tracking + cold-to-hot detection. -// Base token accounts (exactly 165 bytes, no extensions). -request.accounts.insert( - "light_tokens".to_string(), - SubscribeRequestFilterAccounts { - owner: vec![LIGHT_TOKEN_PROGRAM_ID.to_string()], - filters: vec![SubscribeRequestFilterAccountsFilter { - filter: Some(Filter::Datasize(TOKEN_ACCOUNT_SIZE)), - }], - nonempty_txn_signature: Some(true), - ..Default::default() - }, -); - -// Extended token accounts (account_type == 2 at byte 165). -request.accounts.insert( - "light_tokens_extended".to_string(), - SubscribeRequestFilterAccounts { - owner: vec![LIGHT_TOKEN_PROGRAM_ID.to_string()], - filters: vec![SubscribeRequestFilterAccountsFilter { - filter: Some(Filter::Memcmp(SubscribeRequestFilterAccountsFilterMemcmp { - offset: ACCOUNT_TYPE_OFFSET, - data: Some(Data::Bytes(vec![ACCOUNT_TYPE_TOKEN])), - })), - }], - nonempty_txn_signature: Some(true), - ..Default::default() - }, -); - -// 2. Transaction sub: hot-to-cold detection. -request.transactions.insert( - "light_token_txns".to_string(), - SubscribeRequestFilterTransactions { - vote: Some(false), - failed: Some(false), - account_include: vec![LIGHT_TOKEN_PROGRAM_ID.to_string()], - ..Default::default() - }, -); - -let (stream, _handle) = subscribe(config, request); -tokio::pin!(stream); -``` - -## Hot-to-cold - -Uses shared `find_closed_accounts` directly (see `shared.md`). No discriminator check is needed for tokens -- `compress_and_close` always drains lamports to zero, and `cache.remove` filters out unrelated closures. - -## Cold-to-hot - -The account subscription delivers the re-created account when a token account is decompressed. Parse with `PodAccount` and update the cache: - -```rust -Some(UpdateOneof::Account(account_update)) => { - if let Some(account) = account_update.account { - let pubkey: [u8; 32] = account.pubkey.as_slice().try_into().unwrap(); - let parsed: &PodAccount = pod_from_bytes(&account.data[..165])?; - - cold_cache.remove(&pubkey); // no longer cold - cache.insert(pubkey, *parsed); - } -} -``` - -## Data layout - -165 bytes base, identical to SPL Token Account. - -| Field | Offset | Size | -|:------|:-------|:-----| -| `mint` | 0 | 32 | -| `owner` | 32 | 32 | -| `amount` | 64 | 8 | -| `delegate` | 72 | 36 | -| `state` | 108 | 1 | -| `is_native` | 109 | 12 | -| `delegated_amount` | 121 | 8 | -| `close_authority` | 129 | 36 | -| `account_type` | 165 | 1 | - -`account_type = 2` at byte 165 indicates extensions follow (borsh-encoded `Option>`). - -## Extensions - -Extensions are borsh-encoded as `Option>` after the 165-byte base. These are not needed for indexing or trading. - -Deserialize with `Token::deserialize`: - -```rust -use borsh::BorshDeserialize; -use light_token_interface::state::{Token, ExtensionStruct}; - -let token = Token::deserialize(&mut data.as_slice())?; - -if let Some(exts) = &token.extensions { - for ext in exts { - if let ExtensionStruct::Compressible(info) = ext { - // info.compression_authority, info.rent_sponsor, info.last_claimed_slot - } - } -} -``` - -| Variant | Description | -|:--------|:------------| -| `TokenMetadata(TokenMetadata)` | Name, symbol, URI, additional metadata | -| `PausableAccount(PausableAccountExtension)` | Marker: mint is pausable (no data; pause state lives on mint) | -| `PermanentDelegateAccount(PermanentDelegateAccountExtension)` | Marker: mint has permanent delegate | -| `TransferFeeAccount(TransferFeeAccountExtension)` | Withheld fees from transfers | -| `TransferHookAccount(TransferHookAccountExtension)` | Marker: mint has transfer hook | -| `CompressedOnly(CompressedOnlyExtension)` | Compressed-only token (stores delegated amount) | -| `Compressible(CompressibleExtension)` | Compression config: authority, rent sponsor, timing | - -Source: [light-token-interface](https://github.com/Lightprotocol/light-protocol/tree/main/program-libs/token-interface/src/state/extensions) - -## Additional dependencies - -Additions to `Cargo.toml` beyond base streaming dependencies (see `shared.md`): - -```toml -[dependencies] -borsh = "0.10" -light-token-interface = "0.3.0" -spl-token-2022-interface = "0.1" -spl-pod = "0.5" -``` diff --git a/ai-tools/skills/defi-program/SKILL.md b/ai-tools/skills/defi-program/SKILL.md deleted file mode 100644 index b5525dd3..00000000 --- a/ai-tools/skills/defi-program/SKILL.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -name: defi-program -description: "Build rent-free DeFi programs on Solana. Anchor macros or Pinocchio patterns for AMMs, vaults, lending. Covers state structs, account creation, CPI, testing, and client SDK trait implementation." ---- - -# Rent-Free DeFi Programs - -The Light SDK pays rent-exemption for PDAs, token accounts, and mints (98% cost savings). Program logic stays the same. - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [domain references](#domain-references) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Domain References - -| Audience | Reference | -| -------- | --------- | -| Anchor pattern | [references/anchor.md](references/anchor.md) | -| Pinocchio pattern | [references/pinocchio.md](references/pinocchio.md) | -| Client SDK (LightProgramInterface) | [references/client-sdk.md](references/client-sdk.md) | -| Testing | [references/testing.md](references/testing.md) | -| CPI instructions | [references/instructions.md](references/instructions.md) | -| FAQ | [references/faq.md](references/faq.md) | - -## When to use which - -| Criteria | Anchor | Pinocchio | -|----------|--------|-----------| -| Framework | `anchor-lang 0.31`, `#[light_program]` macro | `pinocchio 0.9`, `LightProgramPinocchio` derive | -| State struct | `Option` + `LightAccount` derive | `CompressionInfo` (non-optional) + `bytemuck::Pod + Zeroable` + `#[repr(C)]` | -| Account creation | `#[light_account(init, ...)]` attribute or `CreateTokenAccountCpi` | `CreateTokenAccountCpi`, `CreateMints` batch pattern | -| Compress/decompress handlers | Auto-generated by `#[light_program]` macro | Route `ProgramAccounts::COMPRESS_ACCOUNTS_IDEMPOTENT` etc. in entrypoint | -| Program ID constant | Not needed (macro derives it) | `pub const ID: Pubkey = pubkey_array!(...)` + `LIGHT_CPI_SIGNER` required | -| Dependencies | `light-sdk`, `light-sdk-macros`, `light-token`, `light-anchor-spl` | `light-account-pinocchio`, `light-token-pinocchio`, `bytemuck` | - -## Hot vs Cold Model - -After extended inactivity (multiple epochs without writes), accounts auto-compress to cold state. Programs only interact with hot accounts. Clients load cold accounts back on-chain via `create_load_instructions`. - -| | Hot (active) | Cold (inactive) | -|---|---|---| -| Storage | On-chain | Compressed | -| Latency | Normal | +0-200ms | -| Tx size | Normal | +100-2400 bytes | -| CU | Normal | +15k-400k CU | -| Program code | No change | No change | - -The hot path has zero overhead. - -## External References - -| Resource | Link | -|----------|------| -| Anchor AMM reference | [cp-swap-reference](https://github.com/Lightprotocol/cp-swap-reference) | -| Pinocchio swap reference | [pinocchio-swap](https://github.com/Lightprotocol/examples-light-token/tree/main/pinocchio/swap) | diff --git a/ai-tools/skills/defi-program/references/anchor.md b/ai-tools/skills/defi-program/references/anchor.md deleted file mode 100644 index 8abc9795..00000000 --- a/ai-tools/skills/defi-program/references/anchor.md +++ /dev/null @@ -1,311 +0,0 @@ -# Macro Pattern - -Declarative macros for rent-free account creation. The SDK generates CPI and proof handling code at compile time. - -## Dependencies - -From workspace `Cargo.toml` — versions required for macro-based programs: - -```toml -blake3 = { version = "=1.8.2", default-features = false } - -anchor-lang = "0.31.1" - -light-sdk = { version = "0.19.0", features = ["anchor", "idl-build", "v2", "anchor-discriminator", "cpi-context"] } -light-sdk-macros = "0.19.0" -light-sdk-types = { version = "0.19.0", features = ["v2", "cpi-context"] } -light-compressible = { version = "0.4.0", features = ["anchor"] } -light-token = { version = "0.4.0", features = ["anchor"] } -``` - -`blake3 = "=1.8.2"` is pinned at the workspace level to prevent edition2024 crate compilation errors. - -## Program Setup - -Every macro program requires three elements: - -1. **`LIGHT_CPI_SIGNER` constant** — derived from the program ID at compile time. -2. **`#[light_program]` + `#[allow(deprecated)]` + `#[program]`** — stacked attributes on the module. -3. **Explicit lifetimes** — `Context<'_, '_, '_, 'info, T<'info>>` on every instruction handler. -4. **Single params struct** with `CreateAccountsProof` field — multi-arg functions are not supported. - -```rust -pub const LIGHT_CPI_SIGNER: CpiSigner = - derive_light_cpi_signer!("PDAm7XVHEkBvzBYDh8qF3z8NxnYQzPjGQJKcHVmMZpT"); - -#[light_program] -#[allow(deprecated)] -#[program] -pub mod counter { - use super::*; - - pub fn create_counter<'info>( - ctx: Context<'_, '_, '_, 'info, CreateCounter<'info>>, - params: CreateCounterParams, - ) -> Result<()> { - ctx.accounts.counter.owner = ctx.accounts.owner.key(); - ctx.accounts.counter.count = params.count; - Ok(()) - } -} -``` - -The params struct always contains `CreateAccountsProof`: - -```rust -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct CreateCounterParams { - // We must create a compressed address at creation to ensure the account does not exist yet. - pub create_accounts_proof: CreateAccountsProof, - pub count: u64, -} -``` - -The `#[instruction(params: ...)]` attribute on the accounts struct wires params into the macro expansion: - -```rust -#[derive(Accounts, LightAccounts)] -#[instruction(params: CreateCounterParams)] -pub struct CreateCounter<'info> { - // ... -} -``` - -## Import Paths - -Two valid import styles exist across the examples: - -**Style A — `light_token::anchor` re-exports** (counter, token-transfer examples): - -```rust -use light_sdk::interface::CreateAccountsProof; -use light_token::anchor::{derive_light_cpi_signer, light_program, CompressionInfo, CpiSigner, LightAccount, LightAccounts}; -``` - -**Style B — direct `light_sdk_macros` + `light_sdk_types`** (create-mint, create-ata, create-token-account examples): - -```rust -use light_compressible::CreateAccountsProof; -use light_sdk::derive_light_cpi_signer; -use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::CpiSigner; -``` - -Both styles compile to the same output. Style B requires `light-sdk-macros` and `light-sdk-types` as separate dependencies. - -## State Structs - -Custom state requires `LightAccount` derive and `CompressionInfo` field. Only the counter example defines custom state: - -```rust -#[derive(Default, Debug, InitSpace, LightAccount)] -#[account] -pub struct Counter { - pub compression_info: Option, - pub owner: Pubkey, - pub count: u64, -} -``` - -**KEY RULE**: `Account<'info, T>` in the accounts struct only works if `T` derives `LightAccount`. Mint and token accounts do NOT derive `LightAccount`, so they use `UncheckedAccount<'info>` or `AccountInfo<'info>` instead. - -## Account Patterns - -### PDA init (counter) - -Standard Anchor `#[account(init, ...)]` with `#[light_account(init)]`: - -```rust -#[account( - init, - payer = fee_payer, - space = 8 + Counter::INIT_SPACE, - seeds = [COUNTER_SEED, owner.key().as_ref()], - bump, -)] -#[light_account(init)] -pub counter: Account<'info, Counter>, -``` - -### Mint init (create-mint) - -Uses `UncheckedAccount` with `#[light_account(init, mint::...)]`: - -```rust -/// CHECK: Validated by light-token CPI -#[account(mut)] -#[light_account(init, - mint::signer = mint_signer, - mint::authority = fee_payer, - mint::decimals = 9, - mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], - mint::bump = params.mint_signer_bump -)] -pub mint: UncheckedAccount<'info>, -``` - -Requires a separate PDA signer account: - -```rust -/// CHECK: Validated by light-token CPI -#[account( - seeds = [MINT_SIGNER_SEED, authority.key().as_ref()], - bump, -)] -pub mint_signer: UncheckedAccount<'info>, -``` - -### Mint with metadata (create-mint) - -Extends mint init with metadata fields: - -```rust -/// CHECK: Validated by light-token CPI -#[account(mut)] -#[light_account(init, - mint::signer = mint_signer, - mint::authority = fee_payer, - mint::decimals = 9, - mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], - mint::bump = params.mint_signer_bump, - mint::name = params.name.clone(), - mint::symbol = params.symbol.clone(), - mint::uri = params.uri.clone(), - mint::update_authority = authority, - mint::additional_metadata = params.additional_metadata.clone() -)] -pub mint: UncheckedAccount<'info>, -``` - -Params struct includes metadata fields and `AdditionalMetadata`: - -```rust -use light_token::AdditionalMetadata; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct CreateMintWithMetadataParams { - pub create_accounts_proof: CreateAccountsProof, - pub mint_signer_bump: u8, - pub name: Vec, - pub symbol: Vec, - pub uri: Vec, - pub additional_metadata: Option>, -} -``` - -### ATA init (create-ata) - -```rust -/// CHECK: Validated by light-token CPI -#[account(mut)] -#[light_account(init, associated_token::authority = ata_owner, associated_token::mint = ata_mint, associated_token::bump = params.ata_bump)] -pub ata: UncheckedAccount<'info>, -``` - -### Token account init (create-token-account) - -```rust -/// CHECK: Validated by light-token CPI -#[account( - mut, - seeds = [VAULT_SEED, mint.key().as_ref()], - bump, -)] -#[light_account( - init, - token::authority = [VAULT_SEED, self.mint.key()], - token::mint = mint, - token::owner = vault_authority, - token::bump = params.vault_bump -)] -pub vault: UncheckedAccount<'info>, -``` - -### Transfer with destination ATA init (token-transfer) - -Macro init for the destination combined with `TransferInterfaceCpi` in the handler body: - -```rust -/// CHECK: Validated by light-token CPI -#[account(mut)] -#[light_account(init, - associated_token::authority = recipient, - associated_token::mint = mint, - associated_token::bump = params.dest_ata_bump -)] -pub destination: UncheckedAccount<'info>, -``` - -## Infrastructure Accounts - -Different account types require different infrastructure accounts: - -**PDA (counter):** -- `compression_config: AccountInfo<'info>` — validated by Light System CPI - -**Token accounts (mint, ATA, token account):** -- `light_token_compressible_config: AccountInfo<'info>` -- `rent_sponsor: AccountInfo<'info>` (mut) -- `light_token_program: AccountInfo<'info>` -- `light_token_cpi_authority: AccountInfo<'info>` (for mint and token account; not needed for ATA) - -The create-ata and create-token-account examples show explicit address constraints using known constants: - -```rust -use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; - -#[account(address = COMPRESSIBLE_CONFIG_V1)] -pub light_token_compressible_config: AccountInfo<'info>, - -#[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] -pub light_token_rent_sponsor: AccountInfo<'info>, - -#[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] -pub light_token_program: AccountInfo<'info>, -``` - -## Hybrid Pattern - -The token-transfer example combines macro account init with explicit CPI in the handler body. The macro creates the destination ATA, while `TransferInterfaceCpi` executes the transfer: - -```rust -use light_token::instruction::TransferInterfaceCpi; - -#[light_program] -#[allow(deprecated)] -#[program] -pub mod token_transfer { - use super::*; - - pub fn transfer<'info>( - ctx: Context<'_, '_, '_, 'info, Transfer<'info>>, - params: TransferParams, - ) -> Result<()> { - TransferInterfaceCpi::new( - params.amount, - params.decimals, - ctx.accounts.source.to_account_info(), - ctx.accounts.destination.to_account_info(), - ctx.accounts.authority.to_account_info(), - ctx.accounts.payer.to_account_info(), - ctx.accounts.light_token_cpi_authority.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - .invoke() - .map_err(|e| anchor_lang::prelude::ProgramError::from(e))?; - Ok(()) - } -} -``` - -Note the `.map_err(|e| anchor_lang::prelude::ProgramError::from(e))` — required because `TransferInterfaceCpi::invoke()` returns a different error type than Anchor's `Result<()>`. - -## Common Errors - -- **edition2024 crate errors** — pin `blake3 = "=1.8.2"` in workspace `[workspace.dependencies]`. -- **`Account<'info, Mint>` with `LightAccounts`** — use `UncheckedAccount<'info>` or `AccountInfo<'info>` instead. `Mint` does not derive `LightAccount`, so `Account<'info, Mint>` will fail. -- **Wrong field names** — use `compression_config` (not `light_interface_config`), `light_token_compressible_config` (not `light_token_interface_config`). -- **Missing `#[allow(deprecated)]`** — required between `#[light_program]` and `#[program]`. Some examples use file-level `#![allow(unexpected_cfgs, deprecated)]` instead. -- **Missing `LIGHT_CPI_SIGNER` constant** — every macro program must define this with `derive_light_cpi_signer!` using the program's ID string. -- **Multi-arg functions** — must use a single params struct containing `CreateAccountsProof`. The macro expansion expects exactly one params argument after `ctx`. \ No newline at end of file diff --git a/ai-tools/skills/defi-program/references/client-sdk.md b/ai-tools/skills/defi-program/references/client-sdk.md deleted file mode 100644 index 9ae20b43..00000000 --- a/ai-tools/skills/defi-program/references/client-sdk.md +++ /dev/null @@ -1,183 +0,0 @@ -# Client SDK - -Implement `LightProgramInterface` in your program's SDK crate so routers/aggregators can integrate. - -## LightProgramInterface Trait - -```rust -pub trait LightProgramInterface { - type Variant: Pack + Clone + Debug; - type Instruction; - - fn program_id() -> Pubkey; - fn instruction_accounts(&self, ix: &Self::Instruction) -> Vec; - fn load_specs( - &self, - cold_accounts: &[AccountInterface], - ) -> Result>, Box>; -} -``` - -- `instruction_accounts` -- returns the pubkeys the instruction reads/writes. -- `load_specs` -- given cold `AccountInterface`s (with `ColdContext`), returns the `AccountSpec`s that `create_load_instructions` needs to bring them back on-chain. - -## Anchor Example - -```rust -pub struct AmmSdk { - pub pool_state_pubkey: Pubkey, - pub observation_key: Pubkey, - pub token_0_vault: Pubkey, - pub token_1_vault: Pubkey, - pub token_0_mint: Pubkey, - pub token_1_mint: Pubkey, - pub lp_mint: Pubkey, - pub amm_config: Pubkey, -} - -pub enum AmmInstruction { - Swap, - Deposit, - Withdraw, -} - -impl LightProgramInterface for AmmSdk { - type Variant = LightAccountVariant; - type Instruction = AmmInstruction; - - fn program_id() -> Pubkey { - PROGRAM_ID - } - - fn instruction_accounts(&self, ix: &Self::Instruction) -> Vec { - match ix { - AmmInstruction::Swap => vec![ - self.pool_state_pubkey, - self.observation_key, - self.token_0_vault, - self.token_1_vault, - self.token_0_mint, - self.token_1_mint, - ], - AmmInstruction::Deposit | AmmInstruction::Withdraw => vec![ - self.pool_state_pubkey, - self.observation_key, - self.token_0_vault, - self.token_1_vault, - self.token_0_mint, - self.token_1_mint, - self.lp_mint, - ], - } - } - - fn load_specs( - &self, - cold_accounts: &[AccountInterface], - ) -> Result>, Box> { - // Build AccountSpec for each cold account by matching pubkey - // and deserializing its data into the macro-generated variant. - let mut specs = Vec::new(); - for account in cold_accounts { - let pubkey = account.key(); - if pubkey == self.pool_state_pubkey || pubkey == self.observation_key { - let parsed: PoolState = AnchorDeserialize::deserialize(&mut &account.data()[8..])?; - specs.push(AccountSpec::Pda(PdaSpec { interface: account.clone(), variant: parsed.into() })); - } else if pubkey == self.token_0_vault || pubkey == self.token_1_vault { - specs.push(AccountSpec::Token(account.clone())); - } - // ... - } - Ok(specs) - } -} -``` - -| Resource | Link | -|----------|------| -| Trait Implementation Example | [CpSwapSdk](https://github.com/Lightprotocol/cp-swap-reference/blob/main/programs/cp-swap/tests/program.rs#L409) | - -## Pinocchio Example - -```rust -use light_client::interface::{ - AccountInterface, AccountSpec, ColdContext, LightProgramInterface, PdaSpec, -}; -use light_account::token::Token; -use pinocchio_swap::{LightAccountVariant, PoolState, PoolStateSeeds, VaultSeeds}; - -/// Flat SDK struct. All fields populated at construction from pool state data. -pub struct SwapSdk { - pub pool_state_pubkey: Pubkey, - pub token_a_mint: Pubkey, - pub token_b_mint: Pubkey, - pub token_a_vault: Pubkey, - pub token_b_vault: Pubkey, - pub pool_authority: Pubkey, -} - -impl SwapSdk { - pub fn new(pool_state_pubkey: Pubkey, pool_data: &[u8]) -> Result { - let pool = PoolState::deserialize(&mut &pool_data[8..])?; - // ... derive addresses from pool state - Ok(Self { pool_state_pubkey, /* ... */ }) - } -} - -impl LightProgramInterface for SwapSdk { - type Variant = LightAccountVariant; - type Instruction = SwapInstruction; - - fn program_id() -> Pubkey { PROGRAM_ID } - - fn instruction_accounts(&self, ix: &Self::Instruction) -> Vec { - match ix { - SwapInstruction::Swap => vec![ - self.pool_state_pubkey, - self.token_a_vault, - self.token_b_vault, - self.token_a_mint, - self.token_b_mint, - ], - // ... - } - } - - fn load_specs( - &self, - cold_accounts: &[AccountInterface], - ) -> Result>, Box> { - let mut specs = Vec::new(); - for account in cold_accounts { - if account.key == self.pool_state_pubkey { - let pool = PoolState::deserialize(&mut &account.data()[8..])?; - let variant = LightAccountVariant::PoolState { - seeds: PoolStateSeeds { /* ... */ }, - data: pool, - }; - specs.push(AccountSpec::Pda(PdaSpec::new(account.clone(), variant, PROGRAM_ID))); - } else if account.key == self.token_a_vault { - let token: Token = Token::deserialize(&mut &account.data()[..])?; - let variant = LightAccountVariant::Vault(TokenDataWithSeeds { - seeds: VaultSeeds { pool: /* ... */, mint: /* ... */ }, - token_data: token, - }); - specs.push(AccountSpec::Pda(PdaSpec::new(account.clone(), variant, PROGRAM_ID))); - } - // ... token_b_vault, mints - } - Ok(specs) - } -} -``` - -| Resource | Link | -|----------|------| -| Full SDK implementation | [sdk.rs](https://github.com/Lightprotocol/examples-light-token/blob/main/pinocchio/swap/tests/sdk.rs) | - -## Dependencies - -```toml -[dependencies] -light-client = { version = "0.19.0", features = ["v2"] } -``` \ No newline at end of file diff --git a/ai-tools/skills/defi-program/references/faq.md b/ai-tools/skills/defi-program/references/faq.md deleted file mode 100644 index b1a5cd70..00000000 --- a/ai-tools/skills/defi-program/references/faq.md +++ /dev/null @@ -1,18 +0,0 @@ -# FAQ - -**How does it prevent re-init attacks?** -When creating an account for the first time, the SDK provides a proof that the account doesn't exist in the cold address space. The SVM already verifies this for the onchain space. Both address spaces are checked before creation, preventing re-init attacks, even if the account is currently cold. - -**Who triggers compression?** -Miners (Forester nodes) compress accounts that have been inactive for an extended period of time (when their virtual rent balance drops below threshold). In practice, having to load cold accounts should be rare. The common path (hot) has no extra overhead and does not increase CU or txn size. - -**How is the SDK able to sponsor rent exemption?** -When accounts compress after extended inactivity, the on-chain rent-exemption is released back to the rent sponsor. This creates a revolving lifecycle: active "hot" accounts hold a rent-exempt lamports balance, inactive "cold" accounts release it back. The rent sponsor must be derived from the program owner. For all mint, ATA, and token accounts, the Light Token Program is the rent sponsor. For your own program-owned PDAs, the SDK derives a rent sponsor address automatically. - -**Do rent-free accounts increase CU?** -**Hot path (e.g. swap, deposit, withdraw):** No. Active accounts do not add CU overhead to your instructions. - -**First time init + loading cold accounts:** Yes, adds up to 15k-400k CU, depending on number and type of accounts being initialized or loaded. - -**Do I have to manually handle compression/decompression? (Pinocchio)** -No. `LightProgramPinocchio` generates the handlers. Simply add the generated handlers to your entrypoint, and update your init instruction. \ No newline at end of file diff --git a/ai-tools/skills/defi-program/references/instructions.md b/ai-tools/skills/defi-program/references/instructions.md deleted file mode 100644 index 88655aeb..00000000 --- a/ai-tools/skills/defi-program/references/instructions.md +++ /dev/null @@ -1,156 +0,0 @@ -# CPI Pattern - -Explicit CPI calls to Light Token. Standard `#[program]` and `#[derive(Accounts)]` — no macro transforms needed. - -## Dependencies - -Dependencies for CPI programs: - -```toml -anchor-lang = "0.31.1" -light-token = { version = "0.4.0", features = ["anchor"] } -``` - -No `light-sdk`, `light-sdk-macros`, or `light-sdk-types` required. The `light-token` crate re-exports all CPI types from `light_token::instruction`. - -## Shared pattern - -Every CPI call follows the same shape: - -1. **Import**: `use light_token::instruction::{TypeName};` -2. **Construct** struct literal with account and value fields -3. **Chain** (account creation CPIs only): `.idempotent()`, `.rent_free()` -4. **Invoke**: `.invoke()?` or `.invoke_signed(&[seeds])?` for PDA signers - -## Operations reference - -| Operation | Docs guide | GitHub example | -|-----------|-----------|----------------| -| `CreateAssociatedAccountCpi` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-ata) | -| `CreateTokenAccountCpi` | [create-token-account](https://zkcompression.com/light-token/cookbook/create-token-account) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-token-account) | -| `CreateMintCpi` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-mint) | -| `MintToCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to) | -| `MintToCheckedCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to-checked) | -| `BurnCpi` | [burn](https://zkcompression.com/light-token/cookbook/burn) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/burn) | -| `TransferCheckedCpi` | [transfer-checked](https://zkcompression.com/light-token/cookbook/transfer-checked) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-checked) | -| `TransferInterfaceCpi` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-interface) | -| `ApproveCpi` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/approve) | -| `RevokeCpi` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/revoke) | -| `FreezeCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/freeze) | -| `ThawCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/thaw) | -| `CloseAccountCpi` | [close-token-account](https://zkcompression.com/light-token/cookbook/close-token-account) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/close-token-account) | - -## E2E flow: create-mint → create-ata → mint-to → transfer - -```rust -use light_token::instruction::{ - CreateMintCpi, CreateMintParams, SystemAccountInfos, - CreateAssociatedAccountCpi, MintToCpi, TransferInterfaceCpi, -}; -use light_token::{CompressedProof, ExtensionInstructionData, TokenMetadataInstructionData}; - -// 1. Create mint with metadata -pub fn create_mint(ctx: Context, /* params omitted */) -> Result<()> { - let mint = light_token::instruction::find_mint_address(ctx.accounts.mint_seed.key).0; - - let extensions = Some(vec![ExtensionInstructionData::TokenMetadata( - TokenMetadataInstructionData { - update_authority: Some(ctx.accounts.authority.key().to_bytes().into()), - name: b"My Token".to_vec(), - symbol: b"MTK".to_vec(), - uri: b"https://example.com/metadata.json".to_vec(), - additional_metadata: None, - }, - )]); - - let params = CreateMintParams { - decimals: 9, - address_merkle_tree_root_index, - mint_authority: *ctx.accounts.authority.key, - proof, - compression_address, - mint, - bump, - freeze_authority: None, - extensions, - rent_payment: 16, // ~24 hours - write_top_up: 766, // ~3 hours per write - }; - - let system_accounts = SystemAccountInfos { - light_system_program: ctx.accounts.light_system_program.to_account_info(), - cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(), - registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(), - account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(), - account_compression_program: ctx.accounts.account_compression_program.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - }; - - CreateMintCpi { - mint_seed: ctx.accounts.mint_seed.to_account_info(), - authority: ctx.accounts.authority.to_account_info(), - payer: ctx.accounts.payer.to_account_info(), - address_tree: ctx.accounts.address_tree.to_account_info(), - output_queue: ctx.accounts.output_queue.to_account_info(), - compressible_config: ctx.accounts.compressible_config.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - rent_sponsor: ctx.accounts.rent_sponsor.to_account_info(), - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, - } - .invoke()?; - Ok(()) -} - -// 2. Create ATA — .idempotent() skips if exists, .rent_free() sponsors rent -pub fn create_ata(ctx: Context, bump: u8) -> Result<()> { - CreateAssociatedAccountCpi { - payer: ctx.accounts.payer.to_account_info(), - owner: ctx.accounts.owner.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - ata: ctx.accounts.associated_token_account.to_account_info(), - bump, - } - .idempotent() - .rent_free( - ctx.accounts.compressible_config.to_account_info(), - ctx.accounts.rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - .invoke()?; - Ok(()) -} - -// 3. Mint to — struct literal pattern -pub fn mint_to(ctx: Context, amount: u64) -> Result<()> { - MintToCpi { - mint: ctx.accounts.mint.to_account_info(), - destination: ctx.accounts.destination.to_account_info(), - amount, - authority: ctx.accounts.authority.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - max_top_up: None, - fee_payer: None, - } - .invoke()?; - Ok(()) -} - -// 4. Transfer — ::new() constructor, resolves source/destination types -pub fn transfer(ctx: Context, amount: u64, decimals: u8) -> Result<()> { - TransferInterfaceCpi::new( - amount, - decimals, - ctx.accounts.source.to_account_info(), - ctx.accounts.destination.to_account_info(), - ctx.accounts.authority.to_account_info(), - ctx.accounts.payer.to_account_info(), - ctx.accounts.cpi_authority.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - .invoke()?; - Ok(()) -} -``` \ No newline at end of file diff --git a/ai-tools/skills/defi-program/references/pinocchio.md b/ai-tools/skills/defi-program/references/pinocchio.md deleted file mode 100644 index 21b460cd..00000000 --- a/ai-tools/skills/defi-program/references/pinocchio.md +++ /dev/null @@ -1,279 +0,0 @@ -# Pinocchio Pattern - -Rent-free DeFi accounts using Pinocchio. The Light-SDK sponsors rent-exemption for your PDAs, token accounts, and mints. - -## Dependencies - -```toml -[dependencies] -light-account-pinocchio = { version = "0.20", features = ["token", "std"] } -light-token-pinocchio = "0.20" - -pinocchio = "0.9" -pinocchio-pubkey = { version = "0.3", features = ["const"] } -pinocchio-system = "0.3" -borsh = { version = "0.10.4", default-features = false } -bytemuck = { version = "1.21", features = ["derive"] } -``` - -## State Struct - -```rust -use borsh::{BorshDeserialize, BorshSerialize}; -use light_account_pinocchio::{CompressionInfo, LightPinocchioAccount}; - -#[derive( - Default, Debug, Copy, Clone, PartialEq, - BorshSerialize, BorshDeserialize, - LightPinocchioAccount, - bytemuck::Pod, bytemuck::Zeroable, -)] -#[repr(C)] -pub struct PoolState { - pub compression_info: CompressionInfo, - - // Your regular state... - pub fee_bps: u16, -} -``` - -NOTE: `CompressionInfo` is NOT `Option`. Uses `#[repr(C)]`, `bytemuck::Pod + Zeroable`. - -## Program Enum - -```rust -use light_account_pinocchio::{ - derive_light_cpi_signer, pubkey_array, CpiSigner, LightProgramPinocchio, -}; -use pinocchio::pubkey::Pubkey; - -pub const ID: Pubkey = pubkey_array!("YourProgram11111111111111111111111111111111"); -pub const LIGHT_CPI_SIGNER: CpiSigner = - derive_light_cpi_signer!("YourProgram11111111111111111111111111111111"); - -#[derive(LightProgramPinocchio)] -pub enum ProgramAccounts { - #[light_account(pda::seeds = [POOL_SEED, ctx.mint_a, ctx.mint_b], pda::zero_copy)] - PoolState(PoolState), - - #[light_account(token::seeds = [POOL_VAULT_SEED, ctx.pool, ctx.mint], token::owner_seeds = [POOL_AUTHORITY_SEED])] - Vault, - - #[light_account(associated_token)] - UserToken, -} -``` - -This auto-generates 4 instructions, discriminators, and the `LightAccountVariant` enum used by the client SDK. - -## Entrypoint - -```rust -pinocchio::entrypoint!(process_instruction); - -pub fn process_instruction( - _program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> Result<(), ProgramError> { - if instruction_data.len() < 8 { - return Err(ProgramError::InvalidInstructionData); - } - - let (disc, data) = instruction_data.split_at(8); - let disc: [u8; 8] = disc.try_into().unwrap(); - - match disc { - // your custom program logic... - discriminators::INITIALIZE => process_initialize(accounts, data), - discriminators::SWAP => process_swap(accounts, data), - - // add this: - ProgramAccounts::INITIALIZE_COMPRESSION_CONFIG => { - ProgramAccounts::process_initialize_config(accounts, data) // generated - } - ProgramAccounts::UPDATE_COMPRESSION_CONFIG => { - ProgramAccounts::process_update_config(accounts, data) - } - ProgramAccounts::COMPRESS_ACCOUNTS_IDEMPOTENT => { - ProgramAccounts::process_compress(accounts, data) - } - ProgramAccounts::DECOMPRESS_ACCOUNTS_IDEMPOTENT => { - ProgramAccounts::process_decompress(accounts, data) - } - _ => Err(ProgramError::InvalidInstructionData), - } -} -``` - -## Init Handler - -### Create Token Account - -```rust -use light_account_pinocchio::CreateTokenAccountCpi; - -CreateTokenAccountCpi { - payer: ctx.payer, - account: vault, - mint, - owner: *pool_authority.key(), -} -.rent_free( - ctx.light_token_config, - ctx.light_token_rent_sponsor, - ctx.system_program, - &crate::ID, -) -.invoke_signed(&[ - POOL_VAULT_SEED, - pool_key.as_ref(), - mint_key.as_ref(), - &[bump], -])?; -``` - -### Create Mints (Batch) - -```rust -use light_account_pinocchio::{CreateMints, CreateMintsStaticAccounts, SingleMintParams}; - -let sdk_mints: [SingleMintParams<'_>; 2] = [ - SingleMintParams { - decimals: 9, - mint_authority: authority_key, - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: mint_signer_a_key, - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_a_seeds), - token_metadata: None, - }, - // ... -]; - -CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: ctx.mint_signers, - mint_accounts: ctx.mints, - static_accounts: CreateMintsStaticAccounts { - fee_payer: ctx.payer, - compressible_config: ctx.light_token_config, - rent_sponsor: ctx.light_token_rent_sponsor, - cpi_authority: ctx.cpi_authority, - }, - cpi_context_offset: 1, -} -.invoke(&cpi_accounts)?; -``` - -### Full Initialize Processor - -```rust -use light_account_pinocchio::{ - prepare_compressed_account_on_init, CompressedCpiContext, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, CreateMints, CreateMintsStaticAccounts, CreateTokenAccountCpi, - InstructionDataInvokeCpiWithAccountInfo, InvokeLightSystemProgram, LightAccount, LightConfig, - SingleMintParams, -}; -use pinocchio::sysvars::{clock::Clock, Sysvar}; - -pub fn process( - ctx: &InitializeAccounts<'_>, - params: &InitializeParams, - remaining_accounts: &[AccountInfo], -) -> Result<(), LightSdkTypesError> { - // 1. Build CPI accounts - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - ctx.payer, - &remaining_accounts[params.create_accounts_proof.system_accounts_offset as usize..], - config, - ); - - // 2. Get address tree info + config - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info.get_tree_pubkey(&cpi_accounts)?; - let light_config = LightConfig::load_checked(ctx.compressible_config, &crate::ID)?; - let current_slot = Clock::get()?.slot; - - // 3. Create pool PDA (write to CPI context) - { - let cpi_context = CompressedCpiContext::first(); - let mut new_address_params = Vec::with_capacity(1); - let mut account_infos = Vec::with_capacity(1); - let pool_key = *ctx.pool.key(); - - prepare_compressed_account_on_init( - &pool_key, &address_tree_pubkey, address_tree_info, - params.create_accounts_proof.output_state_tree_index, - 0, &crate::ID, - &mut new_address_params, &mut account_infos, - )?; - - // Initialize pool state (zero-copy) - { - let mut data = ctx.pool.try_borrow_mut_data()?; - let pool_state: &mut PoolState = bytemuck::from_bytes_mut( - &mut data[8..8 + core::mem::size_of::()] - ); - pool_state.set_decompressed(&light_config, current_slot); - pool_state.token_a_mint = *ctx.mint_a().key(); - pool_state.token_b_mint = *ctx.mint_b().key(); - // ... remaining fields - } - - // Write to CPI context - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - // ... - }; - instruction_data.invoke_write_to_cpi_context_first( - CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - } - )?; - } - - // 4. Create mints - CreateMints { /* ... */ }.invoke(&cpi_accounts)?; - - // 5. Create vaults (rent-free) - CreateTokenAccountCpi { /* ... */ }.rent_free(/* ... */).invoke_signed(/* ... */)?; - - Ok(()) -} -``` - -## Client SDK - -Implement `LightProgramInterface` so routers can detect and load cold accounts. See the full Pinocchio implementation in [client-sdk.md](./client-sdk.md#pinocchio-example). - -## Testing - -For the full Pinocchio test lifecycle (init with proof, swap, compress, load + swap), see [testing.md](./testing.md#pinocchio-test). - -## How it works - -The SDK sponsors rent-exemption for all accounts. After extended inactivity, Forester nodes compress accounts to cold state — your program code does not change. See [hot/cold model](../SKILL.md#hot-vs-cold-model) for overhead numbers. For client-side cold-account loading details, see the `defi-router` skill. - -## FAQ - -See [faq.md](./faq.md) for common questions (re-init attacks, compression triggers, rent sponsoring, CU overhead, Pinocchio handler generation). - -## Reference - -| Resource | Link | -|----------|------| -| Pinocchio swap reference | [pinocchio-swap](https://github.com/Lightprotocol/examples-light-token/tree/main/pinocchio/swap) | -| Full SDK implementation | [sdk.rs](https://github.com/Lightprotocol/examples-light-token/blob/main/pinocchio/swap/tests/sdk.rs) | -| Full test | [test_lifecycle.rs](https://github.com/Lightprotocol/examples-light-token/blob/main/pinocchio/swap/tests/test_lifecycle.rs) | diff --git a/ai-tools/skills/defi-program/references/testing.md b/ai-tools/skills/defi-program/references/testing.md deleted file mode 100644 index bd8c9091..00000000 --- a/ai-tools/skills/defi-program/references/testing.md +++ /dev/null @@ -1,124 +0,0 @@ -# Testing - -## Dependencies - -```toml -[dev-dependencies] -light-program-test = "0.19.0" -light-client = { version = "0.19.0", features = ["v2"] } -``` - -## Anchor Test - -```rust -use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc}; -use light_sdk::interface::rent::SLOTS_PER_EPOCH; -use light_client::interface::{create_load_instructions, LightProgramInterface}; - -#[tokio::test] -async fn test_pool_lifecycle() { - let config = ProgramTestConfig::new_v2(true, Some(vec![("my_amm", MY_AMM_ID)])); - let mut rpc = LightProgramTest::new(config).await.unwrap(); - - // 1. Init pool (rent-free) - // ... build and send init instruction ... - - // 2. Swap (hot path - works normally) - // ... build and send swap instruction ... - - // 3. Trigger compression (advance time) - rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); - - let pool_interface = rpc - .get_account_interface(&pool_address, None) - .await - .unwrap() - .value - .unwrap(); - assert!(pool_interface.is_cold()); - - // 4. Build SDK and get load instructions - let sdk = AmmSdk::new(pool_address, pool_interface.data()).unwrap(); - let pubkeys = sdk.instruction_accounts(&AmmInstruction::Deposit); - let accounts = rpc.get_multiple_account_interfaces(pubkeys.iter().collect(), None).await.unwrap().value; - let cold: Vec<_> = accounts.into_iter().flatten().filter(|a| a.is_cold()).collect(); - - let specs = sdk.load_specs(&cold).unwrap(); - let load_ixs = create_load_instructions(&specs, payer.pubkey(), config_pda, &rpc).await.unwrap(); - - // 5. Send transaction - rpc.create_and_send_transaction(&load_ixs, &payer.pubkey(), &[&payer]).await.unwrap(); -} -``` - -| Resource | Link | -|----------|------| -| Test example | [program.rs](https://github.com/Lightprotocol/cp-swap-reference/blob/main/programs/cp-swap/tests/program.rs) | - -## Pinocchio Test - -```rust -use light_program_test::{LightProgramTest, Rpc}; -use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, - AccountSpec, CreateAccountsProofInput, LightProgramInterface, -}; - - -#[tokio::test] -async fn test_pool_lifecycle() { - let mut rpc = LightProgramTest::new(config).await.unwrap(); - - // 1. Initialize pool (rent-free: pool PDA, 2 mints, 2 vaults) - let proof = get_create_accounts_proof(&rpc, &program_id, vec![ - CreateAccountsProofInput::pda(pool_state), - CreateAccountsProofInput::mint(mint_a_signer), - CreateAccountsProofInput::mint(mint_b_signer), - ]).await.unwrap(); - - rpc.create_and_send_transaction(&[init_ix], &payer.pubkey(), &[&payer, &authority]) - .await.unwrap(); - - // 2. Swap (hot path) - rpc.create_and_send_transaction(&[swap_ix], &user.pubkey(), &[&user]) - .await.unwrap(); - - // 3. Trigger compression for the purpose of the test. - const SLOTS_PER_EPOCH: u64 = 13500; - rpc.warp_slot_forward(SLOTS_PER_EPOCH * 30).await.unwrap(); - - // 4. Build SDK from pool state, fetch cold accounts - let pool_iface = rpc.get_account_interface(&pool_state, None).await.unwrap().value.unwrap(); - assert!(pool_iface.is_cold()); - - let sdk = SwapSdk::new(pool_state, pool_iface.data()).unwrap(); - let pubkeys = sdk.instruction_accounts(&SwapInstruction::Swap); - let accounts = rpc.get_multiple_account_interfaces(pubkeys.iter().collect(), None) - .await.unwrap().value; - let cold: Vec<_> = accounts.into_iter().flatten().filter(|a| a.is_cold()).collect(); - - // 5. Load cold accounts - let mut specs = sdk.load_specs(&cold).unwrap(); - // Add user ATAs - let ata_a = rpc.get_associated_token_account_interface(&user.pubkey(), &mint_a, None) - .await.unwrap().value.unwrap(); - let ata_b = rpc.get_associated_token_account_interface(&user.pubkey(), &mint_b, None) - .await.unwrap().value.unwrap(); - specs.push(AccountSpec::Ata(ata_a)); - specs.push(AccountSpec::Ata(ata_b)); - - let load_ixs = create_load_instructions(&specs, payer.pubkey(), config_pda, &rpc) - .await.unwrap(); - - - // 6. Load and Swap - let mut all_ixs = load_ixs; - all_ixs.push(swap_ix); - rpc.create_and_send_transaction(&all_ixs, &user.pubkey(), &[&user]) - .await.unwrap(); -} -``` - -| Resource | Link | -|----------|------| -| Full test | [test_lifecycle.rs](https://github.com/Lightprotocol/examples-light-token/blob/main/pinocchio/swap/tests/test_lifecycle.rs) | \ No newline at end of file diff --git a/ai-tools/skills/defi-router/SKILL.md b/ai-tools/skills/defi-router/SKILL.md deleted file mode 100644 index a61d7948..00000000 --- a/ai-tools/skills/defi-router/SKILL.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: defi-router -description: "Integrate rent-free markets into routers and aggregators. Cold account detection, load instructions, Jito bundles. Use when adding Light Protocol market support to Jupiter, Orca, or custom router forks." ---- - -# Rent-Free Router Integration - -Existing quoting, routing, and swap-building logic stays the same. Add cold account detection and load instructions for rent-free markets. - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [domain references](#domain-references) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Domain References - -| Audience | Reference | -| -------- | --------- | -| Integration overview | [references/overview.md](references/overview.md) | -| Jito bundles | [references/jito.md](references/jito.md) | -| LightProgramInterface trait | [references/trait.md](references/trait.md) | - -## Key Types - -| Type | Source | Purpose | -|------|--------|---------| -| `AccountInterface` | `light-client` | Superset of Solana `Account` with `cold: Option` | -| `LightProgramInterface` | `light-client` | Trait program SDKs implement: `program_id() -> Pubkey`, `instruction_accounts(&self, ix) -> Vec`, `load_specs(&self, cold_accounts) -> Result>` | -| `AccountSpec` | `light-client` | Input to `create_load_instructions` | -| `create_load_instructions` | `light-client` | Builds load instructions for cold accounts (4 args: specs, payer, config_pda, rpc) | diff --git a/ai-tools/skills/defi-router/references/jito.md b/ai-tools/skills/defi-router/references/jito.md deleted file mode 100644 index 0c121118..00000000 --- a/ai-tools/skills/defi-router/references/jito.md +++ /dev/null @@ -1,5 +0,0 @@ -# Jito Bundles - -If load + swap exceed Solana's 1232-byte tx limit, send as a Jito bundle. The SDK deduplicates many account keys over the wire, so instructions that appear large in isolation will be incremental when combined with swap/deposit instructions. - -See [Router Integration — Jito](./overview.md#what-if-load--swap-exceed-solanas-tx-size-limit) for the full code example and tip account list. \ No newline at end of file diff --git a/ai-tools/skills/defi-router/references/overview.md b/ai-tools/skills/defi-router/references/overview.md deleted file mode 100644 index bc9dedd8..00000000 --- a/ai-tools/skills/defi-router/references/overview.md +++ /dev/null @@ -1,218 +0,0 @@ -# Router Integration - -Your existing quoting, routing, and swap-building logic stays the same. -The only addition: when a market has cold accounts, detect them, and prepend load instructions before the swap. - -## What changes - -| | Hot market (99%+) | Cold market | -|---|---|---| -| Quoting | No change | No change | -| Swap instruction | No change | No change | -| Transaction | No change | Prepend `create_load_instructions` | - -## Detecting cold accounts - -Track cold accounts using a `cold_cache: HashMap<[u8; 32], AccountInterface>`. - -Two concurrent gRPC subscriptions detect lifecycle transitions: an account subscription (owner filter) catches cold-to-hot, a transaction subscription (account_include filter) catches hot-to-cold via the balance heuristic (`pre > 0, post == 0`). For the full streaming implementation with `find_closed_accounts`, cache handlers, and connection setup, see the `data-streaming` skill. - -If you don't stream, call `get_multiple_account_interfaces` at swap time and check `is_cold()`. - -## Building swap transactions with cold accounts - -```rust -use light_client::interface::{create_load_instructions, LightProgramInterface}; - -// 1. Identify which accounts the swap touches -let pubkeys = sdk.instruction_accounts(&AmmInstruction::Swap); - -// 2. Check which are cold (from your streaming cache, or fetch) -let cold_pubkeys: Vec<_> = pubkeys.iter().filter(|p| cold_set.contains(p)).collect(); - -// 3. If any are cold, fetch their ColdContext and build load instructions -let mut ixs = vec![]; -if !cold_pubkeys.is_empty() { - let interfaces = rpc - .get_multiple_account_interfaces(cold_pubkeys, None) - .await? - .value; - let cold: Vec<_> = interfaces.into_iter().flatten().collect(); - let specs = sdk.load_specs(&cold)?; - ixs.extend(create_load_instructions(&specs, payer, config_pda, &rpc).await?); -} - -// 4. Swap instruction is unchanged -ixs.push(sdk.swap_ix(&swap_params)?); -``` - -## The LightProgramInterface trait - -Each rent-free AMM SDK exposes this trait. It returns which accounts an instruction reads/writes and builds load specs for cold ones. See [trait.md](./trait.md) for the trait definition. For framework-specific implementations, see the `defi-program` skill's `client-sdk.md`. - -## Full example - -### Dependencies - -```toml -[dependencies] -light-client = { version = "0.19.0", features = ["v2"] } - -# AMM SDK that implements LightProgramInterface (provided by the AMM team) -example-amm-sdk = "0.1" -``` - -### Code - -```rust -use light_client::interface::{create_load_instructions, LightProgramInterface}; -use example_amm_sdk::{ExampleAmmSdk, AmmInstruction}; - -// Construct SDK from pool data (same as before -- pool data is always available, -// hot or cold, via get_account_interface or your cache). -let sdk = ExampleAmmSdk::new(pool_address, pool_data)?; - -// Quote works the same regardless of hot/cold. -let quote = sdk.quote(amount_in, min_out)?; - -// Build transaction. -let mut ixs = vec![]; - -// Check if any swap accounts are cold. -let pubkeys = sdk.instruction_accounts(&AmmInstruction::Swap); -let cold_pubkeys: Vec<_> = pubkeys.iter().filter(|p| cold_set.contains(p)).collect(); - -if !cold_pubkeys.is_empty() { - // Fetch ColdContext for cold accounts. - let interfaces = rpc - .get_multiple_account_interfaces(cold_pubkeys, None) - .await? - .value; - let cold: Vec<_> = interfaces.into_iter().flatten().collect(); - let specs = sdk.load_specs(&cold)?; - ixs.extend(create_load_instructions(&specs, payer.pubkey(), config_pda, &rpc).await?); -} - -// Swap instruction is the same as without rent-free accounts. -ixs.push(sdk.swap_ix(&swap_params)?); - -rpc.send_transaction(&ixs, &payer).await?; -``` - -## Key types - -| Type | Source | Purpose | -|------|--------|---------| -| `AccountInterface` | `light-client` | Account data with optional `ColdContext` | -| `LightProgramInterface` | `light-client` | Trait that AMM SDKs implement | -| `AccountSpec` | `light-client` | Input to `create_load_instructions` | - -## Hot vs Cold - -| | Hot | Cold | -|---|-----|------| -| On-chain | Yes | Ledger (compressed) | -| Quote | Works | Works | -| Swap | Direct | Load first / Bundle | -| Latency | Normal | +0-200ms* | -| Tx size | Normal | +100-2400 bytes*| -| CU | Normal | +15k-400k CU*| - -*Depends on the number and type of cold accounts.* - -### When does a market go cold? - -Accounts go cold after extended inactivity. Their virtual rent balance drops -below a threshold and miners compress them onto the Solana ledger. - -They stay cold until any client loads them back in-flight via `create_load_instructions`. - -**Touching cold markets is rare.** The hot path has zero overhead. - -## FAQ - -**Do I need to change my swap instructions?** - -No. Swap instructions are identical. If the market is hot, the transaction -is the same as today. If cold, you prepend `create_load_instructions`. - -**Can I quote cold markets?** - -Yes. `get_account_interface` returns full account data regardless of hot/cold. -Quoting works the same. - -**Do rent-free markets increase latency?** - -**Hot (common path)**: No. - -**Cold**: Loading accounts adds 1-200ms depending on whether a validity proof -is needed. If load + swap exceed Solana's 1232 byte limit, use Jito bundles. - -**How long do accounts stay hot after loading?** - -Until they go inactive again. Each write resets the timer. The inactivity -threshold is configurable by the program owner (e.g. 24h of no writes). - -**What if load + swap exceed Solana's tx size limit?** - -Send as a Jito bundle. The SDK deduplicates many account keys over the wire, so instructions that appear large in isolation will be incremental when combined with swap/deposit instructions. - -```rust -use solana_sdk::{instruction::Instruction, pubkey::Pubkey, system_instruction}; - -const JITO_TIP_ACCOUNTS: &[&str] = &[ - "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5", - "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe", - "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY", - "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49", - "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh", - "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt", - "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL", - "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT", -]; - -fn jito_tip_ix(payer: &Pubkey, tip_lamports: u64) -> Instruction { - let tip_account = JITO_TIP_ACCOUNTS[rand::random::() % JITO_TIP_ACCOUNTS.len()] - .parse::().unwrap(); - system_instruction::transfer(payer, &tip_account, tip_lamports) -} - -// Add tip to last transaction, serialize, send to Jito -let tip_ix = jito_tip_ix(&payer.pubkey(), 10_000); // 10k lamports -swap_ixs.push(tip_ix); - -let bundle = vec![load_tx_base64, swap_tx_base64]; -let resp = client - .post("https://mainnet.block-engine.jito.wtf/api/v1/bundles") - .json(&serde_json::json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "sendBundle", - "params": [bundle, {"encoding": "base64"}] - })) - .send().await?; -``` - -**Do RPC providers support get_account_interface?** - -Yes. Supported by Helius and Triton. Can also be self-hosted via the -[open-source Photon indexer](https://github.com/helius-labs/photon). - -**What if the indexer is down?** - -Hot markets work as long as Solana is up. Cold accounts can't be loaded until -the indexer recovers. Compression is cryptographically verifiable -- integrity -doesn't depend on the indexer. - -**I don't stream. Can I still support cold markets?** - -Yes. At swap time, call `get_multiple_account_interfaces` for the instruction's -accounts and check `is_cold()`. This adds a round-trip per swap but requires -no streaming setup. - -## Reference - -| Resource | Link | -|----------|------| -| AMM Program | [cp-swap-reference](https://github.com/Lightprotocol/cp-swap-reference) | -| LightProgramInterface Trait Impl | [CpSwapSdk](https://github.com/Lightprotocol/cp-swap-reference/blob/main/programs/cp-swap/tests/program.rs#L409) | diff --git a/ai-tools/skills/defi-router/references/trait.md b/ai-tools/skills/defi-router/references/trait.md deleted file mode 100644 index b33f4922..00000000 --- a/ai-tools/skills/defi-router/references/trait.md +++ /dev/null @@ -1,22 +0,0 @@ -# LightProgramInterface Trait - -Each rent-free program SDK exposes this trait so routers can detect which accounts an instruction reads/writes and build load specs for cold ones. - -```rust -pub trait LightProgramInterface { - type Variant: Pack + Clone + Debug; - type Instruction; - - fn program_id() -> Pubkey; - fn instruction_accounts(&self, ix: &Self::Instruction) -> Vec; - fn load_specs( - &self, - cold_accounts: &[AccountInterface], - ) -> Result>, Box>; -} -``` - -- `instruction_accounts` -- returns the pubkeys the instruction reads/writes. -- `load_specs` -- given cold `AccountInterface`s (with `ColdContext`), returns the `AccountSpec`s that `create_load_instructions` needs to bring them back on-chain. - -For framework-specific implementation examples (Anchor, Pinocchio), see the `defi-program` skill's `client-sdk.md`. diff --git a/ai-tools/skills/dev-compressed-pda/SKILL.md b/ai-tools/skills/dev-compressed-pda/SKILL.md deleted file mode 100644 index 4f1cacdb..00000000 --- a/ai-tools/skills/dev-compressed-pda/SKILL.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -name: migration-program -description: Migrate Solana program to Light Protocol compressed accounts. Use when converting Anchor or native programs to use compressed state. Triggers on migration requests, compression integration, or Light Protocol adoption. -allowed-tools: Bash(git:*), Bash(cargo:*), Bash(anchor:*), Bash(light:*), Read, Edit, Glob, Grep, Write, Task, WebFetch, WebSearch, mcp__deepwiki__ask_question, mcp__claude-code-docs__SearchClaudeCodeDocs ---- - -# Solana Program Migration to Light Protocol - -Migrate Solana programs to use compressed accounts. - -**Goal:** Produce a working POC that builds and passes tests. - -### Client-Program Interaction Flow -```text - ├─ Client - │ ├─ Get ValidityProof from RPC. - │ ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo. - │ ├─ pack CompressedAccountMeta. - │ ├─ Build Instruction from PackedAccounts and CompressedAccountMetas. - │ └─ Send transaction. - │ - └─ Custom Program - ├─ CpiAccounts parse accounts consistent with PackedAccounts. - ├─ LightAccount instantiates from CompressedAccountMeta. - │ - └─ Light System Program CPI - ├─ Verify ValidityProof. - ├─ Update State Merkle tree. - ├─ Update Address Merkle tree. - └─ Complete atomic state transition. -``` - -## Reference Repos - -``` -/home/tilo/Workspace/program-examples/ -├── create-and-update/ # Basic create + update pattern -├── close-account/ # Close, burn, reinit patterns -├── token-escrow/ # Token + compressed account patterns -├── airdrop-implementations/distributor/ # Merkle + compressed -└── zk/ # ZK proof patterns - -/home/tilo/Workspace/examples-zk-compression/ -/home/tilo/Workspace/example-token-distribution/ -/home/tilo/Workspace/example-nodejs-client/ -``` - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [reference repos](#reference-repos) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Phase 1: Index Source Repo - -Clone and analyze the target repository. - -```bash -git clone {repo_url} -``` - -### 1.1 Detect Build System - -| Indicator | Build System | -|-----------|--------------| -| `Anchor.toml` | Anchor program | -| `Cargo.toml` with `solana-program` | Native program | - -### 1.2 Detect Test Framework - -| Indicator | Framework | -|-----------|-----------| -| `#[tokio::test]` | program-test or litesvm | -| `bankrun` | bankrun | -| `anchor test` | Anchor test | -| `light_program_test` | Already using Light (skip migration) | - -### 1.3 Check Solana Version - -```bash -grep solana Cargo.toml -``` - -### 1.4 Index All Accounts - -Find all `#[account]` structs in the program. - -## Phase 2: Classify Accounts - -| Classification | Action | Reason | -|----------------|--------|--------| -| User-owned | **MIGRATE** | Per-user state, best candidates | -| Config | **SKIP** | Read frequently, write rarely | -| Pool | **SKIP** | Concurrent access not supported | -| Global state | **SKIP** | Single instance, no benefit | - -## Phase 3: Index Reference Repos - -Find matching patterns before writing code: - -```bash -grep -r "LightAccount::new_init" /home/tilo/Workspace/program-examples/ -grep -r "LightAccount::new_mut" /home/tilo/Workspace/program-examples/ -grep -r "LightAccount::new_close" /home/tilo/Workspace/program-examples/ -``` - -Read matching files to understand patterns. - -## Phase 4: Migration - -### 4.1 Transform Account Structs - -| Pattern | Source | Use for | -|---------|--------|---------| -| Simple data | create-and-update | Basic CRUD | -| Close/reopen | close-account | Closeable accounts | -| Token flows | token-escrow, distributor | Token integration | - -### 4.2 Transform Instructions - -| Original | Compressed | Reference | -|----------|------------|-----------| -| Create PDA | `LightAccount::new_init()` | create-and-update | -| Update | `LightAccount::new_mut()` | create-and-update | -| Close | `LightAccount::new_close()` | close-account | -| Burn | `LightAccount::new_burn()` | close-account | -| Reinit | `LightAccount::new_empty()` | close-account | - -### 4.3 Update Dependencies - -```toml -[dependencies] -anchor-lang = "0.31" -light-sdk = { version = "0.14", features = ["anchor"] } -light-sdk-types = "0.5" - -[dev-dependencies] -light-program-test = "0.14" -light-client = "0.14" -``` - -### 4.4 Update Tests - -Migrate to `light_program_test`. - -## Phase 5: Build and Test Loop - -### Required Commands - -**Anchor programs:** -```bash -anchor build -anchor test -``` - -**Native programs:** -```bash -cargo build-sbf -cargo test-sbf -``` - -### Forbidden Shortcuts - -- Do NOT use `cargo build` (must use `cargo build-sbf`) -- Do NOT use `cargo test` (must use `cargo test-sbf`) -- Do NOT skip SBF compilation -- Tests MUST run against real BPF bytecode - -### Failure Recovery - -On failure, spawn debugger agent with error context. - -**Loop rules:** -1. Each debugger gets fresh context + previous debug reports -2. Each attempt tries something DIFFERENT -3. **NEVER GIVE UP** - keep spawning until fixed -4. Max 5 attempts per error - -Do NOT proceed until all tests pass. - -## Phase 6: Cleanup - -Run only after tests pass: - -```bash -rm -rf target/ -``` - -## DeepWiki Fallback - -If no matching pattern in reference repos: - -``` -mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "How to {operation}?") -``` diff --git a/ai-tools/skills/dev-compressed-pda/references/error-codes.md b/ai-tools/skills/dev-compressed-pda/references/error-codes.md deleted file mode 100644 index efd84702..00000000 --- a/ai-tools/skills/dev-compressed-pda/references/error-codes.md +++ /dev/null @@ -1,266 +0,0 @@ ---- -name: error-codes -description: "Error code reference for ZK Compression. Codes 6000-16034. Search hex value (0x1770-0x3EB2) or error name." ---- - -# Error Codes Reference - -> Activate: "error code", "0x", hex code, error name, debugging errors - -Complete error code reference for ZK Compression programs. - -## How to Use - -Search for your error code or hex value with `Cmd+F` / `Ctrl+F`. - -## 6000 - 6053 / SystemProgramError Variants - -> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/programs/system/src/errors.rs#L133) - -| Code | Hex | Error | Message | -| :--- | :----- | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | -| 6000 | 0x1770 | `SumCheckFailed` | "Sum check failed" | -| 6001 | 0x1771 | `SignerCheckFailed` | "Signer check failed" | -| 6002 | 0x1772 | `CpiSignerCheckFailed` | "Cpi signer check failed" | -| 6003 | 0x1773 | `ComputeInputSumFailed` | "Computing input sum failed." | -| 6004 | 0x1774 | `ComputeOutputSumFailed` | "Computing output sum failed." | -| 6005 | 0x1775 | `ComputeRpcSumFailed` | "Computing rpc sum failed." | -| 6006 | 0x1776 | `InvalidAddress` | "InvalidAddress" | -| 6007 | 0x1777 | `DeriveAddressError` | "DeriveAddressError" | -| 6008 | 0x1778 | `CompressedSolPdaUndefinedForCompressSol` | "CompressedSolPdaUndefinedForCompressSol" | -| 6009 | 0x1779 | `DecompressLamportsUndefinedForCompressSol` | "DecompressLamportsUndefinedForCompressSol" | -| 6010 | 0x177A | `CompressedSolPdaUndefinedForDecompressSol` | "CompressedSolPdaUndefinedForDecompressSol" | -| 6011 | 0x177B | `DeCompressLamportsUndefinedForDecompressSol` | "DeCompressLamportsUndefinedForDecompressSol" | -| 6012 | 0x177C | `DecompressRecipientUndefinedForDecompressSol` | "DecompressRecipientUndefinedForDecompressSol" | -| 6013 | 0x177D | `WriteAccessCheckFailed` | "WriteAccessCheckFailed" | -| 6014 | 0x177E | `InvokingProgramNotProvided` | "InvokingProgramNotProvided" | -| 6015 | 0x177F | `InvalidCapacity` | "InvalidCapacity" | -| 6016 | 0x1780 | `InvalidMerkleTreeOwner` | "InvalidMerkleTreeOwner" | -| 6017 | 0x1781 | `ProofIsNone` | "ProofIsNone" | -| 6018 | 0x1782 | `ProofIsSome` | "Proof is some but no input compressed accounts or new addresses provided." | -| 6019 | 0x1783 | `EmptyInputs` | "EmptyInputs" | -| 6020 | 0x1784 | `CpiContextAccountUndefined` | "CpiContextAccountUndefined" | -| 6021 | 0x1785 | `CpiContextEmpty` | "CpiContextEmpty" | -| 6022 | 0x1786 | `CpiContextMissing` | "CpiContextMissing" | -| 6023 | 0x1787 | `DecompressionRecipientDefined` | "DecompressionRecipientDefined" | -| 6024 | 0x1788 | `SolPoolPdaDefined` | "SolPoolPdaDefined" | -| 6025 | 0x1789 | `AppendStateFailed` | "AppendStateFailed" | -| 6026 | 0x178A | `InstructionNotCallable` | "The instruction is not callable" | -| 6027 | 0x178B | `CpiContextFeePayerMismatch` | "CpiContextFeePayerMismatch" | -| 6028 | 0x178C | `CpiContextAssociatedMerkleTreeMismatch` | "CpiContextAssociatedMerkleTreeMismatch" | -| 6029 | 0x178D | `NoInputs` | "NoInputs" | -| 6030 | 0x178E | `InputMerkleTreeIndicesNotInOrder` | "Input merkle tree indices are not in ascending order." | -| 6031 | 0x178F | `OutputMerkleTreeIndicesNotInOrder` | "Output merkle tree indices are not in ascending order." | -| 6032 | 0x1790 | `OutputMerkleTreeNotUnique` | "OutputMerkleTreeNotUnique" | -| 6033 | 0x1791 | `DataFieldUndefined` | "DataFieldUndefined" | -| 6034 | 0x1792 | `ReadOnlyAddressAlreadyExists` | "ReadOnlyAddressAlreadyExists" | -| 6035 | 0x1793 | `ReadOnlyAccountDoesNotExist` | "ReadOnlyAccountDoesNotExist" | -| 6036 | 0x1794 | `HashChainInputsLenghtInconsistent` | "HashChainInputsLenghtInconsistent" | -| 6037 | 0x1795 | `InvalidAddressTreeHeight` | "InvalidAddressTreeHeight" | -| 6038 | 0x1796 | `InvalidStateTreeHeight` | "InvalidStateTreeHeight" | -| 6039 | 0x1797 | `InvalidArgument` | "InvalidArgument" | -| 6040 | 0x1798 | `InvalidAccount` | "InvalidAccount" | -| 6041 | 0x1799 | `AddressMerkleTreeAccountDiscriminatorMismatch` | "AddressMerkleTreeAccountDiscriminatorMismatch" | -| 6042 | 0x179A | `StateMerkleTreeAccountDiscriminatorMismatch` | "StateMerkleTreeAccountDiscriminatorMismatch" | -| 6043 | 0x179B | `ProofVerificationFailed` | "Proof verification failed." [How to debug this error](/resources/error-cheatsheet/debug-0x179b-6043-proofverificationfailed) | -| 6044 | 0x179C | `InvalidAccountMode` | "Invalid account mode." | -| 6045 | 0x179D | `InvalidInstructionDataDiscriminator` | "InvalidInstructionDataDiscriminator" | -| 6046 | 0x179E | `NewAddressAssignedIndexOutOfBounds` | "NewAddressAssignedIndexOutOfBounds" | -| 6047 | 0x179F | `AddressIsNone` | "AddressIsNone" | -| 6048 | 0x17A0 | `AddressDoesNotMatch` | "AddressDoesNotMatch" | -| 6049 | 0x17A1 | `CpiContextAlreadySet` | "CpiContextAlreadySet" | -| 6050 | 0x17A2 | `InvalidTreeHeight` | "InvalidTreeHeight" | -| 6051 | 0x17A3 | `TooManyOutputAccounts` | "TooManyOutputAccounts" | -| 6052 | 0x17A4 | `BorrowingDataFailed` | "Borrowing data failed" | -| 6053 | 0x17A5 | `DuplicateAccountInInputsAndReadOnly` | "DuplicateAccountInInputsAndReadOnly" | - -## 7001 - 7009 / HasherError Variants - -> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/hasher/src/errors.rs) - -| Code | Hex | Error | Message | -| :--- | :----- | :-------------------------------------- | :--------------------------------------------------------------------------------------- | -| 7001 | 0x1B59 | `IntegerOverflow` | "Integer overflow, value too large" | -| 7003 | 0x1B5B | `PoseidonSyscall(PoseidonSyscallError)` | "Poseidon syscall error: {0}" | -| 7005 | 0x1B5D | `InvalidInputLength(usize, usize)` | "Allowed input length {0} provided {1}" | -| 7006 | 0x1B5E | `InvalidNumFields` | "Invalid number of fields" | -| 7007 | 0x1B5F | `EmptyInput` | "Empty input" | -| 7008 | 0x1B60 | `BorshError` | "Borsh serialization failed." | -| 7009 | 0x1B61 | `OptionHashToFieldSizeZero` | "Option hash to field size returned [0u8;32], a collision with None for an Option type." | - -## 10001 - 10014 / ConcurrentMerkleTreeError Variants - -> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/concurrent-merkle-tree/src/errors.rs) - -| Code | Hex | Error | Message | -| :---- | :----- | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------- | -| 10001 | 0x2711 | `IntegerOverflow` | "Integer overflow" | -| 10002 | 0x2712 | `HeightZero` | "Invalid height, it has to be greater than 0" | -| 10003 | 0x2713 | `InvalidHeight(usize)` | "Invalid height, expected {0}" | -| 10004 | 0x2714 | `ChangelogZero` | "Invalid changelog size, it has to be greater than 0. Changelog is used for storing Merkle paths during appends." | -| 10005 | 0x2715 | `RootsZero` | "Invalid number of roots, it has to be greater than 0" | -| 10006 | 0x2716 | `CanopyGeThanHeight` | "Canopy depth has to be lower than height" | -| 10007 | 0x2717 | `TreeIsFull` | "Merkle tree is full, cannot append more leaves." | -| 10008 | 0x2718 | `BatchGreaterThanChangelog(usize, usize)` | "Number of leaves ({0}) exceeds the changelog capacity ({1})." | -| 10009 | 0x2719 | `InvalidProofLength(usize, usize)` | "Invalid proof length, expected {0}, got {1}." | -| 10010 | 0x271A | `InvalidProof([u8; 32], [u8; 32])` | "Invalid Merkle proof, expected root: `{0:?}`, the provided proof produces root: `{1:?}`" | -| 10011 | 0x271B | `CannotUpdateLeaf` | "Attempting to update the leaf which was updated by an another newest change." | -| 10012 | 0x271C | `CannotUpdateEmpty` | "Cannot update the empty leaf" | -| 10013 | 0x271D | `EmptyLeaves` | "The batch of leaves is empty" | -| 10014 | 0x271E | `BufferSize(usize, usize)` | "Invalid buffer size, expected {0}, got {1}" | - -## 11001 - 11009 / IndexedMerkleTreeError Variants - -> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/indexed-merkle-tree/src/errors.rs) - -| Code | Hex | Error | Message | -| :---- | :----- | :-------------------------------------- | :------------------------------------------------------------------ | -| 11001 | 0x2AF9 | `IntegerOverflow` | "Integer overflow" | -| 11002 | 0x2AFA | `IndexHigherThanMax` | "Invalid index, it exceeds the number of elements." | -| 11003 | 0x2AFB | `LowElementNotFound` | "Could not find the low element." | -| 11004 | 0x2AFC | `LowElementGreaterOrEqualToNewElement` | "Low element is greater or equal to the provided new element." | -| 11005 | 0x2AFD | `NewElementGreaterOrEqualToNextElement` | "The provided new element is greater or equal to the next element." | -| 11006 | 0x2AFE | `ElementAlreadyExists` | "The element already exists, but was expected to be absent." | -| 11007 | 0x2AFF | `ElementDoesNotExist` | "The element does not exist, but was expected to be present." | -| 11008 | 0x2B00 | `ChangelogBufferSize(usize, usize)` | "Invalid changelog buffer size, expected {0}, got {1}" | -| 11009 | 0x2B01 | `ArrayFull` | "Indexed array is full, cannot append more elements" | - -## 12006 - 12019 / AccountError Variants - -> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/account-checks/src/error.rs) - -| Code | Hex | Error | Message | -| :---- | :----- | :--------------------------- | :-------------------------------- | -| 12006 | 0x2EE6 | `InvalidDiscriminator` | "Invalid Discriminator." | -| 12007 | 0x2EE7 | `AccountOwnedByWrongProgram` | "Account owned by wrong program." | -| 12008 | 0x2EE8 | `AccountNotMutable` | "Account not mutable." | -| 12009 | 0x2EE9 | `BorrowAccountDataFailed` | "Borrow account data failed." | -| 12010 | 0x2EEA | `InvalidAccountSize` | "Invalid Account size." | -| 12011 | 0x2EEB | `AccountMutable` | "Account is mutable." | -| 12012 | 0x2EEC | `AlreadyInitialized` | "Account is already initialized." | -| 12013 | 0x2EED | `InvalidAccountBalance` | "Invalid account balance." | -| 12014 | 0x2EEE | `FailedBorrowRentSysvar` | "Failed to borrow rent sysvar." | -| 12015 | 0x2EEF | `InvalidSigner` | "Invalid Signer" | -| 12016 | 0x2EF0 | `InvalidSeeds` | "Invalid Seeds" | -| 12017 | 0x2EF1 | `InvalidProgramId` | "Invalid Program Id" | -| 12018 | 0x2EF2 | `ProgramNotExecutable` | "Program not executable." | -| 12019 | 0x2EF3 | `AccountNotZeroed` | "Account not zeroed." | - -## 14001 - 14009 / MerkleTreeMetadataError Variants - -> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/merkle-tree-metadata/src/errors.rs) - -| Code | Hex | Error | Message | -| :---- | :----- | :-------------------------------- | :------------------------------------------ | -| 14001 | 0x36B1 | `MerkleTreeAndQueueNotAssociated` | "Merkle tree and queue are not associated." | -| 14002 | 0x36B2 | `RolloverNotConfigured` | "Rollover not configured." | -| 14003 | 0x36B3 | `MerkleTreeAlreadyRolledOver` | "Merkle tree already rolled over." | -| 14004 | 0x36B4 | `InvalidQueueType` | "Invalid queue type." | -| 14005 | 0x36B5 | `InsufficientRolloverFee` | "Insufficient rollover fee." | -| 14006 | 0x36B6 | `NotReadyForRollover` | "Merkle tree not ready for rollover." | -| 14007 | 0x36B7 | `InvalidTreeType` | "Invalid tree type." | -| 14008 | 0x36B8 | `InvalidRolloverThreshold` | "Invalid Rollover Threshold." | -| 14009 | 0x36B9 | `InvalidHeight` | "Invalid Height." | - -## 14017 - 14034 / LightSdkTypesError Variants - -> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/sdk-libs/sdk-types/src/error.rs#L4) - -| Code | Hex | Error | Message | -| :---- | :----- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | -| 14017 | 0x36C1 | `FewerAccountsThanSystemAccounts` | "Fewer accounts than system accounts" | -| 14021 | 0x36C5 | `InitAddressIsNone` | "Address is none during initialization" | -| 14022 | 0x36C6 | `InitWithAddressIsNone` | "Address is none during initialization with address" | -| 14023 | 0x36C7 | `InitWithAddressOutputIsNone` | "Output is none during initialization with address" | -| 14024 | 0x36C8 | `MetaMutAddressIsNone` | "Address is none during meta mutation" | -| 14025 | 0x36C9 | `MetaMutInputIsNone` | "Input is none during meta mutation" | -| 14026 | 0x36CA | `MetaMutOutputLamportsIsNone` | "Output lamports is none during meta mutation" | -| 14027 | 0x36CB | `MetaMutOutputIsNone` | "Output is none during meta mutation" | -| 14028 | 0x36CC | `MetaCloseAddressIsNone` | "Address is none during meta close" | -| 14029 | 0x36CD | `MetaCloseInputIsNone` | "Input is none during meta close" | -| 14031 | 0x36CF | `CpiAccountsIndexOutOfBounds(usize)` | "CPI accounts index out of bounds: {0}" | -| 14032 | 0x36D0 | `InvalidCpiContextAccount` | "Invalid CPI context account" | -| 14033 | 0x36D1 | `InvalidSolPoolPdaAccount` | "Invalid sol pool pda account" | -| 14034 | 0x36D2 | `InvalidCpiAccountsOffset` | "CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7." | - -## 14301 - 14312 / BatchedMerkleTreeError Variants - -> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/batched-merkle-tree/src/errors.rs) - -| Code | Hex | Error | Message | -| :---- | :----- | :------------------------------------ | :------------------------------------------------------ | -| 14301 | 0x37DD | `BatchNotReady` | "Batch is not ready to be inserted" | -| 14302 | 0x37DE | `BatchAlreadyInserted` | "Batch is already inserted" | -| 14303 | 0x37DF | `BatchInsertFailed` | "Batch insert failed" | -| 14304 | 0x37E0 | `LeafIndexNotInBatch` | "Leaf index not in batch." | -| 14305 | 0x37E1 | `InvalidNetworkFee` | "Invalid network fee." | -| 14306 | 0x37E2 | `BatchSizeNotDivisibleByZkpBatchSize` | "Batch size not divisible by ZKP batch size." | -| 14307 | 0x37E3 | `InclusionProofByIndexFailed` | "Inclusion proof by index failed." | -| 14308 | 0x37E4 | `InvalidBatchIndex` | "Invalid batch index" | -| 14309 | 0x37E5 | `InvalidIndex` | "Invalid index" | -| 14310 | 0x37E6 | `TreeIsFull` | "Batched Merkle tree is full." | -| 14311 | 0x37E7 | `NonInclusionCheckFailed` | "Value already exists in bloom filter." | -| 14312 | 0x37E8 | `BloomFilterNotZeroed` | "Bloom filter must be zeroed prior to reusing a batch." | - -## 15001 - 15017 / ZeroCopyError Variants - -> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/zero-copy/src/errors.rs) - -| Code | Hex | Error | Message | -| :---- | :----- | :------------------------------------------ | :----------------------------------------------------------- | -| 15001 | 0x3A99 | `Full` | "The vector is full, cannot push any new elements" | -| 15002 | 0x3A9A | `ArraySize(usize, usize)` | "Requested array of size {}, but the vector has {} elements" | -| 15003 | 0x3A9B | `IterFromOutOfBounds` | "The requested start index is out of bounds" | -| 15004 | 0x3A9C | `InsufficientMemoryAllocated(usize, usize)` | "Memory allocated {}, Memory required {}" | -| 15006 | 0x3A9E | `UnalignedPointer` | "Unaligned pointer" | -| 15007 | 0x3A9F | `MemoryNotZeroed` | "Memory not zeroed" | -| 15008 | 0x3AA0 | `InvalidConversion` | "Invalid conversion" | -| 15009 | 0x3AA1 | `InvalidData(Infallible)` | "Invalid data" | -| 15010 | 0x3AA2 | `Size` | "Invalid size" | -| 15011 | 0x3AA3 | `InvalidOptionByte(u8)` | "Invalid option byte {} must be 0 (None) or 1 (Some)" | -| 15012 | 0x3AA4 | `InvalidCapacity` | "Invalid capacity. Capacity must be greater than 0" | -| 15013 | 0x3AA5 | `LengthGreaterThanCapacity` | "Length is greater than capacity" | -| 15014 | 0x3AA6 | `CurrentIndexGreaterThanLength` | "Current index is greater than length" | -| 15015 | 0x3AA7 | `InvalidEnumValue` | "Invalid enum value" | -| 15016 | 0x3AA8 | `InsufficientCapacity` | "Insufficient capacity for operation" | -| 15017 | 0x3AA9 | `PlatformSizeOverflow` | "Value too large for platform usize" | - -## 16001 - 16034 / LightSdkError Variants - -> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/sdk/src/error.rs#L126) - -| Code | Hex | Error | Message | -| :---- | :----- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | -| 16001 | 0x3E81 | `ConstraintViolation` | "Constraint violation" | -| 16002 | 0x3E82 | `InvalidLightSystemProgram` | "Invalid light-system-program ID" | -| 16003 | 0x3E83 | `ExpectedAccounts` | "Expected accounts in the instruction" | -| 16004 | 0x3E84 | `ExpectedAddressTreeInfo` | "Expected address Merkle context to be provided" | -| 16005 | 0x3E85 | `ExpectedAddressRootIndex` | "Expected address root index to be provided" | -| 16006 | 0x3E86 | `ExpectedData` | "Accounts with a specified input are expected to have data" | -| 16007 | 0x3E87 | `ExpectedDiscriminator` | "Accounts with specified data are expected to have a discriminator" | -| 16008 | 0x3E88 | `ExpectedHash` | "Accounts with specified data are expected to have a hash" | -| 16009 | 0x3E89 | `ExpectedLightSystemAccount(String)` | "Expected the `{0}` light account to be provided" | -| 16010 | 0x3E8A | `ExpectedMerkleContext` | "`mut` and `close` accounts are expected to have a Merkle context" | -| 16011 | 0x3E8B | `ExpectedRootIndex` | "Expected root index to be provided" | -| 16012 | 0x3E8C | `TransferFromNoInput` | "Cannot transfer lamports from an account without input" | -| 16013 | 0x3E8D | `TransferFromNoLamports` | "Cannot transfer from an account without lamports" | -| 16014 | 0x3E8E | `TransferFromInsufficientLamports` | "Account, from which a transfer was attempted, has insufficient amount of lamports" | -| 16015 | 0x3E8F | `TransferIntegerOverflow` | "Integer overflow resulting from too large resulting amount" | -| 16016 | 0x3E90 | `Borsh` | "Borsh error." | -| 16017 | 0x3E91 | `FewerAccountsThanSystemAccounts` | "Fewer accounts than number of system accounts." | -| 16018 | 0x3E92 | `InvalidCpiSignerAccount` | "InvalidCpiSignerAccount" | -| 16019 | 0x3E93 | `MissingField(String)` | "Missing meta field: {0}" | -| 16020 | 0x3E94 | `OutputStateTreeIndexIsNone` | "Output state tree index is none. Use an CompressedAccountMeta type with output tree index to initialize or update accounts." | -| 16021 | 0x3E95 | `InitAddressIsNone` | "Address is none during initialization" | -| 16022 | 0x3E96 | `InitWithAddressIsNone` | "Address is none during initialization with address" | -| 16023 | 0x3E97 | `InitWithAddressOutputIsNone` | "Output is none during initialization with address" | -| 16024 | 0x3E98 | `MetaMutAddressIsNone` | "Address is none during meta mutation" | -| 16025 | 0x3E99 | `MetaMutInputIsNone` | "Input is none during meta mutation" | -| 16026 | 0x3E9A | `MetaMutOutputLamportsIsNone` | "Output lamports is none during meta mutation" | -| 16027 | 0x3E9B | `MetaMutOutputIsNone` | "Output is none during meta mutation" | -| 16028 | 0x3E9C | `MetaCloseAddressIsNone` | "Address is none during meta close" | -| 16029 | 0x3E9D | `MetaCloseInputIsNone` | "Input is none during meta close" | -| 16031 | 0x3E9F | `CpiAccountsIndexOutOfBounds(usize)` | "CPI accounts index out of bounds: {0}" | -| 16032 | 0x3EA0 | `InvalidCpiContextAccount` | "Invalid CPI context account" | -| 16033 | 0x3EA1 | `InvalidSolPoolPdaAccount` | "Invalid SolPool PDA account" | -| 16034 | 0x3EA2 | `InvalidCpiAccountsOffset` | "CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7." | diff --git a/ai-tools/skills/dev-compressed-pda/stash.md b/ai-tools/skills/dev-compressed-pda/stash.md deleted file mode 100644 index ae629640..00000000 --- a/ai-tools/skills/dev-compressed-pda/stash.md +++ /dev/null @@ -1,49 +0,0 @@ -## Compressed PDAs - -[Full reference](references/compressed-pda.md) - -Compressed accounts do not require rent-exemption, which makes them suitable for: -- user owned accounts -- not config accounts which are often read -- not pool accounts, since compressed accounts cannot be used concurrently - -### Difference to Light-Accounts (Light-PDA) -Light-PDA's are Solana accounts with sponsored rent-exemption. -There is no proof required for interactions with Light-PDA's which makes -them suitable for Defi Usecases. Compressed PDA's don't require rent-exemption, -but a proof for interactions. - -### Client-Program Interaction Flow -```text - ├─ Client - │ ├─ Get ValidityProof from RPC. - │ ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo. - │ ├─ pack CompressedAccountMeta. - │ ├─ Build Instruction from PackedAccounts and CompressedAccountMetas. - │ └─ Send transaction. - │ - └─ Custom Program - ├─ CpiAccounts parse accounts consistent with PackedAccounts. - ├─ LightAccount instantiates from CompressedAccountMeta. - │ - └─ Light System Program CPI - ├─ Verify ValidityProof. - ├─ Update State Merkle tree. - ├─ Update Address Merkle tree. - └─ Complete atomic state transition. -``` - -### Using Compressed Accounts in Programs - -1. [`Instruction`](https://docs.rs/light-sdk/latest/light_sdk/instruction/) - - `CompressedAccountMeta` - Compressed account metadata structs for instruction data. - - `PackedAccounts` - Abstraction to prepare accounts offchain for instructions with compressed accounts. - - `ValidityProof` - Proves that new addresses don't exist yet, and compressed account state exists. -2. Compressed Account in Program - - [`LightAccount`](https://docs.rs/light-sdk/latest/light_sdk/account/) - Compressed account abstraction similar to anchor Account. - - [`derive_address`](https://docs.rs/light-sdk/latest/light_sdk/address/) - Create a compressed account address. - - `LightDiscriminator` - DeriveMacro to derive a compressed account discriminator. -3. [`Cpi`](https://docs.rs/light-sdk/latest/light_sdk/cpi/) - - `CpiAccounts` - Prepare accounts to cpi the light system program. - - `LightSystemProgramCpi` - Prepare instruction data to cpi the light system program. - - [`InvokeLightSystemProgram::invoke`](https://docs.rs/light-sdk/latest/light_sdk/cpi/) - Invoke the light system program via cpi. \ No newline at end of file diff --git a/ai-tools/skills/payments-and-wallets/SKILL.md b/ai-tools/skills/payments-and-wallets/SKILL.md deleted file mode 100644 index 50f6da93..00000000 --- a/ai-tools/skills/payments-and-wallets/SKILL.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -name: payments-and-wallets -description: "Build payment flows and wallet integrations with light-token. Covers receive/send/balance/history, sign with privy and wallet adapters, and nullifier-based double-spend prevention." ---- - -# Payments and wallets - -Build payment flows and wallet integrations using light-token on Solana. The light-token API matches SPL-token and extends it to include the light token program. - -| Creation cost | SPL | light-token | -| :---------------- | :------------------ | :------------------- | -| **Token Account** | ~2,000,000 lamports | ~**11,000** lamports | - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [domain references](#domain-references) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Domain references - -| Task | Reference | -|------|-----------| -| Build payment flows (receive, send, balance, history, wrap/unwrap) | [payments.md](references/payments.md) | -| Build wallet UI (display tokens, transfer, wrap/unwrap) | [wallets.md](references/wallets.md) | -| Sign with Wallet Adapter or Mobile Wallet Adapter | [sign-with-adapter.md](references/sign-with-adapter.md) | -| Sign with Privy (embedded wallet provider) | [sign-with-privy.md](references/sign-with-privy.md) | -| Optional Prevent duplicate actions (double-spend prevention) | [nullifiers.md](references/nullifiers.md) | - -## Setup - -```bash -npm install @lightprotocol/compressed-token @lightprotocol/stateless.js @solana/web3.js @solana/spl-token -``` - -```typescript -import { createRpc } from "@lightprotocol/stateless.js"; -import { - getOrCreateAtaInterface, - getAtaInterface, - getAssociatedTokenAddressInterface, - transferInterface, - wrap, - unwrap, -} from "@lightprotocol/compressed-token/unified"; - -const rpc = createRpc(RPC_ENDPOINT); -``` - -## Resources - -- [Payments docs](https://zkcompression.com/light-token/toolkits/for-payments) -- [Wallets docs](https://zkcompression.com/light-token/toolkits/for-wallets) -- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) -- [Nullifier program](https://github.com/Lightprotocol/nullifier-program/) \ No newline at end of file diff --git a/ai-tools/skills/payments-and-wallets/references/nullifiers.md b/ai-tools/skills/payments-and-wallets/references/nullifiers.md deleted file mode 100644 index 0a84f86e..00000000 --- a/ai-tools/skills/payments-and-wallets/references/nullifiers.md +++ /dev/null @@ -1,239 +0,0 @@ -# Nullifier PDAs - -Prevent duplicate actions (e.g., double-spend) by creating a rent-free compressed PDA that fails if it already exists. -Prepend or append the nullifier instruction to your transaction. - -## Program information - -| | | -|---|---| -| **Program ID** | `NFLx5WGPrTHHvdRNsidcrNcLxRruMC92E4yv7zhZBoT` | -| **Networks** | Mainnet, Devnet | -| **Source code** | [github.com/Lightprotocol/nullifier-program](https://github.com/Lightprotocol/nullifier-program/) | - -## How it works - -1. Derives PDA from `["nullifier", id]` seeds (where `id` is a unique 32-byte identifier — nonce, UUID, hash of payment inputs, etc.) -2. Creates an empty rent-free compressed PDA at that address -3. If the address already exists, the entire transaction fails -4. Prepend or append this instruction to your transaction - -## Dependencies - -### TypeScript - -```bash -npm install @lightprotocol/nullifier-program @lightprotocol/stateless.js@beta -``` - -### Rust - -```toml -[dependencies] -light-nullifier-program = "0.1.2" -light-client = "0.19.0" -``` - -## Quick start - -### TypeScript - -```typescript -import { createNullifierIx } from "@lightprotocol/nullifier-program"; -import { createRpc } from "@lightprotocol/stateless.js"; -import { Transaction, SystemProgram, ComputeBudgetProgram } from "@solana/web3.js"; - -const rpc = createRpc(RPC_URL); - -// Create a unique 32-byte ID (e.g., hash of payment inputs) -const id = new Uint8Array(32); -crypto.getRandomValues(id); - -// Build nullifier instruction -const nullifierIx = await createNullifierIx(rpc, payer.publicKey, id); - -// Combine with your transaction -const tx = new Transaction().add( - nullifierIx, - SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: recipient, - lamports: 1_000_000, - }) -); -``` - -### Rust - -```rust -use light_nullifier_program::sdk::{create_nullifier_ix, PROGRAM_ID}; -use light_client::{LightClient, LightClientConfig}; -use solana_sdk::{system_instruction, transaction::Transaction}; - -let mut rpc = LightClient::new( - LightClientConfig::new("https://mainnet.helius-rpc.com/?api-key=...") -).await?; - -// Create a unique 32-byte ID -let id: [u8; 32] = /* hash of payment inputs or random */; - -// Build nullifier instruction -let nullifier_ix = create_nullifier_ix(&mut rpc, payer.pubkey(), id).await?; - -// Combine with your transaction -let transfer_ix = system_instruction::transfer(&payer.pubkey(), &recipient, 1_000_000); -let tx = Transaction::new_signed_with_payer( - &[nullifier_ix, transfer_ix], - Some(&payer.pubkey()), - &[&payer], - recent_blockhash, -); -``` - -## Manual proof fetching - -When you need more control, fetch the proof and build the instruction separately. - -### TypeScript - -```typescript -import { fetchProof, buildInstruction } from "@lightprotocol/nullifier-program"; - -const proofResult = await fetchProof(rpc, id); -const nullifierIx = buildInstruction(payer.publicKey, id, proofResult); - -const tx = new Transaction().add( - nullifierIx, - SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: recipient, - lamports: 1_000_000, - }) -); -``` - -### Rust - -```rust -use light_nullifier_program::sdk::{fetch_proof, build_instruction}; - -let proof_result = fetch_proof(&mut rpc, &id).await?; -let nullifier_ix = build_instruction(payer.pubkey(), id, proof_result); - -let tx = Transaction::new_signed_with_payer( - &[nullifier_ix, transfer_ix], - Some(&payer.pubkey()), - &[&payer], - recent_blockhash, -); -``` - -## Check if nullifier exists - -### TypeScript - -```typescript -import { deriveNullifierAddress } from "@lightprotocol/nullifier-program"; -import { bn } from "@lightprotocol/stateless.js"; - -const address = deriveNullifierAddress(id); -const account = await rpc.getCompressedAccount(bn(address.toBytes())); -const exists = account !== null; -``` - -### Rust - -```rust -use light_nullifier_program::sdk::derive_nullifier_address; - -let address = derive_nullifier_address(&id); -let account = rpc.get_compressed_account(None, Some(address)).await?; -let exists = account.value.is_some(); -``` - -## Complete examples - -### TypeScript - -```typescript -import "dotenv/config"; -import * as crypto from "crypto"; -import { Keypair, ComputeBudgetProgram, Transaction } from "@solana/web3.js"; -import { createRpc, confirmTx } from "@lightprotocol/stateless.js"; -import { createNullifierIx, deriveNullifierAddress } from "@lightprotocol/nullifier-program"; - -const rpc = createRpc(RPC_URL); - -async function main() { - // Generate random 32-byte identifier - const id = new Uint8Array(crypto.randomBytes(32)); - - // Build nullifier instruction - const ix = await createNullifierIx(rpc, payer.publicKey, id); - - // Send transaction - const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }); - const tx = new Transaction().add(computeIx, ix); - tx.recentBlockhash = (await rpc.getRecentBlockhash()).blockhash; - tx.feePayer = payer.publicKey; - tx.sign(payer); - - const sig = await rpc.sendTransaction(tx, [payer]); - await confirmTx(rpc, sig); - - const address = deriveNullifierAddress(id); - - // Wait for indexer to process - const slot = await rpc.getSlot(); - await rpc.confirmTransactionIndexed(slot); - - // Double-spend: same id fails - try { - await createNullifierIx(rpc, payer.publicKey, id); - console.error("ERROR: duplicate nullifier should have failed"); - } catch { - console.log("Double-spend correctly rejected"); - } -} -``` - -Source: `nullifier-program/examples/action-create-nullifier.ts` - -### Rust - -```rust -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; -use light_nullifier_program::sdk::{create_nullifier_ix, derive_nullifier_address}; -use solana_sdk::signer::Signer; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let mut rpc = get_client().await?; - let payer = rpc.get_payer().insecure_clone(); - - // must be identifier, eg, nonce/UUID/hash of payment info - let id: [u8; 32] = rand::random(); - let nullifier_ix = create_nullifier_ix(&mut rpc, payer.pubkey(), id).await?; - - let sig = rpc - .create_and_send_transaction(&[nullifier_ix], &payer.pubkey(), &[&payer]) - .await?; - println!("Tx: {}", sig); - - let _address = derive_nullifier_address(&id); - - // Double spend should fail - assert!(create_nullifier_ix(&mut rpc, payer.pubkey(), id) - .await - .is_err()); - - Ok(()) -} -``` - -Source: `nullifier-program/examples/rust/src/main.rs` - -## Source - -- [Nullifier program repository](https://github.com/Lightprotocol/nullifier-program/) -- [Nullifier PDA guide](https://zkcompression.com/compressed-pdas/guides/how-to-create-nullifier-pdas) \ No newline at end of file diff --git a/ai-tools/skills/payments-and-wallets/references/payments.md b/ai-tools/skills/payments-and-wallets/references/payments.md deleted file mode 100644 index af555776..00000000 --- a/ai-tools/skills/payments-and-wallets/references/payments.md +++ /dev/null @@ -1,305 +0,0 @@ -# Payments - -The light-token API matches SPL-token. Your users receive the same stablecoin, stored more efficiently. - -| Creation cost | SPL | light-token | -| :---------------- | :------------------ | :------------------- | -| **Token Account** | ~2,000,000 lamports | ~**11,000** lamports | - -## API comparison - -| Operation | SPL | light-token | -|-----------|-----|-------------| -| Get/Create ATA | `getOrCreateAssociatedTokenAccount()` | `getOrCreateAtaInterface()` | -| Derive ATA | `getAssociatedTokenAddress()` | `getAssociatedTokenAddressInterface()` | -| Transfer | `transferChecked()` | `transferInterface()` | -| Get balance | `getAccount()` | `getAtaInterface()` | -| Tx history | `getSignaturesForAddress()` | `rpc.getSignaturesForOwnerInterface()` | -| Wrap from SPL | N/A | `wrap()` | -| Unwrap to SPL | N/A | `unwrap()` | - -Full code examples: [payments-and-wallets](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) - -## Setup - -```bash -npm install @lightprotocol/compressed-token @lightprotocol/stateless.js @solana/web3.js @solana/spl-token -``` - -```typescript -import { createRpc } from "@lightprotocol/stateless.js"; - -import { - getOrCreateAtaInterface, - getAtaInterface, - getAssociatedTokenAddressInterface, - transferInterface, - wrap, - unwrap, -} from "@lightprotocol/compressed-token/unified"; - -const rpc = createRpc(RPC_ENDPOINT); -``` - -## Receive payments - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { - createAssociatedTokenAccountInterfaceIdempotentInstruction, - createLoadAtaInstructions, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; -import { LIGHT_TOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js"; - -const ata = getAssociatedTokenAddressInterface(mint, recipient); - -const tx = new Transaction().add( - createAssociatedTokenAccountInterfaceIdempotentInstruction( - payer.publicKey, - ata, - recipient, - mint, - LIGHT_TOKEN_PROGRAM_ID - ), - ...(await createLoadAtaInstructions( - rpc, - ata, - recipient, - mint, - payer.publicKey - )) -); -``` - -### Action helper - -```typescript -import { getOrCreateAtaInterface } from "@lightprotocol/compressed-token/unified"; - -const ata = await getOrCreateAtaInterface(rpc, payer, mint, recipient); -// Share ata.parsed.address with sender - -console.log(ata.parsed.amount); -``` - -## Send payments - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { - createLoadAtaInstructions, - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); -const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); - -const tx = new Transaction().add( - ...(await createLoadAtaInstructions( - rpc, - sourceAta, - owner.publicKey, - mint, - payer.publicKey - )), - createTransferInterfaceInstruction( - sourceAta, - destinationAta, - owner.publicKey, - amount - ) -); -``` - -With idempotent ATA creation for recipient: - -```typescript -import { Transaction } from "@solana/web3.js"; -import { - createAssociatedTokenAccountInterfaceIdempotentInstruction, - createLoadAtaInstructions, - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; -import { LIGHT_TOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js"; - -const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); -const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); - -const tx = new Transaction().add( - createAssociatedTokenAccountInterfaceIdempotentInstruction( - payer.publicKey, - destinationAta, - recipient, - mint, - LIGHT_TOKEN_PROGRAM_ID - ), - ...(await createLoadAtaInstructions( - rpc, - sourceAta, - owner.publicKey, - mint, - payer.publicKey - )), - createTransferInterfaceInstruction( - sourceAta, - destinationAta, - owner.publicKey, - amount - ) -); -``` - -### Action helper - -```typescript -import { - getAssociatedTokenAddressInterface, - transferInterface, -} from "@lightprotocol/compressed-token/unified"; - -const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); -const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); - -await transferInterface( - rpc, - payer, - sourceAta, - mint, - destinationAta, - owner, - amount -); -``` - -## Show balance - -```typescript -import { - getAssociatedTokenAddressInterface, - getAtaInterface, -} from "@lightprotocol/compressed-token/unified"; - -const ata = getAssociatedTokenAddressInterface(mint, owner); -const account = await getAtaInterface(rpc, ata, owner, mint); - -console.log(account.parsed.amount); -``` - -## Transaction history - -```typescript -const result = await rpc.getSignaturesForOwnerInterface(owner); - -console.log(result.signatures); // All signatures -``` - -Use `getSignaturesForAddressInterface(address)` for address-specific rather than owner-wide history. - -## Wrap from SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - createWrapInstruction, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; -import { getSplInterfaceInfos } from "@lightprotocol/compressed-token"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); -const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized); - -const tx = new Transaction().add( - createWrapInstruction( - splAta, - tokenAta, - owner.publicKey, - mint, - amount, - splInterfaceInfo, - decimals - ) -); -``` - -### Action helper - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - wrap, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -await wrap(rpc, payer, splAta, tokenAta, owner, mint, amount); -``` - -## Unwrap to SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - createLoadAtaInstructions, - createUnwrapInstruction, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; -import { getSplInterfaceInfos } from "@lightprotocol/compressed-token"; - -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); -const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized); - -const tx = new Transaction().add( - ...(await createLoadAtaInstructions( - rpc, - tokenAta, - owner.publicKey, - mint, - payer.publicKey - )), - createUnwrapInstruction( - tokenAta, - splAta, - owner.publicKey, - mint, - amount, - splInterfaceInfo, - decimals - ) -); -``` - -### Action helper - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -await unwrap(rpc, payer, splAta, owner, mint, amount); -``` - -## Source - -- [Payments docs](https://zkcompression.com/light-token/toolkits/for-payments) -- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) \ No newline at end of file diff --git a/ai-tools/skills/payments-and-wallets/references/sign-with-adapter.md b/ai-tools/skills/payments-and-wallets/references/sign-with-adapter.md deleted file mode 100644 index 1fa6c2fa..00000000 --- a/ai-tools/skills/payments-and-wallets/references/sign-with-adapter.md +++ /dev/null @@ -1,182 +0,0 @@ -# Signing with wallet adapters - -All wallet adapters follow the same pattern: - -1. `createRpc()` for RPC connection -2. Build instructions with unified API (`getAssociatedTokenAddressInterface`, `createTransferInterfaceInstruction`, etc.) -3. Sign with adapter -4. Send transaction - -For embedded wallet providers (Privy), see [sign-with-privy.md](sign-with-privy.md). - -## Wallet Adapter (React) - -### Dependencies - -```bash -npm install @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js -``` - -### Sign pattern - -```typescript -import { useConnection, useWallet } from '@solana/wallet-adapter-react'; -import { createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey, Transaction } from '@solana/web3.js'; -import { - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, -} from '@lightprotocol/compressed-token'; - -const { connection } = useConnection(); -const { publicKey, sendTransaction } = useWallet(); -const rpc = createRpc(connection.rpcEndpoint); - -// Build instructions (same as any other adapter) -const sourceAta = getAssociatedTokenAddressInterface(mint, publicKey); -const destinationAta = getAssociatedTokenAddressInterface(mint, recipientPubkey); -const ix = createTransferInterfaceInstruction(sourceAta, destinationAta, publicKey, amount); - -// Build transaction -const { - context: { slot: minContextSlot }, - value: { blockhash, lastValidBlockHeight }, -} = await connection.getLatestBlockhashAndContext(); - -const transaction = new Transaction({ - feePayer: publicKey, - recentBlockhash: blockhash, -}).add(ix); - -// sendTransaction handles signing internally — no manual serialization -const signature = await sendTransaction(transaction, connection, { minContextSlot }); -await connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature }); -``` - -Source: `wallet-adapter/packages/starter/light-example/src/components/SendForm.tsx` - -## Mobile Wallet Adapter (React Native) - -### Dependencies - -```bash -npm install @solana-mobile/mobile-wallet-adapter-protocol-kit @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js -``` - -### Sign pattern - -```typescript -import { transact } from '@solana-mobile/mobile-wallet-adapter-protocol-kit'; -import { createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey, Transaction } from '@solana/web3.js'; -import { - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, - createLoadAtaInstructions, -} from '@lightprotocol/compressed-token'; - -const rpc = createRpc(RPC_URL); - -await transact(async wallet => { - const freshAccount = await authorizeSession(wallet); - const ownerPubkey = new PublicKey(freshAccount.address); - - // Build instructions (same as any other adapter) - const sourceAta = getAssociatedTokenAddressInterface(mint, ownerPubkey); - const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); - - // Load source ATA if compressed - const loadIxs = await createLoadAtaInstructions(rpc, sourceAta, ownerPubkey, mint, ownerPubkey); - const transferIx = createTransferInterfaceInstruction(sourceAta, destinationAta, ownerPubkey, amount); - - // Build transaction - const { blockhash, lastValidBlockHeight } = await rpc.getLatestBlockhash(); - const tx = new Transaction(); - if (loadIxs.length > 0) tx.add(...loadIxs); - tx.add(transferIx); - tx.recentBlockhash = blockhash; - tx.feePayer = ownerPubkey; - - // Serialize unsigned, sign with MWA - const unsigned = tx.serialize({ requireAllSignatures: false }); - const [signedBytes] = await wallet.signTransactions({ transactions: [unsigned] }); - - // Send - const signature = await rpc.sendRawTransaction(signedBytes, { - skipPreflight: false, - preflightCommitment: 'confirmed', - }); - await rpc.confirmTransaction({ blockhash, lastValidBlockHeight, signature }); -}); -``` - -Source: `mobile-wallet-adapter/examples/example-react-native-app/components/LightTokenTransferButton.tsx` - -## Mobile Wallet Adapter (Web) - -### Sign pattern - -```typescript -import { useWallet } from '@solana/wallet-adapter-react'; -import { createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey, Transaction } from '@solana/web3.js'; -import { - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, - createLoadAtaInstructions, -} from '@lightprotocol/compressed-token'; - -const { publicKey, signTransaction } = useWallet(); -const rpc = createRpc(RPC_URL); - -// Build instructions (same as any other adapter) -const sourceAta = getAssociatedTokenAddressInterface(mint, publicKey); -const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); - -// Load source ATA if compressed -const loadIxs = await createLoadAtaInstructions(rpc, sourceAta, publicKey, mint, publicKey); -const transferIx = createTransferInterfaceInstruction(sourceAta, destinationAta, publicKey, amount); - -// Build transaction -const { blockhash, lastValidBlockHeight } = await rpc.getLatestBlockhash(); -const tx = new Transaction(); -if (loadIxs.length > 0) tx.add(...loadIxs); -tx.add(transferIx); -tx.recentBlockhash = blockhash; -tx.feePayer = publicKey; - -// Sign Transaction object directly, then serialize -const signedTx = await signTransaction(tx); -const signedBytes = signedTx.serialize(); - -// Send -const signature = await rpc.sendRawTransaction(signedBytes, { - skipPreflight: false, - preflightCommitment: 'confirmed', -}); -await rpc.confirmTransaction({ blockhash, lastValidBlockHeight, signature }); -``` - -Source: `mobile-wallet-adapter/examples/example-web-app/components/LightTokenTransferButton.tsx` - -## Key differences - -| Adapter | Serialization | Sign method | Send method | -| ------- | ------------- | ----------- | ----------- | -| Wallet Adapter | none (handled internally) | `sendTransaction(tx, connection)` | built-in | -| MWA React Native | serialize unsigned → bytes | `wallet.signTransactions()` | `sendRawTransaction()` | -| MWA Web | none (Transaction object) | `signTransaction(tx)` | `sendRawTransaction()` | - -## Loading compressed accounts - -When the source ATA may be compressed, prepend load instructions: - -```typescript -const loadIxs = await createLoadAtaInstructions(rpc, sourceAta, owner, mint, payer); - -const tx = new Transaction(); -if (loadIxs.length > 0) tx.add(...loadIxs); -tx.add(transferIx); -``` - -Returns empty array if account is already hot (on-chain). \ No newline at end of file diff --git a/ai-tools/skills/payments-and-wallets/references/sign-with-privy.md b/ai-tools/skills/payments-and-wallets/references/sign-with-privy.md deleted file mode 100644 index 698f7f10..00000000 --- a/ai-tools/skills/payments-and-wallets/references/sign-with-privy.md +++ /dev/null @@ -1,145 +0,0 @@ -# Signing with Privy - -Privy is an embedded wallet provider — it creates and manages wallets on behalf of users. The light-token instruction-building code is the same as any other integration. The difference is the sign-and-send flow: - -1. Build unsigned transaction with light-token instructions -2. Serialize and send to Privy for signing -3. Sign with Privy SDK -4. Submit signed bytes via `sendRawTransaction()` - -## Node.js - -### Dependencies - -```bash -npm install @privy-io/node @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js -``` - -### Sign pattern - -```typescript -import {PrivyClient} from '@privy-io/node'; -import {createRpc} from '@lightprotocol/stateless.js'; -import {PublicKey, Transaction} from '@solana/web3.js'; -import { - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, -} from '@lightprotocol/compressed-token'; - -const connection = createRpc(process.env.HELIUS_RPC_URL!); -const privy = new PrivyClient({ - appId: process.env.PRIVY_APP_ID!, - appSecret: process.env.PRIVY_APP_SECRET!, -}); - -// Build instructions (same as any other integration) -const sourceAta = getAssociatedTokenAddressInterface(mint, fromPubkey); -const destAta = getAssociatedTokenAddressInterface(mint, toPubkey); -const instruction = createTransferInterfaceInstruction(sourceAta, destAta, fromPubkey, amount); - -// Build transaction -const transaction = new Transaction(); -transaction.add(instruction); -const {blockhash} = await connection.getLatestBlockhash(); -transaction.recentBlockhash = blockhash; -transaction.feePayer = fromPubkey; - -// Serialize unsigned, sign with Privy -const signResult = await privy.wallets().solana().signTransaction( - process.env.TREASURY_WALLET_ID!, - { - transaction: transaction.serialize({requireAllSignatures: false}), - authorization_context: { - authorization_private_keys: [process.env.TREASURY_AUTHORIZATION_KEY!] - } - } -); -const signedTransaction = Buffer.from((signResult as any).signed_transaction, 'base64'); - -// Send -const signature = await connection.sendRawTransaction(signedTransaction, { - skipPreflight: false, - preflightCommitment: 'confirmed' -}); -``` - -Source: `examples-light-token/privy/nodejs-privy-light-token/src/transfer.ts` - -## React - -### Dependencies - -```bash -npm install @privy-io/react-auth @lightprotocol/stateless.js @lightprotocol/compressed-token @solana/web3.js -``` - -### Sign pattern - -```typescript -import { useSignTransaction } from '@privy-io/react-auth/solana'; -import { createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey, Transaction } from '@solana/web3.js'; -import { - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, -} from '@lightprotocol/compressed-token'; - -const rpc = createRpc(import.meta.env.VITE_HELIUS_RPC_URL); - -// Build instructions (same as any other integration) -const sourceAta = getAssociatedTokenAddressInterface(mint, owner); -const destAta = getAssociatedTokenAddressInterface(mint, recipient); -const transferIx = createTransferInterfaceInstruction(sourceAta, destAta, owner, amount); - -// Build transaction -const { blockhash } = await rpc.getLatestBlockhash(); -const transaction = new Transaction(); -transaction.add(transferIx); -transaction.recentBlockhash = blockhash; -transaction.feePayer = owner; - -// Serialize unsigned, sign with Privy -const unsignedTxBuffer = transaction.serialize({ requireAllSignatures: false }); -const signedTx = await signTransaction({ - transaction: unsignedTxBuffer, - wallet, - chain: 'solana:devnet', -}); - -// Send -const signedTxBuffer = Buffer.from(signedTx.signedTransaction); -const signature = await rpc.sendRawTransaction(signedTxBuffer, { - skipPreflight: false, - preflightCommitment: 'confirmed', -}); -``` - -Source: `examples-light-token/privy/react-privy-light-token/src/hooks/useTransfer.ts` - -## Key differences from wallet adapters - -| | Privy | Wallet Adapter / MWA | -|---|---|---| -| **Wallet ownership** | Privy creates and holds wallets | User owns their wallet | -| **Signing** | Serialize unsigned → send to Privy API → get signed bytes | Adapter handles signing internally or via `signTransaction()` | -| **Send** | `sendRawTransaction()` with signed bytes | `sendTransaction()` (built-in) or `sendRawTransaction()` | -| **Auth** | App ID + secret (Node.js) or SDK hook (React) | Browser extension or mobile app | - -## Loading compressed accounts - -When the source ATA may be compressed, prepend load instructions: - -```typescript -const loadIxs = await createLoadAtaInstructions(rpc, sourceAta, owner, mint, payer); - -const tx = new Transaction(); -if (loadIxs.length > 0) tx.add(...loadIxs); -tx.add(transferIx); -``` - -Returns empty array if account is already hot (on-chain). - -## Source - -- [Privy Node.js example](https://github.com/Lightprotocol/examples-light-token/tree/main/privy/nodejs-privy-light-token) -- [Privy React example](https://github.com/Lightprotocol/examples-light-token/tree/main/privy/react-privy-light-token) diff --git a/ai-tools/skills/payments-and-wallets/references/wallets.md b/ai-tools/skills/payments-and-wallets/references/wallets.md deleted file mode 100644 index 29fe235b..00000000 --- a/ai-tools/skills/payments-and-wallets/references/wallets.md +++ /dev/null @@ -1,289 +0,0 @@ -# Wallets - -The light-token API matches SPL-token. Your users hold and receive tokens of the same mints, stored more efficiently. - -| Creation cost | SPL | light-token | -| :---------------- | :------------------ | :------------------- | -| **Token Account** | ~2,000,000 lamports | ~**11,000** lamports | - -## API comparison - -| Operation | SPL | light-token | -|-----------|-----|-------------| -| Get/Create ATA | `getOrCreateAssociatedTokenAccount()` | `getOrCreateAtaInterface()` | -| Derive ATA | `getAssociatedTokenAddress()` | `getAssociatedTokenAddressInterface()` | -| Transfer | `transferChecked()` | `transferInterface()` | -| Get balance | `getAccount()` | `getAtaInterface()` | -| Tx history | `getSignaturesForAddress()` | `rpc.getSignaturesForOwnerInterface()` | -| Wrap from SPL | N/A | `wrap()` | -| Unwrap to SPL | N/A | `unwrap()` | - -Full code examples: [payments-and-wallets](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) - -## Setup - -```bash -npm install @lightprotocol/compressed-token @lightprotocol/stateless.js @solana/web3.js @solana/spl-token -``` - -```typescript -import { createRpc } from "@lightprotocol/stateless.js"; - -import { - getOrCreateAtaInterface, - getAtaInterface, - getAssociatedTokenAddressInterface, - transferInterface, - wrap, - unwrap, -} from "@lightprotocol/compressed-token"; - -const rpc = createRpc(RPC_ENDPOINT); -``` - -## Receive tokens - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { - createAssociatedTokenAccountInterfaceIdempotentInstruction, - createLoadAtaInstructions, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token"; -import { LIGHT_TOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js"; - -const ata = getAssociatedTokenAddressInterface(mint, recipient); - -const tx = new Transaction().add( - createAssociatedTokenAccountInterfaceIdempotentInstruction( - payer.publicKey, - ata, - recipient, - mint, - LIGHT_TOKEN_PROGRAM_ID - ), - ...(await createLoadAtaInstructions( - rpc, - ata, - recipient, - mint, - payer.publicKey - )) -); -``` - -### Action helper - -```typescript -import { getOrCreateAtaInterface } from "@lightprotocol/compressed-token"; - -const ata = await getOrCreateAtaInterface(rpc, payer, mint, recipient); -// Share ata.parsed.address with sender - -console.log(ata.parsed.amount); -``` - -## Send tokens - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { - createLoadAtaInstructions, - createTransferInterfaceInstruction, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token"; - -const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); -const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); - -const tx = new Transaction().add( - ...(await createLoadAtaInstructions( - rpc, - sourceAta, - owner.publicKey, - mint, - payer.publicKey - )), - createTransferInterfaceInstruction( - sourceAta, - destinationAta, - owner.publicKey, - amount - ) -); -``` - -With idempotent ATA creation for recipient: - -```typescript -import { - getAssociatedTokenAddressInterface, - createAssociatedTokenAccountInterfaceIdempotentInstruction, -} from "@lightprotocol/compressed-token"; -import { LIGHT_TOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js"; - -const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); -const createAtaIx = createAssociatedTokenAccountInterfaceIdempotentInstruction( - payer.publicKey, - destinationAta, - recipient, - mint, - LIGHT_TOKEN_PROGRAM_ID -); - -new Transaction().add(createAtaIx, transferIx); -``` - -### Action helper - -```typescript -import { - getAssociatedTokenAddressInterface, - transferInterface, -} from "@lightprotocol/compressed-token"; - -const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); -const destinationAta = getAssociatedTokenAddressInterface(mint, recipient); - -await transferInterface( - rpc, - payer, - sourceAta, - mint, - destinationAta, - owner, - amount -); -``` - -## Show balance - -```typescript -import { - getAssociatedTokenAddressInterface, - getAtaInterface, -} from "@lightprotocol/compressed-token"; - -const ata = getAssociatedTokenAddressInterface(mint, owner); -const account = await getAtaInterface(rpc, ata, owner, mint); - -console.log(account.parsed.amount); -``` - -## Transaction history - -```typescript -const result = await rpc.getSignaturesForOwnerInterface(owner); - -console.log(result.signatures); -console.log(result.solana); -console.log(result.compressed); -``` - -Use `getSignaturesForAddressInterface(address)` for address-specific rather than owner-wide history. - -## Wrap from SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - createWrapInstruction, - getAssociatedTokenAddressInterface, - getSplInterfaceInfos, -} from "@lightprotocol/compressed-token"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); -const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized); - -const tx = new Transaction().add( - createWrapInstruction( - splAta, - tokenAta, - owner.publicKey, - mint, - amount, - splInterfaceInfo, - decimals - ) -); -``` - -### Action helper - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - wrap, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -await wrap(rpc, payer, splAta, tokenAta, owner, mint, amount); -``` - -## Unwrap to SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - createLoadAtaInstructions, - createUnwrapInstruction, - getAssociatedTokenAddressInterface, - getSplInterfaceInfos, -} from "@lightprotocol/compressed-token"; - -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); -const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized); - -const tx = new Transaction().add( - ...(await createLoadAtaInstructions( - rpc, - tokenAta, - owner.publicKey, - mint, - payer.publicKey - )), - createUnwrapInstruction( - tokenAta, - splAta, - owner.publicKey, - mint, - amount, - splInterfaceInfo, - decimals - ) -); -``` - -### Action helper - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -await unwrap(rpc, payer, splAta, owner, mint, amount); -``` - -## Source - -- [Wallets docs](https://zkcompression.com/light-token/toolkits/for-wallets) -- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) \ No newline at end of file diff --git a/ai-tools/skills/testing/SKILL.md b/ai-tools/skills/testing/SKILL.md deleted file mode 100644 index cd3506cc..00000000 --- a/ai-tools/skills/testing/SKILL.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -name: light-testing -description: "Test Light Protocol programs and clients for localnet, devnet and mainnet" -license: MIT -author: Light Protocol -version: 1.0.0 ---- - -# Light Protocol testing - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [test references](#routing) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Routing - -| Task | Reference | -|------|-----------| -| Start local validator | [references/local.md](references/local.md) | -| Test on devnet | [references/devnet.md](references/devnet.md) | - -## Program addresses - -These addresses are identical on devnet and mainnet. - -| Program | Address | -|---------|---------| -| Light System | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` | -| Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` | -| Account Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` | -| Light Registry | `Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX` | - -## Pre-initialized accounts - -See [references/accounts.md](references/accounts.md) for state trees, address trees, and protocol PDAs. - -See [references/addresses.md](references/addresses.md) for devnet-specific addresses and lookup tables. - -## Fetch for latest context - -Fetch these URLs for the latest API surface and setup instructions: - -- https://docs.rs/light-program-test -- https://www.npmjs.com/package/@lightprotocol/stateless.js -- https://github.com/Lightprotocol/light-protocol diff --git a/ai-tools/skills/testing/references/accounts.md b/ai-tools/skills/testing/references/accounts.md deleted file mode 100644 index 29daa210..00000000 --- a/ai-tools/skills/testing/references/accounts.md +++ /dev/null @@ -1,29 +0,0 @@ -# Pre-initialized accounts - -The test validator loads 39 pre-initialized accounts from the CLI's `accounts/` directory. By default only V2 trees are used. - -## Batched state trees (V2) - -| Set | BMT | OQ | CPI | -|-----|-----|-----|-----| -| 1 | `bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU` | `oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto` | `cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y` | -| 2 | `bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi` | `oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg` | `cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B` | -| 3 | `bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb` | `oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ` | `cpi3mbwMpSX8FAGMZVP85AwxqCaQMfEk9Em1v8QK9Rf` | -| 4 | `bmt4d3p1a4YQgk9PeZv5s4DBUmbF5NxqYpk9HGjQsd8` | `oq4ypwvVGzCUMoiKKHWh4S1SgZJ9vCvKpcz6RT6A8dq` | `cpi4yyPDc4bCgHAnsenunGA8Y77j3XEDyjgfyCKgcoc` | -| 5 | `bmt5yU97jC88YXTuSukYHa8Z5Bi2ZDUtmzfkDTA2mG2` | `oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P` | `cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6` | - -## Address trees - -| Type | Address | -|------|---------| -| Batch address tree (V2) | `amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx` | - -## Protocol PDAs - -| Type | Address | -|------|---------| -| Governance authority | `CuEtcKkkbTn6qy2qxqDswq5U2ADsqoipYDAYfRvxPjcp` | -| Config counter | `8gH9tmziWsS8Wc4fnoN5ax3jsSumNYoRDuSBvmH2GMH8` | -| Registered program PDA | `35hkDgaAKwMCaxRz2ocSZ6NaUrtKkyNqU6c4RV3tYJRh` | -| Registered registry program PDA | `DumMsyvkaGJG4QnQ1BhTgvoRMXsgGxfpKDUCr22Xqu4w` | -| Group PDA | `24rt4RgeyjUCWGS2eF7L7gyNMuz6JWdqYpAvb1KRoHxs` | diff --git a/ai-tools/skills/testing/references/addresses.md b/ai-tools/skills/testing/references/addresses.md deleted file mode 100644 index 3454ff5a..00000000 --- a/ai-tools/skills/testing/references/addresses.md +++ /dev/null @@ -1,39 +0,0 @@ -# Devnet addresses - -## Program addresses - -These addresses are identical on devnet and mainnet. - -| Program | Address | -|----------------------|------------------------------------------------| -| Light System Program | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` | -| Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` | -| Account Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` | -| Light Registry | `7Z9Yuy3HkBCc2Wf3xEN9BEkqxY3hn2NLqBJm4HKDtT5c` | - -## State tree lookup tables (devnet) - -Fetch active trees at runtime: - -```typescript -const { stateTrees } = await connection.getCachedActiveStateTreeInfo(); -``` - -| Lookup Table | Address | -|---------------------------------|------------------------------------------------| -| State Tree Lookup Table | `DmRueT3LMJdGj3TEprqKtfwMxyNUHDnKrQua4xrqtbmG` | -| Address Tree Lookup Table | `G4HqCAWPJ1E3JmYX1V2RZvNMuzF6gcFdbwT8FccWX6ru` | - -## Batch address tree - -```typescript -const BATCH_ADDRESS_TREE_ADDRESS = new PublicKey( - "amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2" -); -``` - -## Usage notes - -- Always fetch state trees dynamically using `getCachedActiveStateTreeInfo()` -- Do not hardcode tree addresses; they rotate as trees fill up -- Lookup table addresses are stable and can be referenced directly diff --git a/ai-tools/skills/testing/references/devnet.md b/ai-tools/skills/testing/references/devnet.md deleted file mode 100644 index 97888ece..00000000 --- a/ai-tools/skills/testing/references/devnet.md +++ /dev/null @@ -1,47 +0,0 @@ -# Devnet testing - -## Quick start - -```typescript -import { createRpc } from "@lightprotocol/stateless.js"; - -const connection = createRpc( - "https://devnet.helius-rpc.com?api-key=", - "https://devnet.helius-rpc.com?api-key=", - "https://devnet.helius-rpc.com?api-key=" -); -``` - -## Endpoints - -| Service | URL | -|-----------|--------------------------------------------------| -| RPC | `https://devnet.helius-rpc.com?api-key=` | -| WebSocket | `wss://devnet.helius-rpc.com?api-key=` | -| Indexer | `https://devnet.helius-rpc.com?api-key=` | -| Prover | `https://prover.helius.dev` | - -## Client setup - -```typescript -import { Rpc, createRpc } from "@lightprotocol/stateless.js"; - -const HELIUS_API_KEY = process.env.HELIUS_API_KEY; - -const RPC_ENDPOINT = `https://devnet.helius-rpc.com?api-key=${HELIUS_API_KEY}`; -const COMPRESSION_ENDPOINT = RPC_ENDPOINT; -const PROVER_ENDPOINT = "https://prover.helius.dev"; - -const connection: Rpc = createRpc(RPC_ENDPOINT, COMPRESSION_ENDPOINT, PROVER_ENDPOINT); - -// Fetch state trees at runtime -const { stateTrees } = await connection.getCachedActiveStateTreeInfo(); -const outputStateTree = stateTrees[0].tree; -``` - -## Key considerations - -- **Helius or Triton required**: The photon indexer implementation is maintained by Helius. You can also use Triton. Currently these RPC's provide compression endpoints. -- **Runtime tree fetch**: Always fetch active state trees at runtime via `getCachedActiveStateTreeInfo()` -- **Same programs**: Program addresses are identical on devnet and mainnet -- **Devnet-specific trees**: State tree lookup tables differ from mainnet \ No newline at end of file diff --git a/ai-tools/skills/testing/references/local.md b/ai-tools/skills/testing/references/local.md deleted file mode 100644 index c866a756..00000000 --- a/ai-tools/skills/testing/references/local.md +++ /dev/null @@ -1,86 +0,0 @@ -# Local testing with light-test-validator - -Local development environment running Solana test validator with Light Protocol programs, Photon indexer, and ZK prover. - -## Quick start - -```bash -# Start all services -light test-validator - -# Stop -light test-validator --stop -``` - -## Services & ports - -| Service | Port | Endpoint | -|---------|------|----------| -| Solana RPC | 8899 | `http://127.0.0.1:8899` | -| Solana WebSocket | 8900 | `ws://127.0.0.1:8900` | -| Photon Indexer | 8784 | `http://127.0.0.1:8784` | -| Light Prover | 3001 | `http://127.0.0.1:3001` | - -## Command flags - -| Flag | Default | Description | -|------|---------|-------------| -| `--skip-indexer` | false | Run without Photon indexer | -| `--skip-prover` | false | Run without Light Prover | -| `--skip-system-accounts` | false | Skip pre-initialized accounts | -| `--devnet` | false | Clone programs from devnet | -| `--mainnet` | false | Clone programs from mainnet | -| `--sbf-program ` | - | Load additional program | -| `--skip-reset` | false | Keep existing ledger | -| `--verbose` | false | Enable verbose logging | - -## Deployed programs - -| Program | Address | -|---------|---------| -| SPL Noop | `noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV` | -| Light System | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` | -| Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` | -| Account Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` | -| Light Registry | `Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX` | - -## TypeScript test integration - -```typescript -import { getTestRpc, newAccountWithLamports } from '@lightprotocol/stateless.js/test-helpers'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; - -const lightWasm = await WasmFactory.getInstance(); -const rpc = await getTestRpc(lightWasm); -const payer = await newAccountWithLamports(rpc, 1e9, 256); -``` - -**Run tests:** -```bash -cd js/stateless.js -pnpm test-validator && pnpm test:e2e:all -``` - -## Troubleshooting - -**Validator fails to start:** -```bash -lsof -i :8899 # Check port -light test-validator --stop # Stop existing -rm -rf test-ledger/ # Reset ledger -``` - -**Photon version mismatch:** -```bash -cargo install --git https://github.com/lightprotocol/photon.git \ - --rev ac7df6c388db847b7693a7a1cb766a7c9d7809b5 --locked --force -``` - -## File locations - -| Component | Location | -|-----------|----------| -| Program binaries | `~/.config/light/bin/` | -| Prover binary | `~/.config/light/bin/prover-{platform}-{arch}` | -| Proving keys | `~/.config/light/proving-keys/` | -| Test ledger | `./test-ledger/` | \ No newline at end of file diff --git a/ai-tools/skills/zk-nullifier/SKILL.md b/ai-tools/skills/zk-nullifier/SKILL.md deleted file mode 100644 index b1535d05..00000000 --- a/ai-tools/skills/zk-nullifier/SKILL.md +++ /dev/null @@ -1,290 +0,0 @@ ---- -name: zk-nullifier -description: "Use for privacy preserving applications and ZK Solana programs. Prevents double spending using rent-free PDAs." ---- -# ZK Nullifiers - -## Overview - -Building a ZK Solana program requires: -- Nullifiers to prevent double spending -- Proof verification -- A Merkle tree to store state -- An indexer to serve Merkle proofs -- Encrypted state - -For non zk applications see this skill to use nullifiers: skills/payments - -## Workflow - -1. **Clarify intent** - - Recommend plan mode, if it's not activated - - Use `AskUserQuestion` to resolve blind spots - - All questions must be resolved before execution -2. **Identify references and skills** - - Match task to [resources](#resources) below - - Locate relevant documentation and examples -3. **Write plan file** (YAML task format) - - Use `AskUserQuestion` for anything unclear — never guess or assume - - Identify blockers: permissions, dependencies, unknowns - - Plan must be complete before execution begins -4. **Execute** - - Use `Task` tool with subagents for parallel research - - Subagents load skills via `Skill` tool - - Track progress with `TodoWrite` -5. **When stuck**: spawn subagent with `Read`, `Glob`, `Grep`, DeepWiki MCP access and load `skills/ask-mcp` - -## Nullifiers on Solana - -A nullifier is a deterministically derived hash to ensure an action can only be performed once. The nullifier cannot be linked to the action or user. For example Zcash uses nullifiers to prevent double spending. - -To implement nullifiers we need a data structure that ensures every nullifier is only created once and never deleted. On Solana a straight forward way to implement nullifiers is to create a PDA account with the nullifier as seed. - -PDA accounts cannot be closed and permanently lock 890,880 lamports (per nullifier rent-exemption). -Compressed PDAs are derived similar to Solana PDAs and cost 15,000 lamports to create (no rent-exemption). - -| Storage | Cost per nullifier | -|---------|-------------------| -| PDA | 890,880 lamports | -| Compressed PDA | 15,000 lamports | - - -## Testing - -```bash -# Rust tests -cargo test-sbf -p nullifier - -# TypeScript tests (requires light test-validator) -light test-validator # separate terminal -npm run test:ts -``` - -## Pattern Overview - -``` -1. Client computes nullifier = hash(secret, context) -2. Client fetches validity proof for derived address (proves it does not exist) -3. Client calls create_nullifier with nullifier values and proof -4. Program derives address from nullifier, creates compressed account via CPI -5. Light system program rejects CPI if address already exists -``` - -## Resources - -- Full example: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier) -- ZK overview: [zkcompression.com/zk/overview](https://www.zkcompression.com/zk/overview) -- Additional ZK examples: [program-examples/zk](https://github.com/Lightprotocol/program-examples/tree/main/zk) (nullifier, zk-id, mixer, shielded-pool) - -## Reference Implementation - -Source: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier) - -### Account Structure - -```rust -#[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize, LightDiscriminator)] -pub struct NullifierAccount {} -``` - -Empty struct since existence alone proves the nullifier was used. - -### Address Derivation - -```rust -pub const NULLIFIER_PREFIX: &[u8] = b"nullifier"; - -let (address, address_seed) = derive_address( - &[NULLIFIER_PREFIX, nullifier.as_slice()], // seeds - &address_tree_pubkey, // address tree - &program_id, // program ID -); -``` - -Address is deterministically derived from: -- Constant prefix (prevents collisions with other account types) -- Nullifier value (32 bytes) -- Address tree pubkey -- Program ID - -### Instruction Data - -```rust -#[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] -pub struct NullifierInstructionData { - pub proof: ValidityProof, // ZK proof that addresses don't exist - pub address_tree_info: PackedAddressTreeInfo, - pub output_state_tree_index: u8, - pub system_accounts_offset: u8, -} -``` - -### Create Nullifiers Function - -```rust -pub fn create_nullifiers<'info>( - nullifiers: &[[u8; 32]], - data: NullifierInstructionData, - signer: &AccountInfo<'info>, - remaining_accounts: &[AccountInfo<'info>], -) -> Result<()> { - let light_cpi_accounts = CpiAccounts::new( - signer, - &remaining_accounts[data.system_accounts_offset as usize..], - LIGHT_CPI_SIGNER, - ); - - let address_tree_pubkey = data - .address_tree_info - .get_tree_pubkey(&light_cpi_accounts) - .map_err(|_| ErrorCode::AccountNotEnoughKeys)?; - - let mut cpi_builder = LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, data.proof); - let mut new_address_params: Vec = - Vec::with_capacity(nullifiers.len()); - - for (i, nullifier) in nullifiers.iter().enumerate() { - let (address, address_seed) = derive_address( - &[NULLIFIER_PREFIX, nullifier.as_slice()], - &address_tree_pubkey, - &crate::ID, - ); - - let nullifier_account = LightAccount::::new_init( - &crate::ID, - Some(address), - data.output_state_tree_index, - ); - - cpi_builder = cpi_builder.with_light_account(nullifier_account)?; - new_address_params.push( - data.address_tree_info - .into_new_address_params_assigned_packed(address_seed, Some(i as u8)), - ); - } - - cpi_builder - .with_new_addresses(&new_address_params) - .invoke(light_cpi_accounts)?; - - Ok(()) -} -``` - -### Program Entry Point - -```rust -#[program] -pub mod nullifier { - pub fn create_nullifier<'info>( - ctx: Context<'_, '_, '_, 'info, CreateNullifierAccounts<'info>>, - data: NullifierInstructionData, - nullifiers: Vec<[u8; 32]>, - ) -> Result<()> { - // Verify your ZK proof here. Use nullifiers as public inputs. - // Example: - // let public_inputs = [...nullifiers, ...your_other_inputs]; - // Groth16Verifier::new(...).verify()?; - - create_nullifiers( - &nullifiers, - data, - ctx.accounts.signer.as_ref(), - ctx.remaining_accounts, - ) - } -} - -#[derive(Accounts)] -pub struct CreateNullifierAccounts<'info> { - #[account(mut)] - pub signer: Signer<'info>, -} -``` - -## Client Implementation (TypeScript) - -```typescript -const NULLIFIER_PREFIX = Buffer.from("nullifier"); -const addressTree = new web3.PublicKey(batchAddressTree); - -// Derive addresses for each nullifier -const addressesWithTree = nullifiers.map((nullifier) => { - const seed = deriveAddressSeedV2([NULLIFIER_PREFIX, nullifier]); - const address = deriveAddressV2(seed, addressTree, programId); - return { tree: addressTree, queue: addressTree, address: bn(address.toBytes()) }; -}); - -// Get validity proof (proves addresses don't exist) -const proofResult = await rpc.getValidityProofV0([], addressesWithTree); - -// Build remaining accounts -const remainingAccounts = new PackedAccounts(); -remainingAccounts.addSystemAccountsV2(SystemAccountMetaConfig.new(programId)); -const addressMerkleTreeIndex = remainingAccounts.insertOrGet(addressTree); -const outputStateTreeIndex = remainingAccounts.insertOrGet(outputStateTree); - -// Build instruction data -const data = { - proof: { 0: proofResult.compressedProof }, - addressTreeInfo: { - addressMerkleTreePubkeyIndex: addressMerkleTreeIndex, - addressQueuePubkeyIndex: addressMerkleTreeIndex, - rootIndex: proofResult.rootIndices[0], - }, - outputStateTreeIndex, - systemAccountsOffset: systemStart, -}; - -// Call program -const ix = await program.methods - .createNullifier(data, nullifiers.map((n) => Array.from(n))) - .accounts({ signer: signer.publicKey }) - .remainingAccounts(remainingAccounts) - .instruction(); -``` - -## Client Implementation (Rust) - -```rust -use light_sdk::address::v2::derive_address; - -let address_tree_info = rpc.get_address_tree_v2(); - -// Derive addresses -let address_with_trees: Vec = nullifiers - .iter() - .map(|n| { - let (address, _) = derive_address( - &[NULLIFIER_PREFIX, n.as_slice()], - &address_tree_info.tree, - &program_id, - ); - AddressWithTree { - address, - tree: address_tree_info.tree, - } - }) - .collect(); - -// Get validity proof (empty hashes = non-inclusion proof) -let rpc_result = rpc - .get_validity_proof(vec![], address_with_trees, None) - .await? - .value; - -// Build accounts -let mut remaining_accounts = PackedAccounts::default(); -let config = SystemAccountMetaConfig::new(program_id); -remaining_accounts.add_system_accounts_v2(config)?; - -let packed_address_tree_accounts = rpc_result - .pack_tree_infos(&mut remaining_accounts) - .address_trees; - -let output_state_tree_index = rpc - .get_random_state_tree_info()? - .pack_output_tree_index(&mut remaining_accounts)?; -``` - - diff --git a/api-reference/json-rpc-methods/methods.mdx b/api-reference/json-rpc-methods/methods.mdx deleted file mode 100644 index 67124992..00000000 --- a/api-reference/json-rpc-methods/methods.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: JSON RPC Methods -description: Reference to all JSON RPC endpoints for ZK Compression on Solana. -sidebarTitle: "Overview" ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[GetCompressedAccount](/api-reference/json-rpc-methods/getcompressedaccount)Retrieves information about a specific compressed account by its address or hash.
[GetCompressedAccountsByOwner](/api-reference/json-rpc-methods/getcompressedaccountsbyowner)Returns all compressed accounts owned by a specific address.
[GetCompressedBalance](/api-reference/json-rpc-methods/getcompressedbalance)Retrieves the balance of a compressed account.
[GetCompressedBalanceByOwner](/api-reference/json-rpc-methods/getcompressedbalancebyowner)Gets the total balance of all compressed accounts owned by an address.
[GetCompressedMintTokenHolders](/api-reference/json-rpc-methods/getcompressedminttokenholders)Lists all holders of a specific compressed token mint.
[GetCompressedTokenAccountBalance](/api-reference/json-rpc-methods/getcompressedtokenaccountbalance)Retrieves the token balance of a compressed token account.
[GetCompressedTokenAccountsByDelegate](/api-reference/json-rpc-methods/getcompressedtokenaccountbydelegate)Returns all compressed token accounts delegated to a specific address.
[GetCompressedTokenAccountsByOwner](/api-reference/json-rpc-methods/getcompressedtokenaccountsbyowner)Lists all compressed token accounts owned by a specific address.
[GetCompressedTokenBalancesByOwner](/api-reference/json-rpc-methods/getcompressedtokenbalancesbyowner)Retrieves all token balances for compressed accounts owned by an address.
[GetCompressionSignaturesForAccount](/api-reference/json-rpc-methods/getcompressionsignaturesforaccount)Returns signatures for transactions involving a compressed account.
[GetCompressionSignaturesForAddress](/api-reference/json-rpc-methods/getcompressionsignaturesforaddress)Retrieves signatures for transactions involving a specific address.
[GetCompressionSignaturesForOwner](/api-reference/json-rpc-methods/getcompressionsignaturesforowner)Returns signatures for transactions where an address is the owner.
[GetCompressionSignaturesForTokenOwner](/api-reference/json-rpc-methods/getcompressionsignaturesfortokenowner)Lists signatures for transactions involving tokens owned by an address.
[GetIndexerHealth](/api-reference/json-rpc-methods/getindexerhealth)Returns the health status of the compression indexer.
[GetIndexerSlot](/api-reference/json-rpc-methods/getindexerslot)Retrieves the current slot of the compression indexer.
[GetLatestCompressionSignatures](/api-reference/json-rpc-methods/getlatestcompressionsignatures)Returns the most recent transaction signatures related to compression.
[GetLatestNon-VotingSignatures](/api-reference/json-rpc-methods/getlatestnonvotingsignatures)Retrieves recent non-voting transaction signatures.
[GetMultipleCompressedAccounts](/api-reference/json-rpc-methods/getmultiplecompressedaccounts)Retrieves multiple compressed accounts in a single request.
[GetMultipleNewAddressProofs](/api-reference/json-rpc-methods/getmultiplenewaddressproofs)Returns proofs that the new addresses are not taken already and can be created.
[GetTransactionWithCompressionInfo](/api-reference/json-rpc-methods/gettransactionwithcompressioninfo)Returns transaction details with additional compression-related information.
[GetValidityProof](/api-reference/json-rpc-methods/getvalidityproof)Retrieves a validity proof for compressed data.
diff --git a/api-reference/json-rpc-methods/overview.mdx b/api-reference/json-rpc-methods/overview.mdx index 288dab49..9043baf4 100644 --- a/api-reference/json-rpc-methods/overview.mdx +++ b/api-reference/json-rpc-methods/overview.mdx @@ -1,27 +1,35 @@ --- title: JSON RPC Methods -description: Overview JSON RPC endpoints on Solana, best practices, and error codes. +description: Photon indexer methods for querying compressed state on Solana. sidebarTitle: "Overview" --- -The API exposed by the indexer closely mirrors existing RPC calls, with one-to-one mapping: + +The Light Token SDK calls these methods internally via interface functions +like `transferInterface` and `getAtaInterface`. You rarely need to call +them directly. -| Solana RPC | Photon RPC Calls | -| :---------------------- | :------------------------------- | -| getAccountInfo | getCompressedAccount | -| getBalance | getCompressedBalance | -| getTokenAccountsByOwner | getCompressedTokenAccountsByOwner | -| getProgramAccounts | getCompressedAccountsByOwner | +Call these methods directly if you are building a block explorer, +custom indexer, or working with +[legacy compressed tokens](/compressed-tokens/overview). + - +The Light Token SDK exposes interface methods that check both hot (on-chain) and cold (compressed) state: -## Create an RPC Connection +| Light SDK interface method | Solana RPC | Photon RPC | +| :-------------------------------- | :---------------------- | :-------------------------------- | +| `getAccountInfoInterface` | getAccountInfo | getCompressedAccount | +| `getBalanceInterface` | getBalance | getCompressedBalanceByOwner | +| `getAtaInterface` | getAccountInfo | getCompressedTokenAccountsByOwner | +| `getTokenAccountBalanceInterface` | getTokenAccountBalance | getCompressedTokenBalancesByOwner | +| `getSignaturesForAddressInterface`| getSignaturesForAddress | getCompressionSignaturesForAddress| +| `getSignaturesForOwnerInterface` | getSignaturesForAddress | getCompressionSignaturesForOwner | + +Each interface method executes both calls in parallel and merges the results. + + + +## Create an RPC connection @@ -29,7 +37,7 @@ The API exposed by the indexer closely mirrors existing RPC calls, with one-to-o // Helius exposes Solana and Photon RPC endpoints through a single URL import { createRpc, Rpc } from '@lightprotocol/stateless.js'; -const RPC_ENDPOINT = ''; +const RPC_ENDPOINT = 'https://mainnet.helius-rpc.com?api-key=YOUR_KEY'; const connection: Rpc = createRpc(RPC_ENDPOINT, RPC_ENDPOINT); ``` @@ -45,41 +53,138 @@ const connection: Rpc = createRpc(RPC_ENDPOINT, RPC_ENDPOINT); -## Best Practices - -| Best Practice | Description | -|:----------------------|:----------------------------------------------------------------------------------------------------------------| -| **Commitment Levels**| Use appropriate commitment levels: `processed` (fastest), `confirmed` (balanced), `finalized` (most reliable) | -| **Rate Limiting** | Implement retry logic and respect rate limits. Public endpoints: 100 req/s, Private: 1000+ req/s | -| **Batch Requests** | Use batch requests when possible to improve efficiency and reduce API calls | -| **Caching** | Cache frequently accessed data to reduce API calls and improve performance | +## Best practices -## Error Codes +| Best practice | Description | +|:-----------------------|:---------------------------------------------------------------------------------------------------------------| +| **Commitment levels** | Use appropriate commitment levels: `processed` (fastest), `confirmed` (balanced), `finalized` (most reliable). | +| **Rate limiting** | Implement retry logic and respect rate limits. Public endpoints: 100 req/s, Private: 1000+ req/s. | +| **Batch requests** | Use batch requests when possible to reduce API calls. | +| **Caching** | Cache frequently accessed data to reduce API calls. | - -
- For help with debugging use - - Ask DeepWiki - -
-
+## Error codes | Code | Message | Description | -| :------ | :-------------------- | :-------------------------------------------- | -| -32600 | Invalid Request | The JSON sent is not a valid Request object | -| -32601 | Method not found | The method does not exist / is not available | -| -32602 | Invalid params | Invalid method parameter(s) | -| -32603 | Internal error | Internal JSON-RPC error | -| -32000 | Account not found | The compressed account was not found | -| -32001 | Invalid account hash | The provided account hash is invalid | - -# Next Steps - - +| :----- | :------------------- | :------------------------------------------- | +| -32600 | Invalid Request | The JSON sent is not a valid Request object. | +| -32601 | Method not found | The method does not exist / is not available. | +| -32602 | Invalid params | Invalid method parameter(s). | +| -32603 | Internal error | Internal JSON-RPC error. | +| -32000 | Account not found | The compressed account was not found. | +| -32001 | Invalid account hash | The provided account hash is invalid. | + +## Methods + +These methods are called internally by the Light Token SDK. Call them +directly for custom indexing, block explorers, or debugging. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
[GetCompressedAccount](/api-reference/json-rpc-methods/getcompressedaccount)Returns a compressed account by address or hash.
[GetCompressedAccountsByOwner](/api-reference/json-rpc-methods/getcompressedaccountsbyowner)Returns all compressed accounts for an owner.
[GetCompressedBalanceByOwner](/api-reference/json-rpc-methods/getcompressedbalancebyowner)Returns the total compressed balance for an owner.
[GetCompressedTokenAccountsByOwner](/api-reference/json-rpc-methods/getcompressedtokenaccountsbyowner)Returns all compressed token accounts for an owner.
[GetCompressedTokenBalancesByOwner](/api-reference/json-rpc-methods/getcompressedtokenbalancesbyowner)Returns all token balances for compressed accounts owned by an address.
[GetValidityProof](/api-reference/json-rpc-methods/getvalidityproof)Returns a validity proof for compressed state transitions.
[GetCompressionSignaturesForAddress](/api-reference/json-rpc-methods/getcompressionsignaturesforaddress)Returns signatures for transactions involving an address.
[GetCompressionSignaturesForOwner](/api-reference/json-rpc-methods/getcompressionsignaturesforowner)Returns signatures for transactions where an address is the owner.
[GetTransactionWithCompressionInfo](/api-reference/json-rpc-methods/gettransactionwithcompressioninfo)Returns transaction details with compression context.
[GetIndexerHealth](/api-reference/json-rpc-methods/getindexerhealth)Returns the health status of the Photon indexer.
[GetIndexerSlot](/api-reference/json-rpc-methods/getindexerslot)Returns the current slot of the Photon indexer.
+ +## Legacy + +Methods for direct compressed-token workflows. Not called by the Light +Token SDK interface functions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
[GetCompressedBalance](/api-reference/json-rpc-methods/getcompressedbalance)Returns the balance of a single compressed account.
[GetCompressedMintTokenHolders](/api-reference/json-rpc-methods/getcompressedminttokenholders)Lists all holders of a compressed token mint.
[GetCompressedTokenAccountBalance](/api-reference/json-rpc-methods/getcompressedtokenaccountbalance)Returns the token balance of a single compressed token account.
[GetCompressedTokenAccountsByDelegate](/api-reference/json-rpc-methods/getcompressedtokenaccountbydelegate)Returns all compressed token accounts delegated to an address.
[GetCompressionSignaturesForAccount](/api-reference/json-rpc-methods/getcompressionsignaturesforaccount)Returns signatures for transactions involving a compressed account.
[GetCompressionSignaturesForTokenOwner](/api-reference/json-rpc-methods/getcompressionsignaturesfortokenowner)Returns signatures for token transactions by owner.
[GetLatestCompressionSignatures](/api-reference/json-rpc-methods/getlatestcompressionsignatures)Returns the most recent compression-related signatures.
[GetLatestNonVotingSignatures](/api-reference/json-rpc-methods/getlatestnonvotingsignatures)Returns recent non-voting transaction signatures.
[GetMultipleCompressedAccounts](/api-reference/json-rpc-methods/getmultiplecompressedaccounts)Returns multiple compressed accounts in a single request.
[GetMultipleNewAddressProofs](/api-reference/json-rpc-methods/getmultiplenewaddressproofs)Returns proofs that new addresses can be created.
\ No newline at end of file diff --git a/api-reference/sdk.mdx b/api-reference/sdk.mdx new file mode 100644 index 00000000..488d0888 --- /dev/null +++ b/api-reference/sdk.mdx @@ -0,0 +1,8 @@ +--- +title: SDK Reference +description: Client and program SDKs for Light Token and ZK Compression. +--- + +import SdkReference from "/snippets/overview-tables/sdk-reference.mdx"; + + \ No newline at end of file diff --git a/client-library/client-guide.mdx b/client-library/client-guide.mdx index deb82b16..4be83bc8 100644 --- a/client-library/client-guide.mdx +++ b/client-library/client-guide.mdx @@ -10,8 +10,6 @@ import SystemAccountsList from '/snippets/accounts-list/compressed-pdas-system-a import TypescriptClient from '/snippets/code-snippets/create/typescript-client.mdx'; import RustClient from '/snippets/code-snippets/create/rust-client.mdx'; -ZK Compression provides Rust and Typescript clients to interact with compressed accounts and tokens on Solana. - diff --git a/compressed-pdas/guides/how-to-create-compressed-accounts.mdx b/compressed-pdas/guides/how-to-create-compressed-accounts.mdx index 32533423..987f7dea 100644 --- a/compressed-pdas/guides/how-to-create-compressed-accounts.mdx +++ b/compressed-pdas/guides/how-to-create-compressed-accounts.mdx @@ -14,9 +14,7 @@ Compressed accounts and addresses are created via CPI to the Light System Progra * Compressed and regular Solana accounts share the same functionality and are fully composable. * A compressed account has two identifiers: the account hash and its address (optional). In comparison, regular Solana accounts are identified by their address. * The account hash is not persistent and changes with every write to the account. -* For Solana PDA like behavior your compressed account needs an address as persistent identifier.\ - Fungible state like [compressed token accounts](/compressed-tokens/guides/create-compressed-token-accounts) do not need addresses. - +* For Solana PDA like behavior your compressed account needs an address as persistent identifier. Find [full code examples at the end](/compressed-pdas/guides/how-to-create-compressed-accounts#full-code-example) for Anchor and native Rust. diff --git a/compressed-pdas/guides/how-to-create-nullifier-pdas.mdx b/compressed-pdas/guides/how-to-create-nullifier-pdas.mdx index 63f10de2..40b23916 100644 --- a/compressed-pdas/guides/how-to-create-nullifier-pdas.mdx +++ b/compressed-pdas/guides/how-to-create-nullifier-pdas.mdx @@ -1,5 +1,6 @@ --- title: Create Nullifier PDAs +sidebarTitle: Nullifiers description: Create rent-free nullifier PDAs to prevent duplicate actions. keywords: ["nullifiers on solana", "prevent double spending", "compressed pdas", "rent-free nullifiers"] --- diff --git a/compressed-pdas/overview.mdx b/compressed-pdas/overview.mdx index 75c410a7..fcf414e7 100644 --- a/compressed-pdas/overview.mdx +++ b/compressed-pdas/overview.mdx @@ -1,36 +1,93 @@ --- title: Overview & Program Template -description: Compressed PDAs provide full functionality of accounts at PDAs, without rent-exemption per account. -keywords: ["rent free program derived addresses", "rent free pda accounts", "compressed pda on solana", "infrastructure for depins on solana", "scalable solana infrastructure", "stake accounts for depins", "cost efficient stake accounts", "scalable reward distribution on solana"] +description: To store user and app state, or other infrequently accessed state. Compressed PDAs provide full composability and functionality of accounts at PDAs, without rent-exemption per account. +keywords: ["user state", "app state", "node acounts", "depin accounts", "stake accounts", "not shared state or config or pool accounts", "rent free program derived addresses", "rent free pda accounts", "compressed pda on solana", "infrastructure for depins on solana", "scalable solana infrastructure", "stake accounts for depins", "cost efficient stake accounts", "scalable reward distribution on solana"] --- import ProgramExamplesTable from '/snippets/overview-tables/program-examples-table.mdx'; import DevelopmentEnvironmentSetup from '/snippets/setup/development-environment-setup.mdx'; +import CompressedPdasGuidesTable from '/snippets/overview-tables/compressed-pdas-guides-table.mdx'; +import SdkReferenceCompressedPdas from '/snippets/overview-tables/sdk-reference-compressed-pdas.mdx'; | Creation | Regular PDA Account | Compressed PDA | | :------------- | :--------------------- | :---------------------- | | 100-byte PDA | ~1,600,000 lamports | 15,000 lamports | -Compressed PDAs are derived using a specific program address and seed, like regular PDAs. Custom programs invoke the Light System program to create and update accounts, instead of the System program. +Compressed PDAs are derived using a specific program address and seed, like regular PDAs. +Custom programs invoke the Light System program +to create and update compressed accounts, instead of the System program. +Compressed PDAs are compressed accounts with an address. -#### Compressed PDAs at a Glance - - - - Create accounts at program-derived addresses without upfront rent exempt balance. - - - Persistent unique identification and program ownership. - - - CPI support between compressed and regular PDAs. - - - -# Start Building + +Use Compressed PDAs to store app and user state or other accounts that are infrequently accessed. +Do not use for shared state, pool accounts, or config accounts. + -Developing with compressed PDAs works similar to regular PDAs and involves minimal setup: + + +
+ + ![](/images/Untitled.png) + +
+
+ + ![](/images/image-22.png) + +
+
+ +
+ + ![](/images/Untitled-1.png) + +
+
+ + ![](/images/image-20.png) + +
+
+ +
+ + ![](/images/Untitled-4.png) + +
+
+ + ![](/images/image-23.png) + +
+
+ +
+ + ![](/images/program-reinit-1.png) + +
+
+ + ![](/images/program-reinit.png) + +
+
+ +
+ + ![](/images/program-burn-1.png) + +
+
+ + ![](/images/program-burn.png) + +
+
+
+ +# Start Building with the Program Template @@ -174,10 +231,17 @@ Caused by: +## Guides + + + ## Program Examples +## SDK Reference + + # Next Steps diff --git a/compressed-pdas/program-examples.mdx b/compressed-pdas/program-examples.mdx index 7e14d3d8..7bdf37fc 100644 --- a/compressed-pdas/program-examples.mdx +++ b/compressed-pdas/program-examples.mdx @@ -3,62 +3,63 @@ title: Program Examples description: Program example repository for compressed accounts with tests. --- -## Basic Operations +## Airdrop claim reference implementations -* [**basic-operations/anchor**](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/anchor) - Anchor program with Rust and TypeScript tests -* [**basic-operations/native**](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/native) - Native Solana program with `light-sdk` and Rust tests. +- **Basic**: [**simple-claim**](https://github.com/Lightprotocol/program-examples/tree/main/airdrop-implementations/simple-claim) - Distributes compressed tokens that get decompressed to SPL on claim with cliff. +- **Advanced**: [**merkle-distributor**](https://github.com/Lightprotocol/program-examples/tree/main/airdrop-implementations/distributor) - Distributes SPL tokens, uses compressed PDAs to track claims with linear vesting, partial claims and clawback. Based on Jito Merkle distributor and optimized with rent-free PDAs. -Basic Operations include: -- **create** - Initialize a new compressed account -- **update** - Modify data of an existing compressed account -- **close** - Close a compressed account (it can be initialized again). -- **reinit** - Reinitialize a closed account -- **burn** - Permanently delete a compressed account (it cannot be initialized again). +For simple client-side distribution visit [this example](https://github.com/Lightprotocol/example-token-distribution). -## Counter Program - -Full compressed account lifecycle (create, increment, decrement, reset, close): +## Basic operations -* [**counter/anchor**](https://github.com/Lightprotocol/program-examples/tree/main/counter/anchor) - Anchor program with Rust and TypeScript tests -* [**counter/native**](https://github.com/Lightprotocol/program-examples/tree/main/counter/native) - Native Solana program with `light-sdk` and Rust tests. -* [**counter/pinocchio**](https://github.com/Lightprotocol/program-examples/tree/main/counter/pinocchio) - Pinocchio program with `light-sdk-pinocchio` and Rust tests. +- **create** - Initialize a new compressed account + - [Anchor](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/anchor/create) | [Native](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/native/programs/create) +- **update** - Modify data in an existing compressed account + - [Anchor](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/anchor/update) | [Native](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/native/programs/update) +- **close** - Clear account data and preserve its address + - [Anchor](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/anchor/close) | [Native](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/native/programs/close) +- **reinit** - Reinitialize a closed account with the same address + - [Anchor](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/anchor/reinit) | [Native](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/native/programs/reinit) +- **burn** - Permanently delete a compressed account + - [Anchor](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/anchor/burn) | [Native](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/native/programs/burn) -## Create-and-update Program +## Nullifier program -* [**create-and-update**](https://github.com/Lightprotocol/program-examples/tree/main/create-and-update) - Create a new compressed account and update an existing compressed account with a single validity proof in one instruction. +[**nullifier-program**](https://github.com/Lightprotocol/nullifier-program) - For some use cases, such as sending payments, you might want to prevent your onchain instruction from being executed more than once. Creates a rent-free PDA derived from an id. If the id has been used before, the PDA already exists, causing the instruction to fail. -## Create-and-read Program +SDK: [`light-nullifier-program`](https://docs.rs/light-nullifier-program) | [Example client usage](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/create_nullifier.rs) -* [**read-only**](https://github.com/Lightprotocol/program-examples/tree/main/read-only) - Create a new compressed account and read it on-chain. +## Counter program -## Compare Programs with Solana and Compressed Accounts +Full compressed account lifecycle (create, increment, decrement, reset, close): -* [**account-comparison**](https://github.com/Lightprotocol/program-examples/tree/main/account-comparison) - Compare compressed vs regular Solana accounts. +- [**counter/anchor**](https://github.com/Lightprotocol/program-examples/tree/main/counter/anchor) - Anchor program with Rust and TypeScript tests +- [**counter/native**](https://github.com/Lightprotocol/program-examples/tree/main/counter/native) - Native Solana program with `light-sdk` and Rust tests +- [**counter/pinocchio**](https://github.com/Lightprotocol/program-examples/tree/main/counter/pinocchio) - Pinocchio program with `light-sdk-pinocchio` and Rust tests -## Nullifier Program +## Create-and-update program -* [**nullifier-program**](https://github.com/Lightprotocol/nullifier-program) - For some use cases, such as sending payments, you might want to prevent your onchain instruction from being executed more than once. Creates a rent-free PDA derived from an id. If the id has been used before, the PDA already exists, causing the instruction to fail. +[**create-and-update**](https://github.com/Lightprotocol/program-examples/tree/main/create-and-update) - Create a new compressed account and update an existing compressed account with a single validity proof in one instruction. -## Token Claim Reference Implementations +## Create-and-read program -* **Basic**: [**simple-claim**](https://github.com/Lightprotocol/program-examples-airdrop-implementations/simple-claim) - Distributes compressed tokens that get decompressed to SPL on claim. -* **Advanced**: [**merkle-distributor**](https://github.com/Lightprotocol/distributor) - Distributes SPL tokens, uses compressed PDAs to track claims with linear vesting, partial claims, clawback and admin controls. +[**read-only**](https://github.com/Lightprotocol/program-examples/tree/main/read-only) - Create a new compressed account and read it onchain. - -For simple client side distribution visit [this example](https://github.com/Lightprotocol/example-token-distribution). - +## Compare program with Solana vs compressed accounts -## ZK-ID Program +[**account-comparison**](https://github.com/Lightprotocol/program-examples/tree/main/account-comparison) - Compare compressed vs regular Solana accounts. -* [**zk-id**](https://github.com/Lightprotocol/program-examples/tree/main/zk-id) - A minimal Solana program that uses zero-knowledge proofs for identity verification with compressed accounts. +## ZK programs +- [**zk-id**](https://github.com/Lightprotocol/program-examples/tree/main/zk/zk-id) - Identity verification using Groth16 proofs. Issuers create credentials; users prove ownership without revealing the credential. +- [**nullifier**](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier) - Simple program to create nullifiers for ZK programs. -# Next Steps +## Next steps diff --git a/compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens.mdx b/compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens.mdx deleted file mode 100644 index c03840d7..00000000 --- a/compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens.mdx +++ /dev/null @@ -1,640 +0,0 @@ ---- -title: Add Wallet Support for Compressed Tokens -description: Guide to add Compressed Token Support to Your Wallet Application -keywords: ["wallet integration compressed tokens", "wallet infrastructure on solana"] ---- - -import InstallDependencies from '/snippets/setup/install-dependencies-codegroup.mdx'; -import SetupEnvironment from '/snippets/setup/setup-environment-tabs.mdx'; - - -Leading Solana Wallets like Phantom and Backpack already support compressed tokens. - - -# Integration Steps - - - - -### Display Compressed Token Balances - -```javascript -import { Rpc, createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey } from '@solana/web3.js'; - -const connection: Rpc = createRpc(); -const publicKey = new PublicKey('FWwR2s4TwpWN3nkCzVfhuPrpePG8kNzBXAxEbNsaDFNu'); - -(async () => { - const balances = await connection.getCompressedTokenBalancesByOwnerV2(publicKey); - - if (balances.value.items.length === 0) { - console.log("No compressed token balances found"); - return; - } - - for (const item of balances.value.items) { - const balanceValue = typeof item.balance === 'string' - ? parseInt(item.balance, 16) - : item.balance; - - const mintInfo = await connection.getAccountInfo(new PublicKey(item.mint)); - const decimals = mintInfo.data[44]; - const formattedBalance = balanceValue / Math.pow(10, decimals); - - console.log(`Mint: ${item.mint}`); - console.log(`Balance: ${formattedBalance} tokens\n`); - } -})(); -``` - -### Get Transaction History - -```typescript -import { Rpc, createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey } from '@solana/web3.js'; - -const connection: Rpc = createRpc(); -const publicKey = new PublicKey('FWwR2s4TwpWN3nkCzVfhuPrpePG8kNzBXAxEbNsaDFNu'); - -(async () => { - const signatures = await connection.getCompressionSignaturesForTokenOwner(publicKey); - - if (signatures.items.length > 0) { - console.log(`Signatures:`); - signatures.items.forEach((sig, index) => { - console.log(`${index + 1}. ${sig.signature}`); - console.log(` Slot: ${sig.slot}`); - console.log(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`); - }); - } else { - console.log("No transactions found"); - } -})(); -``` - - - -```typescript -import { Rpc, createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey } from '@solana/web3.js'; - -const RPC_ENDPOINT = process.env.RPC_ENDPOINT || 'https://devnet.helius-rpc.com?api-key='; - -const connection: Rpc = createRpc(RPC_ENDPOINT, RPC_ENDPOINT); -const publicKey = new PublicKey('FWwR2s4TwpWN3nkCzVfhuPrpePG8kNzBXAxEbNsaDFNu'); - -(async () => { - try { - const signatures = await connection.getCompressionSignaturesForTokenOwner(publicKey); - - if (signatures.items.length === 0) { - console.log("No transactions found"); - return; - } - - console.log(`Latest Transaction:`); - console.log(`Signature: ${signatures.items[0].signature}`); - console.log(`Slot: ${signatures.items[0].slot}`); - - const txInfo = await connection.getTransactionWithCompressionInfo(signatures.items[0].signature); - - if (!txInfo) { - console.log('Transaction not found or has no compression info'); - return; - } - - if (txInfo.compressionInfo) { - console.log(`\nClosed Accounts: ${txInfo.compressionInfo.closedAccounts.length}`); - console.log(`Opened Accounts: ${txInfo.compressionInfo.openedAccounts.length}`); - } - } catch (error) { - console.error('Error fetching transaction history:', error); - } -})(); -``` - - - -### Send Compressed Tokens - -```typescript expandable -import { - Rpc, - createRpc, - bn, - dedupeSigner, - sendAndConfirmTx, - buildAndSignTx, -} from "@lightprotocol/stateless.js"; -import { - CompressedTokenProgram, - selectMinCompressedTokenAccountsForTransfer, -} from "@lightprotocol/compressed-token"; -import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js"; - -const connection: Rpc = createRpc(); -const mint = new PublicKey("MINT_ADDRESS"); -const payer = PAYER_KEYPAIR; -const owner = payer; -const recipient = Keypair.generate(); -const amount = bn(1e8); - -(async () => { - const compressedTokenAccounts = - await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { mint }); - - if (compressedTokenAccounts.items.length === 0) { - console.log("No compressed token accounts found"); - return; - } - - const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer( - compressedTokenAccounts.items, - amount - ); - - const proof = await connection.getValidityProof( - inputAccounts.map((account) => account.compressedAccount.hash) - ); - - const ix = await CompressedTokenProgram.transfer({ - payer: payer.publicKey, - inputCompressedTokenAccounts: inputAccounts, - toAddress: recipient.publicKey, - amount, - recentInputStateRootIndices: proof.rootIndices, - recentValidityProof: proof.compressedProof, - }); - - const { blockhash } = await connection.getLatestBlockhash(); - const additionalSigners = dedupeSigner(payer, [owner]); - const signedTx = buildAndSignTx( - [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix], - payer, - blockhash, - additionalSigners - ); - - const transferTxId = await sendAndConfirmTx(connection, signedTx); - console.log(`Transaction: ${transferTxId}`); -})(); -``` - - - - - -### Prerequisites - -Make sure you have dependencies and developer environment set up! - - - -**Dependencies** - - - -**Developer Environment** - - - - - - -### Display Compressed Token Balances - -This example fetches and displays all compressed token balances for a wallet address. - -```javascript -import { Rpc, createRpc } from '@lightprotocol/stateless.js'; -import { Keypair, PublicKey } from '@solana/web3.js'; -import * as fs from 'fs'; -import * as os from 'os'; - -const connection: Rpc = createRpc(); - -const walletPath = `${os.homedir()}/.config/solana/id.json`; -const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); -const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); - -(async () => { - const balances = await connection.getCompressedTokenBalancesByOwnerV2(payer.publicKey); - - if (balances.value.items.length === 0) { - console.log("No compressed token balances found"); - return; - } - - for (const item of balances.value.items) { - const balanceValue = typeof item.balance === 'string' - ? parseInt(item.balance, 16) - : item.balance; - - const mintInfo = await connection.getAccountInfo(new PublicKey(item.mint)); - const decimals = mintInfo.data[44]; - const formattedBalance = balanceValue / Math.pow(10, decimals); - - console.log(`Mint: ${item.mint}`); - console.log(`Balance: ${formattedBalance} tokens\n`); - } -})(); -``` - - - -### Get Transaction History - -This example retrieves compression transaction signatures to display transaction history of the wallet. - -```javascript -import { Rpc, createRpc } from '@lightprotocol/stateless.js'; -import { Keypair } from '@solana/web3.js'; -import * as fs from 'fs'; -import * as os from 'os'; - -const connection: Rpc = createRpc(); - -const walletPath = `${os.homedir()}/.config/solana/id.json`; -const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); -const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); - -(async () => { - const signatures = await connection.getCompressionSignaturesForTokenOwner(payer.publicKey); - - if (signatures.items.length > 0) { - console.log(`Signatures:`); - signatures.items.forEach((sig, index) => { - console.log(`${index + 1}. ${sig.signature}`); - console.log(` Slot: ${sig.slot}`); - console.log(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`); - }); - } else { - console.log("No transactions found"); - } -})(); -``` - - - -### Send Compressed Tokens - -First, set up a test mint to and mint 10 compressed tokens to your filesystem wallet. - - - -```typescript -import { Keypair } from "@solana/web3.js"; -import { Rpc, confirmTx, createRpc } from '@lightprotocol/stateless.js'; -import { createMint, mintTo } from '@lightprotocol/compressed-token'; -import * as fs from 'fs'; -import * as os from 'os'; - -// 1. Setup RPC connection and load filesystem wallet for mint operations -// 2. Call createMint() to create SPL mint with token pool for compression -// 3. Call mintTo() to mint compressed tokens to filesystem wallet - -const connection: Rpc = createRpc(); // defaults to localhost:8899 - -// Load wallet from filesystem -const walletPath = `${os.homedir()}/.config/solana/id.json`; -const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); -const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); -const mintKeypair = Keypair.generate(); - -(async() => { - // Fund payer with SOL - await connection.requestAirdrop(payer.publicKey, 1e9); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Create SPL mint with token pool for compression - const { mint, transactionSignature } = await createMint( - connection, - payer, - payer.publicKey, // mint authority - 9, - mintKeypair, - ); - console.log(`Mint address: ${mint.toString()}`); - console.log(`Transaction: ${transactionSignature}`); - - // Mint compressed tokens to payer - const mintToTxId = await mintTo( - connection, - payer, - mint, // SPL mint with token pool for compression - payer.publicKey, // recipient address - payer, - 10e9, - ); - - console.log(`\nMinted ${10e9 / 1e9} compressed token`); - console.log(`Transaction: ${mintToTxId}`); -})(); -``` - - - - -Make sure you add your Mint address to `send-tokens.ts`. - - -```typescript send-tokens.ts expandable -// Compressed Token Transfer - Local -// 1. Load wallet and fetch compressed token accounts with getCompressedTokenAccountsByOwner() -// 2. Select accounts for transfer using selectMinCompressedTokenAccountsForTransfer() -// and get validity proof with getValidityProof() -// 3. Create transfer instruction with CompressedTokenProgram.transfer() -// and submit transaction with sendAndConfirmTx() -// 4. Verify balances via getCompressedTokenAccountsByOwner() - -import { - Rpc, - createRpc, - bn, - dedupeSigner, - sendAndConfirmTx, - buildAndSignTx, -} from "@lightprotocol/stateless.js"; -import { - CompressedTokenProgram, - selectMinCompressedTokenAccountsForTransfer, -} from "@lightprotocol/compressed-token"; -import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js"; -import * as fs from 'fs'; -import * as os from 'os'; - -// Step 1: Setup RPC connection and define transfer parameters -const connection: Rpc = createRpc(); // defaults to localhost:8899 -const mint = new PublicKey("MINT ADDRESS"); // Replace with mint address - -// Load wallet from filesystem -const walletPath = `${os.homedir()}/.config/solana/id.json`; -const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); -const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); -const owner = payer; - -const recipient = Keypair.generate(); -const amount = bn(1e8); - -(async () => { - // Step 2: Fetch compressed account hashes from state trees - const compressedTokenAccounts = - await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { - mint, // SPL mint with token pool for compression - }); - - if (compressedTokenAccounts.items.length === 0) { - console.log("No compressed token accounts found for this mint"); - return; - } - - // Show initial sender balance - const initialBalance = compressedTokenAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0); - console.log(`Sender balance: ${initialBalance / 1e8} compressed tokens`); - - // Step 3: Select minimum compressed accounts for transfer amount - const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer( - compressedTokenAccounts.items, - amount - ); - - // Get validity proof for Merkle tree verification - const proof = await connection.getValidityProof( - inputAccounts.map((account) => account.compressedAccount.hash) - ); - - // Step 4: Create transfer instruction that consumes input accounts and creates new output accounts - const ix = await CompressedTokenProgram.transfer({ - payer: payer.publicKey, - inputCompressedTokenAccounts: inputAccounts, // accounts to consume - toAddress: recipient.publicKey, - amount, - recentInputStateRootIndices: proof.rootIndices, - recentValidityProof: proof.compressedProof, - }); - - // Step 5: Build, sign, and submit transaction - const { blockhash } = await connection.getLatestBlockhash(); - const additionalSigners = dedupeSigner(payer, [owner]); - const signedTx = buildAndSignTx( - [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix], - payer, - blockhash, - additionalSigners - ); - const transferTxId = await sendAndConfirmTx(connection, signedTx); - - console.log(`\nTransferred ${amount.toNumber() / 1e8} compressed tokens`); - console.log(`Transaction: ${transferTxId}`); - console.log(`Recipient: ${recipient.publicKey.toString()}`); - - // Step 6: Verify via getCompressedTokenAccountsByOwner - const senderCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(payer.publicKey, { mint }); - const senderBalance = senderCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0); - - const recipientCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(recipient.publicKey, { mint }); - const recipientBalance = recipientCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0); - - console.log(`\nSummary compressed token balances:`); - console.log(`Sender balance: ${senderBalance / 1e8} compressed tokens`); - console.log(`Recipient balance: ${recipientBalance / 1e8} compressed token`); - - return transferTxId; -})(); -``` - - - - - -# Advanced Integrations - -Use these integrations to let users convert between regular and compressed format as needed. - - - -This example converts compressed tokens to regular SPL format using `CompressedTokenProgram.decompress().` - -```javascript -import { - bn, - buildAndSignTx, - sendAndConfirmTx, - dedupeSigner, - Rpc, - createRpc, -} from "@lightprotocol/stateless.js"; -import { ComputeBudgetProgram } from "@solana/web3.js"; -import { - CompressedTokenProgram, - getTokenPoolInfos, - selectMinCompressedTokenAccountsForTransfer, - selectTokenPoolInfosForDecompression, -} from "@lightprotocol/compressed-token"; - -// 1. Setup RPC connection and fetch compressed token accounts with getCompressedTokenAccountsByOwner() -// 2. Select accounts and token pool infos using selectMinCompressedTokenAccountsForTransfer() and selectTokenPoolInfosForDecompression() -// 3. Create decompress instruction with CompressedTokenProgram.decompress() and submit transaction - -// Step 1: Setup RPC connection and define decompression parameters -const connection: Rpc = createRpc("https://mainnet.helius-rpc.com?api-key=";); -const payer = PAYER_KEYPAIR; -const owner = PAYER_KEYPAIR; -const mint = MINT_ADDRESS; -const amount = 1e5; // 100K tokens to decompress - -(async () => { - // 1. Fetch compressed token accounts - const compressedTokenAccounts = - await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { - mint, - }); - - // 2. Select - const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer( - compressedTokenAccounts.items, - bn(amount) - ); - - // 3. Fetch validity proof - const proof = await connection.getValidityProof( - inputAccounts.map((account) => account.compressedAccount.hash) - ); - - // 4. Fetch & Select tokenPoolInfos - const tokenPoolInfos = await getTokenPoolInfos(connection, mint); - const selectedTokenPoolInfos = selectTokenPoolInfosForDecompression( - tokenPoolInfos, - amount - ); - - // 5. Build instruction - const ix = await CompressedTokenProgram.decompress({ - payer: payer.publicKey, - inputCompressedTokenAccounts: inputAccounts, - toAddress: owner.publicKey, - amount, - tokenPoolInfos: selectedTokenPoolInfos, - recentInputStateRootIndices: proof.rootIndices, - recentValidityProof: proof.compressedProof, - }); - - - // 6. Sign, send, and confirm. - // Example with keypair: - const { blockhash } = await connection.getLatestBlockhash(); - const additionalSigners = dedupeSigner(payer, [owner]); - const signedTx = buildAndSignTx( - [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix], - payer, - blockhash, - additionalSigners - ); - - return await sendAndConfirmTx(connection, signedTx); -})(); -``` - - - - - -This example converts regular SPL tokens to compressed format using `CompressedTokenProgram.compress().` - -```typescript -// 1. Setup RPC connection and get user ATA with getOrCreateAssociatedTokenAccount() -// 2. Fetch state tree and token pool infos using getStateTreeInfos() and getTokenPoolInfos() -// 3. Create compress instruction with CompressedTokenProgram.compress() and submit transaction - - -import { - buildAndSignTx, - sendAndConfirmTx, - Rpc, - createRpc, - selectStateTreeInfo, -} from "@lightprotocol/stateless.js"; -import { ComputeBudgetProgram } from "@solana/web3.js"; -import { - CompressedTokenProgram, - getTokenPoolInfos, - selectTokenPoolInfo, -} from "@lightprotocol/compressed-token"; -import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; - -// Step 1: Setup RPC connection and define compression parameters -const connection: Rpc = createRpc( - "https://mainnet.helius-rpc.com?api-key=" -); - const payer = ; - const mint = ; -const amount = 1e5; // 100K tokens to compress - -(async () => { - // Step 2: Get or create associated token account for SPL tokens - const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - payer, // fee payer - mint, // token mint address - payer.publicKey // token account owner - ); - - // Step 3: Fetch and select state tree info for compression - const treeInfos = await connection.getStateTreeInfos(); - const treeInfo = selectStateTreeInfo(treeInfos); - - // Step 4: Fetch and select token pool info for compression - const tokenPoolInfos = await getTokenPoolInfos(connection, mint); - const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos); - - // Step 5: Create compress instruction - transfer SPL tokens to pool and create compressed accounts - const compressInstruction = await CompressedTokenProgram.compress({ - payer: payer.publicKey, // fee payer - owner: payer.publicKey, // owner of source SPL tokens - source: sourceTokenAccount.address, // source ATA address - toAddress: payer.publicKey, // recipient of compressed tokens (self) - amount, // amount to compress - mint, // token mint address - outputStateTreeInfo: treeInfo, // state tree for compressed accounts - tokenPoolInfo, // token pool for compression - }); - - // Step 6: Build, sign, and submit compression transaction - const { blockhash } = await connection.getLatestBlockhash(); - const tx = buildAndSignTx( - [ - ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), - compressInstruction, - ], - payer, // transaction signer - blockhash, - [payer] // additional signers - ); - await sendAndConfirmTx(connection, tx); -})(); -``` - - - -# Common Errors - - - -If `getCompressedTokenBalancesByOwnerV2` returns empty: - -* Ensure the wallet has compressed tokens (not regular SPL tokens) -* Verify you're on the correct network (devnet/mainnet) - - - -# Next Steps - - - diff --git a/compressed-tokens/advanced-guides/example-token-distribution.mdx b/compressed-tokens/advanced-guides/example-token-distribution.mdx deleted file mode 100644 index b034f615..00000000 --- a/compressed-tokens/advanced-guides/example-token-distribution.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "Example Token Distribution" -description: "Reference implementations for airdrops, payments, and rewards with ZK Compression" -url: "https://github.com/Lightprotocol/example-token-distribution" -keywords: ["scalable token distribution on solana", "infrastructure for token distribution", "mass token distribution", "reward distribution for depins", "depin infrastructure on solana"] ---- - -View the full example on GitHub: [example-token-distribution](https://github.com/Lightprotocol/example-token-distribution) diff --git a/compressed-tokens/advanced-guides/how-to-combine-operations-in-one-transaction.mdx b/compressed-tokens/advanced-guides/how-to-combine-operations-in-one-transaction.mdx deleted file mode 100644 index 3201b90c..00000000 --- a/compressed-tokens/advanced-guides/how-to-combine-operations-in-one-transaction.mdx +++ /dev/null @@ -1,198 +0,0 @@ ---- -title: Combine Instructions in One Transaction -description: Guide to combine multiple instructions in a single transaction. Full code example for token pool creation and for first-time compression of existing SPL tokens. -keywords: ["batch token operations solana", "combine transactions solana"] ---- - -import InstallDependencies from '/snippets/setup/install-dependencies-codegroup.mdx'; -import SetupEnvironment from '/snippets/setup/setup-environment-tabs.mdx'; - -The SDK provides instruction-level APIs that return instructions without sending transactions. Combine these instructions to build custom transactions with multiple instructions. - -This guide demonstrates creating a token pool and compressing existing SPL tokens in a single transaction. - -# Full Code Example - - - -### Prerequisites - -Make sure you have dependencies and developer environment set up! - - - -**Dependencies** - - - -**Developer Environment** - - - - - - -### Create Token Pool + Compress - -```typescript create-pool-and-compress.ts highlight={26,71} expandable -// 1. Setup: Create regular SPL token and mint to ATA -// 2. Build instructions for create token pool and compress -// 3. Combine instructions in one transaction -// 4. Verify compressed balance - -import { - Keypair, - ComputeBudgetProgram, -} from '@solana/web3.js'; -import { - createRpc, - buildAndSignTx, - sendAndConfirmTx, - selectStateTreeInfo, -} from '@lightprotocol/stateless.js'; -import { CompressedTokenProgram } from '@lightprotocol/compressed-token'; -import { - createMint, - getOrCreateAssociatedTokenAccount, - mintTo, - TOKEN_PROGRAM_ID, -} from '@solana/spl-token'; -import BN from 'bn.js'; - -async function createPoolAndCompress() { - // Step 1: Setup - Create regular SPL token and mint to ATA - const rpc = createRpc(); - const payer = Keypair.generate(); - const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); - await rpc.confirmTransaction(airdropSignature); - - // Create regular SPL token mint - const mint = await createMint( - rpc, - payer, - payer.publicKey, - null, - 9, - undefined, - undefined, - TOKEN_PROGRAM_ID - ); - - // Create ATA and mint tokens to it - const ata = await getOrCreateAssociatedTokenAccount( - rpc, - payer, - mint, - payer.publicKey, - undefined, - undefined, - undefined, - TOKEN_PROGRAM_ID - ); - - await mintTo( - rpc, - payer, - mint, - ata.address, - payer, - 1_000_000_000, // 1 token with 9 decimals - undefined, - undefined, - TOKEN_PROGRAM_ID - ); - - console.log("Regular SPL token created:", mint.toBase58()); - console.log("ATA balance:", 1, "token"); - - // Step 2: Build instructions for create token pool and compress - const outputStateTreeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos()); - - // Derive token pool PDA - const tokenPoolPda = CompressedTokenProgram.deriveTokenPoolPda(mint); - - // Create token pool instruction - const createTokenPoolIx = await CompressedTokenProgram.createTokenPool({ - feePayer: payer.publicKey, - mint, - tokenProgram: TOKEN_PROGRAM_ID, - }); - - // Manually construct TokenPoolInfo for first-time compression - const tokenPoolInfo = { - mint: mint, - tokenPoolPda: tokenPoolPda, - tokenProgram: TOKEN_PROGRAM_ID, - isInitialized: true, // Set to true even though pool will be created in this tx - balance: new BN(0), - poolIndex: 0, - bump: 0, // Placeholder value - }; - - // Create compress instruction - const compressIx = await CompressedTokenProgram.compress({ - outputStateTreeInfo, - tokenPoolInfo, - payer: payer.publicKey, - owner: payer.publicKey, - source: ata.address, - toAddress: payer.publicKey, - amount: new BN(1_000_000_000), - mint, - }); - - // Step 3: Combine instructions in one transaction - const { blockhash } = await rpc.getLatestBlockhash(); - - const allInstructions = [ - ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }), - createTokenPoolIx, - compressIx, - ]; - - const tx = buildAndSignTx( - allInstructions, - payer, - blockhash, - [] - ); - - const txId = await sendAndConfirmTx(rpc, tx); - - console.log("Token pool created and tokens compressed"); - console.log("Transaction:", txId); - - // Step 4: Verify compressed balance - const compressedAccounts = await rpc.getCompressedTokenAccountsByOwner( - payer.publicKey, - { mint } - ); - - const compressedBalance = compressedAccounts.items.reduce( - (sum, account) => sum.add(account.parsed.amount), - new BN(0) - ); - - console.log("Compressed balance:", compressedBalance.toNumber() / 1_000_000_000, "tokens"); - - return { - transactionSignature: txId, - mint, - compressedBalance: compressedBalance.toNumber(), - }; -} - -createPoolAndCompress().catch(console.error); -``` - - - -# Next Steps - - diff --git a/compressed-tokens/advanced-guides/use-token-2022-with-compression.mdx b/compressed-tokens/advanced-guides/use-token-2022-with-compression.mdx deleted file mode 100644 index 0afaf0a1..00000000 --- a/compressed-tokens/advanced-guides/use-token-2022-with-compression.mdx +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: Use Token-2022 with Compression -description: Complete guide to mint, compress and transfer tokens with Token-2022 Metadata with ZK Compression. -keywords: ["token 2022 on solana", "token extensions on solana", "token 2022 compression"] ---- - -import InstallDependencies from '/snippets/setup/install-dependencies-codegroup.mdx'; -import SetupEnvironment from '/snippets/setup/setup-environment-tabs.mdx'; - -# What you will do - -With this guide you will mint, compress, and transfer tokens with Token-2022 Metadata. - -# Overview Token 2022 Extensions - -Token 2022 Extensions are optional features that can be added to Token 2022 mints on Solana to enable additional functionality. - -ZK Compression supports compressing the following mint-extensions: - -* MetadataPointer -* TokenMetadata -* InterestBearingConfig -* GroupPointer -* GroupMemberPointer -* TokenGroup -* TokenGroupMember - -All other extensions are not yet supported. - - -If you require support for other mint-extensions, [let us know](https://t.me/swen_light)! - - -# Get started - - - - - -You need the following SDK versions: - -* `@lightprotocol/stateless.js` ≥ 0.21.0 -* `@lightprotocol/compressed-token` ≥ 0.21.0 -* `@solana/web3.js` ≥ 1.95.3 - - -Make sure you have dependencies and developer environment set up! - - - -**Dependencies** - - - -**Developer Environment** - - - - - - -Run `compress-t22.ts`. - -```typescript compress-t22.ts expandable -// Token-2022 with ZK Compression - Local -// 1. Load wallet and connect to local validator -// 2. Create Token-2022 mint with metadata extension and register for compression via createTokenPool() -// 3. Mint SPL tokens to ATA, compress via compress(), and transfer compressed tokens via transfer() -// 4. Verify balances via getTokenAccountBalance and getCompressedTokenAccountsByOwner - -import { confirmTx, createRpc } from "@lightprotocol/stateless.js"; -import { - compress, - createTokenPool, - transfer, -} from "@lightprotocol/compressed-token"; -import { - getOrCreateAssociatedTokenAccount, - mintTo as mintToSpl, - TOKEN_2022_PROGRAM_ID, - createInitializeMetadataPointerInstruction, - createInitializeMintInstruction, - ExtensionType, - getMintLen, - LENGTH_SIZE, - TYPE_SIZE, -} from "@solana/spl-token"; -import { - Keypair, - sendAndConfirmTransaction, - SystemProgram, - Transaction, -} from "@solana/web3.js"; -import { - createInitializeInstruction, - pack, - TokenMetadata, -} from "@solana/spl-token-metadata"; - -// Step 1: Setup local connection and load wallet -import * as fs from 'fs'; -import * as os from 'os'; - -const walletPath = `${os.homedir()}/.config/solana/id.json`; -const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); -const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); -const connection = createRpc(); // defaults to localhost:8899 - -(async () => { - - // Generate mint keypair and define Token-2022 metadata - const mint = Keypair.generate(); - const decimals = 9; - - const metadata: TokenMetadata = { - mint: mint.publicKey, - name: "Local Test Token", - symbol: "LTT", - uri: "https://example.com/token-metadata.json", - additionalMetadata: [["environment", "localnet"], ["test", "true"]], - }; - - // Calculate space requirements for Token-2022 mint with MetadataPointer extension - const mintLen = getMintLen([ExtensionType.MetadataPointer]); - const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; - - // Check wallet balance - const balance = await connection.getBalance(payer.publicKey); - - // Step 2: Create Token-2022 mint with metadata extension - const mintLamports = await connection.getMinimumBalanceForRentExemption( - mintLen + metadataLen - ); - - const mintTransaction = new Transaction().add( - // Create account for Token-2022 mint - SystemProgram.createAccount({ - fromPubkey: payer.publicKey, - newAccountPubkey: mint.publicKey, - space: mintLen, - lamports: mintLamports, - programId: TOKEN_2022_PROGRAM_ID, - }), - // Initialize MetadataPointer extension - createInitializeMetadataPointerInstruction( - mint.publicKey, - payer.publicKey, - mint.publicKey, - TOKEN_2022_PROGRAM_ID - ), - // Initialize Token-2022 mint account - createInitializeMintInstruction( - mint.publicKey, - decimals, - payer.publicKey, - null, - TOKEN_2022_PROGRAM_ID - ), - // Initialize token metadata - createInitializeInstruction({ - programId: TOKEN_2022_PROGRAM_ID, - mint: mint.publicKey, - metadata: mint.publicKey, - name: metadata.name, - symbol: metadata.symbol, - uri: metadata.uri, - mintAuthority: payer.publicKey, - updateAuthority: payer.publicKey, - }) - ); - - // Send Token-2022 mint creation transaction - const mintCreationTxId = await sendAndConfirmTransaction( - connection, - mintTransaction, - [payer, mint] - ); - console.log(`Token-2022 mint created with address: ${mint.publicKey.toString()}`); - - // Step 3: Call createTokenPool() to initialize omnibus account - // and register Token-2022 mint with Light Token Program - // Create PDA that holds SPL tokens for compressed tokens - const registerTxId = await createTokenPool( - connection, - payer, - mint.publicKey, // Token-2022 mint to register with Light Token Program - undefined, - TOKEN_2022_PROGRAM_ID - ); - - // Step 4: Create associated token account and mint tokens - const ata = await getOrCreateAssociatedTokenAccount( - connection, - payer, - mint.publicKey, // Token-2022 mint with token pool for compression - payer.publicKey, - undefined, - undefined, - undefined, - TOKEN_2022_PROGRAM_ID - ); - - // Mint Token-2022 tokens to ATA - const mintAmount = 400_000_000; // 0.4 tokens - const mintToTxId = await mintToSpl( - connection, - payer, - mint.publicKey, // Token-2022 mint with token pool for compression - ata.address, // destination token account - payer.publicKey, // mint authority - mintAmount, // amount to mint - undefined, // multiSigners (not used for single authority) - undefined, - TOKEN_2022_PROGRAM_ID - ); - console.log(`Minted ${mintAmount / 1e9} Token-2022 tokens with metadata extension`); - console.log(`Transaction: ${mintToTxId}`); - - // Step 5: Call compress() to convert Token-2022 tokens to compressed format - // Transfer Token-2022 tokens to omnibus pool and mint compressed tokens - const compressAmount = 300_000_000; // 0.3 tokens - const compressTxId = await compress( - connection, - payer, - mint.publicKey, // Token-2022 mint with token pool for compression - compressAmount, // amount to compress - payer, // owner of SPL tokens - ata.address, // Source ATA for compression - payer.publicKey // recipient for compressed tokens - ); - console.log(`\nCompressed ${compressAmount / 1e9} Token-2022 tokens`); - console.log(`Transaction: ${compressTxId}`); - - // Step 6: Transfer compressed Token-2022 tokens - const transferRecipient = Keypair.generate(); // Keypair recipient - const transferAmount = 100_000_000; // 0.1 tokens - const transferTxId = await transfer( - connection, - payer, - mint.publicKey, // Token-2022 mint with token pool for compression - transferAmount, - payer, - transferRecipient.publicKey - ); - console.log(`\nTransferred ${transferAmount / 1e9} Compressed Token-2022 tokens`); - console.log(`Transaction: ${transferTxId}`); - console.log(`Recipient: ${transferRecipient.publicKey.toString()}`); - - // Step 7: Verify balances via getTokenAccountBalance and getCompressedTokenAccountsByOwner - const senderCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(payer.publicKey, { mint: mint.publicKey }); - const senderBalance = senderCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0); - - const recipientCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(transferRecipient.publicKey, { mint: mint.publicKey }); - const recipientBalance = recipientCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0); - - const splBalance = await connection.getTokenAccountBalance(ata.address); - - console.log(`\nSummary Token-2022 balances:`); - console.log(`Sender balance: ${senderBalance / 1e9} compressed tokens / ${splBalance.value.uiAmount} SPL tokens`); - console.log(`Recipient balance: ${recipientBalance / 1e9} compressed tokens`); - -})().catch(console.error); -``` - - - -# Next Steps - - - - diff --git a/compressed-tokens/advanced-guides/airdrop.mdx b/compressed-tokens/airdrop.mdx similarity index 99% rename from compressed-tokens/advanced-guides/airdrop.mdx rename to compressed-tokens/airdrop.mdx index 92c26808..34ce8ceb 100644 --- a/compressed-tokens/advanced-guides/airdrop.mdx +++ b/compressed-tokens/airdrop.mdx @@ -1,6 +1,6 @@ --- -title: Airdrop Guide with ZK Compression -sidebarTitle: Airdrop Guide +title: Airdrop Guide +sidebarTitle: Token Distribution description: Complete client and program guides to create an airdrop – with or without code. ZK compression is the most efficient way to distribute SPL tokens. keywords: ["solana airdrop", "merkle distributor", "merkle airdrop", "airdrop infrastructure on solana", "scalable token distribution on solana", "token airdrop for protocols"] --- diff --git a/compressed-tokens/for-privy.mdx b/compressed-tokens/for-privy.mdx index a7200aa6..4d05b2c0 100644 --- a/compressed-tokens/for-privy.mdx +++ b/compressed-tokens/for-privy.mdx @@ -1,6 +1,6 @@ --- title: "Rent-Free SPL Accounts with Privy" -sidebarTitle: "Privy Guide" +sidebarTitle: "Sign with Privy Wallet" description: "Integrate compressed tokens with Privy embedded wallets for rent-free SPL token accounts and transfers." keywords: ["privy solana", "privy compressed tokens", "embedded wallet compression", "rent-free tokens privy"] --- @@ -96,7 +96,7 @@ const rpc = createRpc(RPC_ENDPOINT); Before we call compress or decompresss, we need: -* An SPL mint with a interface PDA for compression. This PDA can be created for new SPL mints via [`createMint()`](/compressed-tokens/guides/create-mint-with-token-pool) or added to existing SPL mints via [`createTokenPool()`](/compressed-tokens/guides/add-token-pools-to-mint-accounts). +* An SPL mint with a interface PDA for compression. This PDA can be created for new SPL mints via [`createMint()`](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) or added to existing SPL mints via [`createTokenPool()`](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook). * For `compress()` SPL tokens in an Associated Token Account, or * For `decompress()` compressed token accounts with sufficient balance. diff --git a/compressed-tokens/guides.mdx b/compressed-tokens/guides.mdx deleted file mode 100644 index 16a44ebd..00000000 --- a/compressed-tokens/guides.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Guides -description: "Overview of guides to compressed token operations and reference table with descriptions. Guides include full code examples, troubleshooting, and advanced configurations." -sidebarTitle: "Overview" ---- - -import GuidesTable from '/snippets/overview-tables/compressed-tokens-guides-table.mdx'; - - diff --git a/compressed-tokens/guides/add-token-pools-to-mint-accounts.mdx b/compressed-tokens/guides/add-token-pools-to-mint-accounts.mdx deleted file mode 100644 index d0f1da85..00000000 --- a/compressed-tokens/guides/add-token-pools-to-mint-accounts.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Create Token Pools for Compression to Existing Mints -description: "Create a token pool for an existing SPL mint. Requires only fee_payer with no mint authority constraint." -keywords: ["compressed token pools", "token pool on solana"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import ActionCode from '/snippets/code-snippets/compressed-token/create-token-pool/action.mdx'; - -Create a token for compression fo an existing SPL mint. `createTokenPool()` requires only `fee_payer` and has no mint authority constraint. - - -The token pool account itself requires rent, but individual compressed token accounts are rent-free. - - - -```typescript function-create-token-pool.ts -// Creates token pool account for existing SPL mint -const transactionSignature = await createTokenPool( - rpc, - payer, - mint, -); -``` - - - -**Best Practice:** Each mint supports a maximum of 4 token pools total. During compression/decompression operations, token pools get write-locked. Use `addTokenPools()` to create additional pools that increase per-block write-lock capacity. - - -## Get Started - - - -### Create Token Pool - - - - - - - -# Troubleshooting - - - - -You're trying to access a token pool that doesn't exist. - -```typescript -// Create the missing token pool -const poolTx = await createTokenPool(rpc, payer, mint); -console.log("Token pool created:", poolTx); -``` - - - - -# Advanced Configuration - - - - -Create pools for multiple mints: - -```typescript -const mints = [ - new PublicKey("MINT_1_ADDRESS"), - new PublicKey("MINT_2_ADDRESS"), - new PublicKey("MINT_3_ADDRESS"), -]; - -for (const mint of mints) { - try { - const poolTx = await createTokenPool(rpc, payer, mint); - console.log(`Pool created for ${mint.toBase58()}:`, poolTx); - } catch (error) { - console.log(`Failed for ${mint.toBase58()}:`, error.message); - } -} -``` - - - - - -Create token pools for Token-2022 mints: - -```typescript -import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; - -const poolTx = await createTokenPool( - rpc, - payer, - mint, // Token-2022 mint - undefined, - TOKEN_2022_PROGRAM_ID, -); -``` - - - - -# Next Steps - - diff --git a/compressed-tokens/guides/compress-decompress.mdx b/compressed-tokens/guides/compress-decompress.mdx deleted file mode 100644 index 8b9e2b73..00000000 --- a/compressed-tokens/guides/compress-decompress.mdx +++ /dev/null @@ -1,217 +0,0 @@ ---- -title: Compress and Decompress SPL Tokens -description: "Convert SPL tokens between compressed and regular format with compress() and decompress()." -keywords: ["compress spl tokens", "decompress tokens on solana"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import MintPrereq from '/snippets/setup/compressed-tokens-mint-prereq.mdx'; -import CompressActionCode from '/snippets/code-snippets/compressed-token/compress/action.mdx'; -import DecompressActionCode from '/snippets/code-snippets/compressed-token/decompress/action.mdx'; - - -```typescript compress() -// Compress SPL tokens to compressed tokens -const compressionSignature = await compress( - rpc, - payer, - mint, // SPL mint with token pool for compression - amount, - payer, // owner of SPL tokens - tokenAccount.address, // source SPL token account (sourceTokenAccount parameter) - recipient, // recipient owner address (toAddress parameter) -); -``` - -```typescript decompress() -// Decompress compressed tokens to SPL tokens -const transactionSignature = await decompress( - rpc, - payer, - mint, // SPL mint with token pool for compression - amount, - payer, // owner of compressed tokens - tokenAccount.address, // destination token account (toAddress parameter) -); -``` - - - -**Function Difference and Best Practice:** - -* `compress(amount, sourceTokenAccount, toAddress)` compresses specific amounts from source to a specified recipient. Use for transfers and precise amounts. -* `compressSplTokenAccount(tokenAccount, remainingAmount)` compresses the entire SPL token account balance minus optional remaining amount only to the same owner. Use to migrate complete token accounts with optional partial retention. [Here is how](/compressed-tokens/guides/compress-spl-token-account). - - -## Get Started - - - -### Compress / Decompress Tokens - - - - -Before we can compress or decompresss, we need: - -* An SPL mint with a token pool for compression. This token pool can be created for new SPL mints via [`createMint()`](/compressed-tokens/guides/create-mint-with-token-pool) or added to existing SPL mints via [`createTokenPool()`](/compressed-tokens/guides/add-token-pools-to-mint-accounts). -* For `compress()` SPL tokens in an Associated Token Account, or -* For `decompress()` compressed token accounts with sufficient balance. - - - - - - - - - - - - - - -# Troubleshooting - - - - -Check your balances before operations: - -```typescript -// For decompression - check compressed balance -const compressedAccounts = await rpc.getCompressedTokenAccountsByOwner( - owner.publicKey, - { mint } -); -const compressedBalance = compressedAccounts.items.reduce( - (sum, account) => sum.add(account.parsed.amount), - new BN(0) -); - -// For compression - check SPL token balance -const tokenBalance = await rpc.getTokenAccountBalance(tokenAccount); -const splBalance = new BN(tokenBalance.value.amount); - -console.log("Can decompress up to:", compressedBalance.toString()); -console.log("Can compress up to:", splBalance.toString()); -``` - - - - - -Ensure the signer owns the tokens being decompressed/compressed: - -```typescript -// The owner parameter must be the actual owner -const decompressTx = await decompress( - rpc, - payer, // can be different (pays fees) - mint, - amount, - actualOwner, // must own compressed tokens - destinationAta, -); - -const compressTx = await compress( - rpc, - payer, // can be different (pays fees) - mint, - amount, - actualOwner, // must own SPL tokens - sourceAta, - recipient, -); -``` - - - - -# Advanced Configuration - - - - -Compress tokens directly to someone else: - -```typescript -const recipientWallet = new PublicKey("RECIPIENT_WALLET_ADDRESS"); - -// Compress your SPL tokens to recipient -const compressTx = await compress( - rpc, - payer, - mint, - amount, - tokenOwner, // current owner signs - tokenAccount, // your token account - recipientWallet, // recipient gets compressed tokens -); -``` - - - - - -Compress multiple token accounts: - -```typescript -// Compress to multiple recipients at once -const recipients = [recipient1.publicKey, recipient2.publicKey, recipient3.publicKey]; -const amounts = [1_000_000_000, 2_000_000_000, 500_000_000]; // Different amounts - -const batchCompressTx = await compress( - rpc, - payer, - mint, - amounts, // Array of amounts - owner, - tokenAccount, - recipients, // Array of recipients -); - -console.log("Batch compression completed:", batchCompressTx); -``` - - - - - -Decompress tokens using delegate authority: - -```typescript -import { decompressDelegated } from '@lightprotocol/compressed-token'; -import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token'; - -// Get ATA for decompressed tokens -const ataAddress = await getAssociatedTokenAddress( - mint, - recipient, - false, - TOKEN_PROGRAM_ID -); - -// Delegate decompresses tokens -await decompressDelegated( - rpc, - payer, - mint, - amount, - delegate, // Signer - owner of compressed tokens - ataAddress, // Uncompressed token account (ATA) -); -``` - - - - -# Next Steps - - diff --git a/compressed-tokens/guides/compress-spl-token-account.mdx b/compressed-tokens/guides/compress-spl-token-account.mdx deleted file mode 100644 index 1d26a2c1..00000000 --- a/compressed-tokens/guides/compress-spl-token-account.mdx +++ /dev/null @@ -1,189 +0,0 @@ ---- -title: Compress Complete SPL Token Accounts -description: "Compress the entire balance of an SPL token account to reclaim rent. Optionally leave some tokens in SPL format." -keywords: ["compress spl token accounts", "migrate spl to compressed"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import MintPrereq from '/snippets/setup/compressed-tokens-mint-prereq.mdx'; -import ActionCode from '/snippets/code-snippets/compressed-token/compress-spl-account/action.mdx'; - - -```typescript compressSplTokenAccount() -// Compress entire SPL token account balance -const transactionSignature = await compressSplTokenAccount( - rpc, - payer, - mint, // SPL mint with token pool for compression - owner, - tokenAccount, // SPL token account to compress -); -``` - -```typescript partialCompression() -// Compress account while keeping some tokens in SPL format -const transactionSignature = await compressSplTokenAccount( - rpc, - payer, - mint, // SPL mint with token pool for compression - owner, - tokenAccount, // SPL token account to compress - remainingAmount, // amount to keep in SPL format -); -``` - - - -After compression, empty token accounts can now be closed to reclaim rent with [`closeAccount()`](https://solana.com/developers/cookbook/tokens/close-token-accounts). - - - -**Function Difference and Best Practice:** - -* `compressSplTokenAccount(tokenAccount, remainingAmount)` compresses the entire SPL token account balance minus optional remaining amount only to the same owner. Use to migrate complete token accounts with optional partial retention. -* `compress(amount, sourceTokenAccount, toAddress)` compresses specific amounts from source to a specified recipient. Use for transfers and precise amounts. [Here is how](/compressed-tokens/guides/compress-decompress). - - -## Get Started - - - -### Compress SPL Token Accounts - - - - - - - - - - -# Troubleshooting - - - - -The token account doesn't have enough tokens for the operation. - -```typescript -// Check token account balance before compression -const balance = await rpc.getTokenAccountBalance(tokenAccount); - -if (Number(balance.value.amount) === 0) { - console.log("Token account is empty"); - return; -} - -console.log("Available balance:", Number(balance.value.amount)); - -// Proceed with compression -const compressTx = await compressSplTokenAccount( - rpc, - payer, - mint, - owner, - tokenAccount, -); -``` - - - - - -The `remainingAmount` parameter exceeds the current account balance. - -```typescript -const balance = await rpc.getTokenAccountBalance(tokenAccount); -const availableAmount = Number(balance.value.amount); -const remainingAmount = bn(500_000_000); // 0.5 tokens - -if (remainingAmount.gt(bn(availableAmount))) { - console.log(`Cannot leave ${remainingAmount.toString()} tokens`); - console.log(`Available balance: ${availableAmount}`); - throw new Error("Remaining amount exceeds balance"); -} - -// Use valid remaining amount -const compressTx = await compressSplTokenAccount( - rpc, - payer, - mint, - owner, - tokenAccount, - remainingAmount, // must be <= balance -); -``` - - - - -# Advanced Configuration - - - - -Compress most tokens while leaving some in SPL format: - -```typescript -import { bn } from '@lightprotocol/stateless.js'; - -// Leave 100 tokens (0.1 with 9 decimals) in SPL account -const remainingAmount = bn(100_000_000); - -const compressTx = await compressSplTokenAccount( - rpc, - payer, - mint, - owner, - tokenAccount, - remainingAmount, // amount to keep in SPL format -); - -// Account will retain remainingAmount tokens -``` - - - - - -Compress several token accounts for the same mint: - -```typescript -const tokenAccounts = [ - { account: new PublicKey("ACCOUNT_1"), owner: owner1 }, - { account: new PublicKey("ACCOUNT_2"), owner: owner2 }, - { account: new PublicKey("ACCOUNT_3"), owner: owner3 }, -]; - -// Compress each account -for (const { account, owner } of tokenAccounts) { - console.log(`Compressing account: ${account.toBase58()}`); - - try { - const compressTx = await compressSplTokenAccount( - rpc, - payer, - mint, - owner, - account, - ); - console.log(`Compressed: ${compressTx}`); - } catch (error) { - console.log(`Failed: ${error.message}`); - } -} -``` - - - - -# Next Steps - - diff --git a/compressed-tokens/guides/create-compressed-token-accounts.mdx b/compressed-tokens/guides/create-compressed-token-accounts.mdx deleted file mode 100644 index 28eecd3f..00000000 --- a/compressed-tokens/guides/create-compressed-token-accounts.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Create Compressed Token Accounts -description: Short guide to compressed token account creation with ZK Compression on Solana and difference to regular token accounts. -keywords: ["create compressed token account", "rent free token accounts on solana"] ---- - -Compressed token accounts store ownership information for compressed tokens like regular token accounts with two core differences. Compressed Tokens - -* do not require an Associated Token Accounts (ATAs), and -* do not require a rent-exempt balance. - -Compressed token accounts are created in the following scenarios: - -1. `mintTo()` creates compressed token accounts for recipients. - -How to Mint Compressed Tokens - -2. `transfer()` consumes existing accounts of the sender as input, and creates new compressed token accounts with updated balances as output for the sender and recipient(s). - -Transfer Compressed Tokens \ No newline at end of file diff --git a/compressed-tokens/guides/create-mint-with-token-pool.mdx b/compressed-tokens/guides/create-mint-with-token-pool.mdx deleted file mode 100644 index 14588ed0..00000000 --- a/compressed-tokens/guides/create-mint-with-token-pool.mdx +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: Create a Mint Account with Token Pool for Compression -description: "Create an SPL token mint with a token pool for compression. The token pool locks tokens while compressed and releases them when decompressed." -keywords: ["register mint for compression", "compressed mint account"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import ActionCode from '/snippets/code-snippets/compressed-token/create-mint/action.mdx'; -import InstructionCode from '/snippets/code-snippets/compressed-token/create-mint/instruction.mdx'; - -The mint account itself requires rent (like regular SPL mints), but individual compressed token accounts are rent-free. -Create a token pool for an existing SPL mint with [`createTokenPool()`](/compressed-tokens/guides/add-token-pools-to-mint-accounts) or use `createMint()` to create a new one from scratch. - - -```typescript createMint() -// Create SPL mint with token pool for compression -const { mint, transactionSignature } = await createMint( - rpc, - payer, - mintAuthority.publicKey, - decimals, -); -``` - - - -**Best Practice:** Each mint supports a maximum of 4 token pools total. During compression/decompression, token pools get write-locked. Use `addTokenPools()` to create additional pools that increase per-block write-lock capacity. - - -## Get Started - - - -### Create a Mint Account with Token Pool for Compression - - - - - - - - - - - - - - -# Advanced Configuration - - - - -Customize who can mint new compressed tokens. - -```typescript -const mintAuthority = Keypair.generate(); - -const { mint, transactionSignature } = await createMint( - rpc, - payer, - mintAuthority.publicKey, - 9, -); -``` - - - - - -Customize who can freeze/thaw compressed token accounts. - -```typescript -const freezeAuthority = Keypair.generate(); - -const { mint, transactionSignature } = await createMint( - rpc, - payer, - payer.publicKey, // mint authority - 9, // decimals - Keypair.generate(), // mint keypair - undefined, // confirm options - undefined, // token program ID - freezeAuthority.publicKey, // freeze authority -); -``` - - - - -# Next Steps - - diff --git a/compressed-tokens/guides/delegate.mdx b/compressed-tokens/guides/delegate.mdx deleted file mode 100644 index 399da209..00000000 --- a/compressed-tokens/guides/delegate.mdx +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: Approve and Revoke Delegate Authority -description: "Grant and remove delegate spending authority for compressed tokens with approve() and revoke(). Only the token owner can perform these operations." -keywords: ["delegate authority on solana", "token delegation compressed"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import ApproveActionCode from '/snippets/code-snippets/compressed-token/approve/action.mdx'; -import RevokeActionCode from '/snippets/code-snippets/compressed-token/revoke/action.mdx'; - -The `approve()` and `revoke()` functions grant and remove delegate spending authority for compressed tokens. Only the token owner can perform these instructions. - - -```typescript approve() -// Approve delegate for spending up to the specified amount -const approveSignature = await approve( - rpc, - payer, - mint, // SPL mint with token pool for compression - amount, - owner, - delegate.publicKey, // delegate account -); -``` - -```typescript revoke() -// Get delegated accounts for revocation -const delegatedAccounts = await rpc.getCompressedTokenAccountsByDelegate( - delegate.publicKey, - { mint } -); - -// Revoke delegate authority -const revokeSignature = await revoke( - rpc, - payer, - delegatedAccounts.items, // delegated compressed token accounts - owner, -); -``` - - -## Get Started - - - -### Approve / Revoke Delegates - - - - - - - - - - - - - -Before we approve or revoke delegates, we need: - -* compressed token accounts to delegate or revoke delegation from, and -* an SPL mint with a token pool for compression. This token pool can be created for new SPL mints via [`createMint()`](/compressed-tokens/guides/create-mint-with-token-pool) or added to existing SPL mints via [`createTokenPool()`](/compressed-tokens/guides/add-token-pools-to-mint-accounts). - - - - - -# Troubleshooting - - - - -Attempting to revoke non-delegated accounts. - -```typescript -/// Verify accounts are delegated before revocation. -const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate( - delegate.publicKey, - { mint } -); - -if (delegateAccounts.items.length === 0) { - console.log("No delegated accounts to revoke"); - return; -} -``` - - - - -# Advanced Configuration - - - - -```typescript -const delegates = [ - Keypair.generate().publicKey, - Keypair.generate().publicKey, -]; - -const amounts = [ - 200_000_000, // 0.2 tokens to first delegate - 300_000_000, // 0.3 tokens to second delegate -]; - -// Approve each delegate -for (let i = 0; i < delegates.length; i++) { - const approveTx = await approve( - rpc, - payer, - mint, - amounts[i], - tokenOwner, - delegates[i], - ); - - console.log(`Delegate ${i + 1} approved:`, approveTx); -} -``` - - - - - -```typescript -const delegates = [ - new PublicKey("DELEGATE_1_ADDRESS"), - new PublicKey("DELEGATE_2_ADDRESS"), -]; - -// Revoke each delegate -for (const delegate of delegates) { - // Get delegated accounts for this delegate - const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate( - delegate, - { mint } - ); - - if (delegateAccounts.items.length > 0) { - const revokeTx = await revoke( - rpc, - payer, - delegateAccounts.items, - tokenOwner, - ); - - console.log(`Delegate ${delegate.toBase58()} revoked:`, revokeTx); - } -} -``` - - - - -# Next Steps - - diff --git a/compressed-tokens/guides/merge-compressed-token-accounts.mdx b/compressed-tokens/guides/merge-compressed-token-accounts.mdx deleted file mode 100644 index e2d53e69..00000000 --- a/compressed-tokens/guides/merge-compressed-token-accounts.mdx +++ /dev/null @@ -1,145 +0,0 @@ ---- -title: Merge Compressed Token Accounts -description: "Consolidate multiple compressed token accounts of the same mint into a single account to reduce fragmentation." -keywords: ["merge compressed token accounts", "consolidate token accounts"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import ActionCode from '/snippets/code-snippets/compressed-token/merge-token-accounts/action.mdx'; - -The `mergeTokenAccounts()` function consolidates multiple compressed accounts of the same mint into a single compressed account. - -The function - -1. consumes multiple compressed token accounts (up to 8 accounts), and -2. creates a single output compressed token account with combined balance for the owner. - - -State trees where compressed account's are stored, are append only. `mergeTokenAccounts()` reduces account fragmentation to simplify balance calculations from `getCompressedTokenAccountsByOwner`. - - - -```typescript function-merge-accounts.ts -// Combines multiple compressed token accounts into single compressed account -const transactionSignature = await mergeTokenAccounts( - rpc, - payer, - mint, // SPL mint with token pool for compression - owner, -); -``` - - -## Get Started - - - -### Merge Compressed Accounts - - - - - - -Before we merge compressed accounts, we need - -* Multiple compressed token accounts of the same mint owned by the same wallet, and -* an SPL mint with a token pool for compression. This token pool can be created for new SPL mints via [`createMint()`](/compressed-tokens/guides/create-mint-with-token-pool) or added to existing SPL mints via [`createTokenPool()`](/compressed-tokens/guides/add-token-pools-to-mint-accounts). - - - - - -# Troubleshooting - - - - -The owner has no compressed token accounts for the specified mint: - -```typescript -// Check if accounts exist before merging -const accounts = await rpc.getCompressedTokenAccountsByOwner( - owner.publicKey, - { mint } -); - -if (accounts.items.length === 0) { - console.log("No compressed token accounts found for this mint"); - console.log("Mint address:", mint.toBase58()); - console.log("Owner address:", owner.publicKey.toBase58()); - return; -} - -console.log(`Found ${accounts.items.length} accounts to merge`); -``` - - - - -# Advanced Configuration - - - - -```typescript -// Get account count -const accounts = await rpc.getCompressedTokenAccountsByOwner( - owner.publicKey, - { mint } -); - -// Only merge if more than 2 accounts -if (accounts.items.length > 2) { - console.log(`Merging ${accounts.items.length} accounts...`); - - const mergeTx = await mergeTokenAccounts( - rpc, - payer, - mint, - tokenOwner, - ); - - console.log("Merge completed:", mergeTx); -} else { - console.log("Merge not needed - optimal account structure"); -} -``` - - - - - -```typescript -const mints = [ - new PublicKey("MINT_1_ADDRESS"), - new PublicKey("MINT_2_ADDRESS"), -]; - -// Merge accounts for each mint -for (const mint of mints) { - console.log(`Merging accounts for mint: ${mint.toBase58()}`); - - const mergeTx = await mergeTokenAccounts( - rpc, - payer, - mint, - tokenOwner, - ); - - console.log(`Merge completed: ${mergeTx}`); -} -``` - - - - -# Next Steps - - diff --git a/compressed-tokens/guides/mint-compressed-tokens.mdx b/compressed-tokens/guides/mint-compressed-tokens.mdx deleted file mode 100644 index 9ff7323a..00000000 --- a/compressed-tokens/guides/mint-compressed-tokens.mdx +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: Mint Compressed Tokens -description: "Mint tokens to compressed token accounts for recipients and increases a mint's token supply. Only the mint authority can perform this operation. -" -keywords: ["mint compressed tokens on solana", "scalable token minting"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import MintPrereq from '/snippets/setup/compressed-tokens-mint-prereq.mdx'; -import ActionCode from '/snippets/code-snippets/compressed-token/mint-to/action.mdx'; -import InstructionCode from '/snippets/code-snippets/compressed-token/mint-to/instruction.mdx'; - -```typescript -// Mint compressed tokens - mints SPL tokens to pool, creates compressed token accounts -const transactionSignature = await mintTo( - rpc, - payer, - mint, // SPL mint with token pool for compression - recipient, // recipient address (toPubkey parameter) - payer, // mint authority - amount, -); -``` - -## Get Started - - - -### Mint Compressed Tokens - - - - - - - - - - - - - - - - -# Troubleshooting - - - - - -```typescript -// Error message: "TokenPool not found. Please create a compressed token -// pool for mint: [ADDRESS] via createTokenPool(). -``` - -The mint does no have a token pool for compression. Ensure you created the mint using `createMint`. - -```typescript -// Create mint with token pool for compression -import { createMint } from '@lightprotocol/compressed-token'; -const { mint } = await createMint(rpc, payer, payer.publicKey, 9); -``` - - - - - -The token pool info doesn't correspond to the mint address. Ensure you're fetching the correct pool: - -```typescript -// Get the correct token pool for your mint -const tokenPoolInfo = await getTokenPoolInfos(rpc, mint); -``` - - - - - -When minting to multiple recipients, ensure arrays are the same size. - -```typescript -// Wrong: Mismatched array lengths -const recipients = [addr1, addr2, addr3]; -const amounts = [100, 200]; // Only 2 amounts for 3 recipients - -// Correct: Same length arrays -const recipients = [addr1, addr2, addr3]; -const amounts = [100, 200, 300]; // 3 amounts for 3 recipients -``` - - - - -# Advanced Configuration - - - - -```typescript -// Mint different amounts to multiple recipients -const recipients = [ - Keypair.generate().publicKey, - Keypair.generate().publicKey, - Keypair.generate().publicKey, -]; - -const amounts = [ - 1_000_000_000, // 1 token - 2_000_000_000, // 2 tokens - 500_000_000, // 0.5 tokens -]; - -const transactionSignature = await mintTo( - rpc, - payer, - mint, // SPL mint with token pool for compression - recipients, // array of recipients (toPubkey parameter) - payer, // mint authority - amounts, // array of amounts (amount parameter) -); -``` - - - - - -Mint tokens using a custom mint authority with `approveAndMintTo()`: - -```typescript -import { approveAndMintTo } from '@lightprotocol/compressed-token'; - -// Mint tokens with a separate mint authority -const transactionSignature = await approveAndMintTo( - rpc, - payer, - mint, // SPL mint with token pool for compression - recipient.publicKey, // recipient of minted tokens (toPubkey parameter) - mintAuthority, // mint authority - mintAmount, -); -``` - - - - -# Next Steps - - \ No newline at end of file diff --git a/compressed-tokens/guides/transfer-compressed-tokens.mdx b/compressed-tokens/guides/transfer-compressed-tokens.mdx deleted file mode 100644 index 41720feb..00000000 --- a/compressed-tokens/guides/transfer-compressed-tokens.mdx +++ /dev/null @@ -1,190 +0,0 @@ ---- -title: Transfer Compressed Tokens -description: "Transfer compressed tokens between accounts. Transfers consume input accounts and create new output accounts with updated balances." -keywords: ["transfer compressed tokens", "token transfer on solana"] ---- - -import FullSetup from "/snippets/setup/full-setup.mdx"; -import MintPrereq from '/snippets/setup/compressed-tokens-mint-prereq.mdx'; -import ActionCode from '/snippets/code-snippets/compressed-token/transfer/action.mdx'; - -The `transfer()` function moves compressed tokens between accounts. -* SPL token transfers that update the existing account -* Compressed token transfers consume input accounts from the sender and create new output accounts for sender and recipient with updated balances. - -SPL token accounts can be compressed in the same transaction with `compress_or_decompress_amount`, if needed. - - - -```typescript function-transfer-compressed-tokens.ts -// Transfer compressed tokens -const transactionSignature = await transfer( - rpc, - payer, - mint, // SPL mint with token pool for compression - amount, - payer, - recipient, // destination address (toAddress parameter) -) -``` - -## Get Started - - - -### Transfer Compressed Tokens - - - - - - - - - -# Troubleshooting - - - - -The sender doesn't have enough compressed tokens for the requested transfer amount. - -```typescript -// Check current balance first -const tokenAccounts = await rpc.getCompressedTokenAccountsByOwner( - owner.publicKey, - { mint } -); - -if (tokenAccounts.items.length === 0) { - throw new Error("No compressed token accounts found"); -} - -// Calculate total balance across all accounts -const totalBalance = tokenAccounts.items.reduce( - (sum, account) => sum.add(account.parsed.amount), - new BN(0) -); - -console.log("Available balance:", totalBalance.toString()); - -// Ensure transfer amount doesn't exceed balance -if (new BN(transferAmount).gt(totalBalance)) { - throw new Error(`Transfer amount ${transferAmount} exceeds balance ${totalBalance.toString()}`); -} -``` - - - - - -The transfer requires more than 4 compressed accounts, which exceeds the transaction limit. - -```typescript -// Error message: "Account limit exceeded: max X (4 accounts) per transaction. -// Total balance: Y (Z accounts). Consider multiple transfers to spend full balance." - -// Split into multiple smaller transfers -const maxTransferPerTx = 1_000_000_000; // Adjust based on your account sizes - -if (transferAmount > maxTransferPerTx) { - console.log("Large transfer detected, splitting into multiple transactions..."); - - let remainingAmount = transferAmount; - while (remainingAmount > 0) { - const currentTransfer = Math.min(remainingAmount, maxTransferPerTx); - - await transfer( - rpc, - payer, - mint, - currentTransfer, - owner, - recipient - ); - - remainingAmount -= currentTransfer; - console.log(`Transferred ${currentTransfer}, remaining: ${remainingAmount}`); - } -} -``` - - - - -# Advanced Configuration - - - - -Transfer to multiple recipients in separate transactions: - -```typescript -const recipients = [ - Keypair.generate().publicKey, - Keypair.generate().publicKey, - Keypair.generate().publicKey, -]; - -const amounts = [ - 100_000_000, // 0.1 tokens - 200_000_000, // 0.2 tokens - 150_000_000, // 0.15 tokens -]; - -for (let i = 0; i < recipients.length; i++) { - const transactionSignature = await transfer( - rpc, - payer, - mint, - amounts[i], - owner, - recipients[i], - ); - - console.log(`Transfer ${i + 1} completed:`, transactionSignature); -} -``` - - - - - -Transfer tokens using delegate authority: - -```typescript -import { approve, transferDelegated } from '@lightprotocol/compressed-token'; - -// 1. Owner approves delegate -await approve( - rpc, - payer, - mint, - amount, - owner, // Signer - delegate.publicKey, // PublicKey -); - -// 2. Delegate transfers tokens -await transferDelegated( - rpc, - payer, - mint, - transferAmount, - delegate, // Signer - named "owner" in SDK - recipient, -); -``` - - - - -# Next Steps - - diff --git a/compressed-tokens/overview.mdx b/compressed-tokens/overview.mdx index cab4c9e9..7a4dcc6a 100644 --- a/compressed-tokens/overview.mdx +++ b/compressed-tokens/overview.mdx @@ -1,6 +1,6 @@ --- -title: Overview -description: "Overview to compressed tokens and guides with full code examples. Use for distributing tokens (eg rewards, airdrops) or creating associated token accounts for users." +title: "Compressed Tokens" +description: "Compressed tokens are the most efficient way to distribute SPL tokens (eg rewards, airdrops). For other token purposes use Light Token." keywords: ["compressed tokens on solana", "scalable token distribution on solana", "infrastructure for token distribution", "airdrop infrastructure on solana", "launchpad infrastructure on solana"] --- @@ -10,9 +10,12 @@ import SetupEnvironment from "/snippets/setup/setup-environment-tabs.mdx"; import InstallDependencies from "/snippets/setup/install-dependencies-codegroup.mdx"; import { TokenAccountCompressedVsSpl } from "/snippets/jsx/token-account-compressed-vs-spl.jsx"; - - **Compressed Tokens are supported by leading Solana wallets such as Phantom and Backpack.** - + +**Note: The new [Light Token Program Beta](/light-token/welcome)** is live on Solana Devnet with mainnet expected in Q1 2026. +We recommend to use Light Token for most purposes except airdrops and other forms of token distribution. +The SDK for compressed tokens can still be used once Light Token is on Solana Mainnet. +For production use today, use Compressed Tokens, which are on Solana Mainnet. + | Creation | Solana | Compressed | | :---------------- | :------------------ | :----------------- | @@ -22,16 +25,20 @@ import { TokenAccountCompressedVsSpl } from "/snippets/jsx/token-account-compres 2. Compressed token accounts are rent-free. 3. Any light-token or SPL token can be compressed/decompressed at will. + + **Compressed Tokens are supported by leading Solana wallets such as Phantom and Backpack.** + + ## Recommended Usage of Compressed Tokens Distribute tokens without paying upfront rent per recipient. @@ -43,44 +50,38 @@ import { TokenAccountCompressedVsSpl } from "/snippets/jsx/token-account-compres title="Compress SPL Token Accounts" icon="chevron-right" color="#0066ff" - href="/compressed-tokens/guides/compress-spl-token-account" + href="https://github.com/Lightprotocol/examples-zk-compression/blob/main/compressed-token-cookbook/actions/compress.ts" horizontal /> -# Get Started +## RPC methods - - - +The Photon API provides ZK Compression methods that extend the default Solana JSON RPC API. -### Install dependencies + - - +## Guides - +The SDK for compressed tokens can be used but is not maintained. +Use [Light Token](/light-token/welcome) for other purposes than token distribution or storage of inactive tokens. -### Set up your developer environment +| | | +|---------|------| +| Compressed token cookbook | [GitHub](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | +| Token distribution | [GitHub](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution) | +| Web client | [GitHub](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-web-client) | - - - - - - - - - - - - ### Basic Guides - - +--- - - ### Advanced Guides - - +## Didn't find what you were looking for? - + +Reach out! [Telegram](https://t.me/swen_light) | [email](mailto:support@lightprotocol.com) | [Discord](https://discord.com/invite/7cJ8BhAXhu) + diff --git a/docs.json b/docs.json index 62533083..4cf4f83e 100644 --- a/docs.json +++ b/docs.json @@ -1,6 +1,6 @@ { "$schema": "https://mintlify.com/docs.json", - "theme": "aspen", + "theme": "maple", "name": "Light Protocol", "colors": { "primary": "#0066FF", @@ -20,67 +20,25 @@ } }, "navigation": { - "tabs": [ + "anchors": [ { - "tab": "Home", + "anchor": "Documentation", + "icon": "book", "groups": [ { - "group": "Overview", + "group": "Introduction", "pages": [ "home" ] - } - ] - }, - { - "tab": "Light Token Program", - "groups": [ + }, { - "group": "Get Started", + "group": "Light Token", "pages": [ "light-token/welcome", + "light-token/faq", "light-token/quickstart", - "light-token/faq" - ] - }, - { - "group": "DeFi", - "pages": [ - "light-token/defi/routers", - "light-token/defi/programs", - "light-token/defi/programs-pinocchio" - ] - }, - { - "group": "Data Streaming", - "pages": [ - "light-token/toolkits/for-streaming-tokens", - "light-token/toolkits/for-streaming-mints" - ] - }, - { - "group": "Toolkits", - "pages": [ - "light-token/toolkits/overview", - "light-token/toolkits/for-payments", - "light-token/toolkits/for-wallets" - ] - }, - { - "group": "Cookbook", - "pages": [ - "light-token/cookbook/overview", { - "group": "Examples", - "expanded": true, - "pages": [ - "light-token/examples/client", - "light-token/examples/program" - ] - }, - { - "group": "Recipes", - "expanded": true, + "group": "Cookbook", "pages": [ "light-token/cookbook/create-mint", "light-token/cookbook/create-ata", @@ -94,107 +52,74 @@ "light-token/cookbook/load-ata", "light-token/cookbook/close-token-account", "light-token/cookbook/burn" - + ] + }, + { + "group": "Examples", + "pages": [ + "light-token/examples/client", + "light-token/examples/program" ] } ] }, { - "group": "Learn", + "group": "DeFi", "pages": [ - "learn/light-token-standard", - "learn/ai-tools-guide" + "light-token/defi/routers", + "light-token/defi/programs", + "light-token/defi/programs-pinocchio" ] }, { - "group": "SDK Reference", - "pages": [ - "api-reference/libraries/light-sdk", - "api-reference/libraries/light-token", - "api-reference/libraries/light-program-test", - "api-reference/libraries/light-client", - "api-reference/libraries/stateless-js", - "api-reference/libraries/compressed-token" - ] - } - ] - }, - { - "tab": "ZK Compression", - "groups": [ - { - "group": "Get Started", + "group": "Data Streaming", "pages": [ - "welcome", - "quickstart" + "light-token/toolkits/for-streaming-mints", + "light-token/toolkits/for-streaming-tokens" ] }, { - "group": "ZK", + "group": "Payments", "pages": [ - "zk/overview", - "zk/examples" + "light-token/toolkits/for-payments", + "compressed-pdas/guides/how-to-create-nullifier-pdas" ] }, { - "group": "Compressed PDAs", - "expanded": true, + "group": "Wallets", "pages": [ - "compressed-pdas/overview", - "client-library/client-guide", - { - "group": "Program Guides", - "pages": [ - "compressed-pdas/guides", - "compressed-pdas/guides/how-to-create-compressed-accounts", - "compressed-pdas/guides/how-to-update-compressed-accounts", - "compressed-pdas/guides/how-to-close-compressed-accounts", - "compressed-pdas/guides/how-to-reinitialize-compressed-accounts", - "compressed-pdas/guides/how-to-burn-compressed-accounts", - "compressed-pdas/guides/how-to-create-nullifier-pdas" - ] - }, - "compressed-pdas/program-examples", - "compressed-pdas/solana-attestation-service" + "light-token/toolkits/for-wallets", + "compressed-tokens/for-privy" ] }, { - "group": "Compressed Tokens", + "group": "PDA Accounts", "pages": [ - "compressed-tokens/overview", - "compressed-tokens/for-privy", - "compressed-tokens/advanced-guides/airdrop", - { - "group": "Cookbook", - "pages": [ - "compressed-tokens/guides/create-compressed-token-accounts", - "compressed-tokens/guides/mint-compressed-tokens", - "compressed-tokens/guides/transfer-compressed-tokens", - "compressed-tokens/guides/compress-decompress", - "compressed-tokens/guides/compress-spl-token-account", - "compressed-tokens/guides/create-mint-with-token-pool", - "compressed-tokens/guides/add-token-pools-to-mint-accounts", - "compressed-tokens/guides/merge-compressed-token-accounts", - "compressed-tokens/guides/delegate" - ] - }, + "light-token/pda-overview", + "light-token/light-pda", { - "group": "Implementation Guides", + "group": "Compressed PDA", "pages": [ + "compressed-pdas/overview", + "client-library/client-guide", { - "group": "Integration", + "group": "Program Guides", "expanded": true, "pages": [ - "compressed-tokens/advanced-guides/how-to-combine-operations-in-one-transaction", - "compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens", - "compressed-tokens/advanced-guides/use-token-2022-with-compression" + "compressed-pdas/guides", + "compressed-pdas/guides/how-to-create-compressed-accounts", + "compressed-pdas/guides/how-to-update-compressed-accounts", + "compressed-pdas/guides/how-to-close-compressed-accounts", + "compressed-pdas/guides/how-to-reinitialize-compressed-accounts", + "compressed-pdas/guides/how-to-burn-compressed-accounts" ] }, + "compressed-pdas/program-examples", { - "group": "Examples", - "expanded": true, + "group": "Error Cheatsheet", "pages": [ - "compressed-tokens/advanced-guides/example-token-distribution" + "resources/error-cheatsheet", + "resources/error-cheatsheet/debug-0x179b-6043-proofverificationfailed" ] } ] @@ -202,43 +127,36 @@ ] }, { - "group": "JSON RPC Methods", + "group": "Other Use Cases", "pages": [ - "api-reference/json-rpc-methods/overview", + "compressed-tokens/airdrop", + "compressed-pdas/solana-attestation-service", { - "group": "Methods", + "group": "ZK", "pages": [ - "api-reference/json-rpc-methods/methods", - "api-reference/json-rpc-methods/getcompressedaccount", - "api-reference/json-rpc-methods/getcompressedaccountsbyowner", - "api-reference/json-rpc-methods/getcompressedbalancebyowner", - "api-reference/json-rpc-methods/getcompressedbalance", - "api-reference/json-rpc-methods/getcompressedminttokenholders", - "api-reference/json-rpc-methods/getcompressedtokenaccountbalance", - "api-reference/json-rpc-methods/getcompressedtokenaccountbydelegate", - "api-reference/json-rpc-methods/getcompressedtokenaccountsbyowner", - "api-reference/json-rpc-methods/getcompressedtokenbalancesbyowner", - "api-reference/json-rpc-methods/getcompressionsignaturesforaccount", - "api-reference/json-rpc-methods/getcompressionsignaturesforaddress", - "api-reference/json-rpc-methods/getcompressionsignaturesforowner", - "api-reference/json-rpc-methods/getcompressionsignaturesfortokenowner", - "api-reference/json-rpc-methods/getindexerhealth", - "api-reference/json-rpc-methods/getindexerslot", - "api-reference/json-rpc-methods/getlatestcompressionsignatures", - "api-reference/json-rpc-methods/getlatestnonvotingsignatures", - "api-reference/json-rpc-methods/getmultiplecompressedaccounts", - "api-reference/json-rpc-methods/getmultiplenewaddressproofs", - "api-reference/json-rpc-methods/gettransactionwithcompressioninfo", - "api-reference/json-rpc-methods/getvalidityproof" + "zk/overview", + "zk/examples" ] } ] }, + { + "group": "Resources", + "pages": [ + "api-reference/sdk", + "resources/anchor-constraints", + "resources/addresses-and-urls", + "references/migration-v1-to-v2", + "resources/cli-installation" + ] + }, { "group": "Learn", "pages": [ + "learn/overview", + "learn/light-token-standard", { - "group": "Core Concepts", + "group": "Core Concepts ZK Compression", "pages": [ "learn/core-concepts", "learn/core-concepts/compressed-account-model", @@ -246,50 +164,78 @@ "learn/core-concepts/transaction-lifecycle", "learn/core-concepts/considerations" ] - }, - "learn/ai-tools-guide" + } ] }, { - "group": "Resources", + "group": "References", "pages": [ - "resources/cli-installation", - { - "group": "Error Cheatsheet", - "pages": [ - "resources/error-cheatsheet", - "resources/error-cheatsheet/debug-0x179b-6043-proofverificationfailed" - ] - }, + "compressed-tokens/overview", { - "group": "SDKs", + "group": "JSON RPC Methods", "pages": [ - "resources/sdks/client-development", - "resources/sdks/program-development" + "api-reference/json-rpc-methods/overview", + { + "group": "Methods", + "pages": [ + "api-reference/json-rpc-methods/getcompressedaccount", + "api-reference/json-rpc-methods/getcompressedaccountsbyowner", + "api-reference/json-rpc-methods/getcompressedbalancebyowner", + "api-reference/json-rpc-methods/getcompressedtokenaccountsbyowner", + "api-reference/json-rpc-methods/getcompressedtokenbalancesbyowner", + "api-reference/json-rpc-methods/getvalidityproof", + "api-reference/json-rpc-methods/getcompressionsignaturesforaddress", + "api-reference/json-rpc-methods/getcompressionsignaturesforowner", + "api-reference/json-rpc-methods/gettransactionwithcompressioninfo", + "api-reference/json-rpc-methods/getindexerhealth", + "api-reference/json-rpc-methods/getindexerslot" + ] + }, + { + "group": "Legacy", + "pages": [ + "api-reference/json-rpc-methods/getcompressedbalance", + "api-reference/json-rpc-methods/getcompressedminttokenholders", + "api-reference/json-rpc-methods/getcompressedtokenaccountbalance", + "api-reference/json-rpc-methods/getcompressedtokenaccountbydelegate", + "api-reference/json-rpc-methods/getcompressionsignaturesforaccount", + "api-reference/json-rpc-methods/getcompressionsignaturesfortokenowner", + "api-reference/json-rpc-methods/getlatestcompressionsignatures", + "api-reference/json-rpc-methods/getlatestnonvotingsignatures", + "api-reference/json-rpc-methods/getmultiplecompressedaccounts", + "api-reference/json-rpc-methods/getmultiplenewaddressproofs" + ] + } ] }, - "resources/addresses-and-urls" - ] - }, - { - "group": "References", - "pages": [ + "references/terminology", "references/whitepaper", "references/node-operators", - "references/terminology", - "references/migration-v1-to-v2", - "support", "references/security" ] - }, + } + ] + }, + { + "anchor": "AI Tools", + "icon": "robot", + "groups": [ { - "group": "SDK Reference", + "group": "AI Tools", "pages": [ - "api-reference/libraries/stateless-js", - "api-reference/libraries/compressed-token", - "api-reference/libraries/light-client", - "api-reference/libraries/light-sdk", - "api-reference/libraries/light-program-test" + "ai-tools/guide" + ] + } + ] + }, + { + "anchor": "Support", + "icon": "headset", + "groups": [ + { + "group": "Support", + "pages": [ + "support" ] } ] @@ -317,15 +263,9 @@ "navbar": { "links": [ { - "label": "Get Support", - "icon": "discord", - "href": "https://discord.gg/CYvjBgzRFP" - }, - { - "type": "button", "label": "GitHub", "icon": "github", - "href": "https://github.com/Lightprotocol/light-protocol" + "href": "https://github.com/Lightprotocol" } ] }, @@ -365,7 +305,23 @@ "redirects": [ { "source": "/landing", - "destination": "/welcome" + "destination": "/home" + }, + { + "source": "/welcome", + "destination": "/home" + }, + { + "source": "/learn/ai-tools-guide", + "destination": "/ai-tools/guide" + }, + { + "source": "/resources/sdks/client-development", + "destination": "/api-reference/sdk" + }, + { + "source": "/resources/sdks/program-development", + "destination": "/api-reference/sdk" } ] -} \ No newline at end of file +} diff --git a/home.mdx b/home.mdx index d3c276f3..8c220eb0 100644 --- a/home.mdx +++ b/home.mdx @@ -1,34 +1,28 @@ --- title: "Home" mode: "custom" -keywords: ["light protocol", "zk compression on solana", "scalable solana infrastructure", "rent free accounts on solana", "create tokens on solana", "scalable infrastructure for solana apps", "privacy on solana", "shielded transactions on solana"] +keywords: ["solana tokens", "spl tokens", "light protocol", "helius", "zk compression on solana", "scalable solana infrastructure", "rent free accounts on solana", "create tokens on solana", "scalable infrastructure for solana apps", "privacy on solana", "shielded transactions on solana"] --- import { Footer } from "/snippets/jsx/footer.jsx"; import { HeroCubeGrid } from "/snippets/jsx/hero-cube-grid.jsx"; import { LiquidGlassPill } from "/snippets/jsx/liquid-glass-pill.jsx"; -import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; +import { PartnerLogos } from "/snippets/jsx/partner-logos.jsx"; +import SdkReference from "/snippets/overview-tables/sdk-reference.mdx"; {/* Hero with animation - styles in style.css (no flash) */}
- + + +
{/* Content container - using Tailwind classes to avoid flash */}
- - - High performance token standard for rent-free DeFi and Payments. - - - Core primitives for rent-free tokens & accounts. - - - ## Features @@ -37,203 +31,138 @@ import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; Works with existing programs and accounts. -## Quickstart +## Light Token - - -### Installation - - - - -### Get started - +High-performance token standard that reduces the cost of mint and token accounts by 200x +while being more CU efficient than SPL on hot paths. - - + + Introduction to the Light Token program. + -## DeFi Integration +### DeFi - Build rent-free AMMs and DeFi programs with minimal code changes. + Build rent-free AMMs and DeFi programs. - Add support for rent-free AMMs to your aggregator or router. + Add support for rent-free AMMs to your aggregator. + + + High-performance DeFi programs with Pinocchio. -## Toolkits +### Payments - - - - Integrate light-token APIs with familiar SPL patterns. - - - - Allow users to display and swap light-tokens. - - - {/* - Stream mint events from the network in real-time. - */} - - {/* - Stream token events from the network in real-time. - */} - - - - - ZK Compression is the most efficient way to distribute SPL tokens. - - - Add rent-free SPL token accounts and transfers to your Privy embedded wallet. - - - Allow users to display and swap compressed tokens. - - - - Integrate Token-2022 extensions with compressed tokens. - - - - Common token distribution flows (airdrops, payments, rewards). - - - - - - Deterministically derived addresses to prevent double spending. - - - - Small proof size and fast verification. - - - - Use existing state Merkle trees with RPC indexer support. - - - - + + + Guide to integrate light-token APIs with comparison to SPL. + + + Prevent onchain instructions from being executed more than once. + + -## Cookbooks +### Wallets - - Program and client guides for mints, token accounts, and transfers. + + Guide for Wallet Applications to let users display and swap light-tokens. - - Client guides for compressed token accounts. + + Add rent-free tokens to Privy embedded wallets. - - Program and client guides for rent-free PDA accounts. + + +### Data Streaming + + + + Stream mint events from the network in real-time. + + + Stream token events from the network in real-time. + + + +### Guides and Examples + + + + Guides for mints, token accounts, and transfers. - - Build with compressed attestations using Rust or Typescript. + + TypeScript and Rust client examples. + + + Program examples for Light Token Anchor macros, instructions and Pinocchio. -## Using AI in Development +## PDA Accounts + + + + Solana PDAs with sponsored rent-exemption that can be implemented with minimal code changes. + Use like any other PDA, e.g. in your DeFi program. + + + Compressed PDAs to store user, app, or other infrequently accessed state without rent-exemption. Fully compatible with existing Solana programs, but requires custom logic. + + + +## Other Use Cases + + + + Distribute tokens on Solana 5000x cheaper. Merkle distributor and simple claim programs for airdrops with claim. + + + Credential system for KYC verification, professional badges, and compliance credentials in minutes. + + + Build ZK programs on Solana with groth-16 proofs and merklelized state. + + + +## AI Tools + + + + +Install orchestrator agent skill or view [skill.md](https://www.zkcompression.com/skill.md): -Download agent skill or view [skill.md](https://www.zkcompression.com/skill.md): ```bash npx skills add https://zkcompression.com ``` -| | | -| :------- | :--------------------------------------------------------------------------- | -| MCP | Connect AI tools to the Light Protocol repository via Model Context Protocol | -| DeepWiki | Use Deepwiki for advanced AI assistance with your development. | +Install or view dedicated agent skills: https://github.com/Lightprotocol/skills + + + +Add the marketplace and install: + +``` +/plugin marketplace add Lightprotocol/skills +/plugin install solana-rent-free-dev +``` + + +1. Open Settings (**Cmd+Shift+J** / **Ctrl+Shift+J**) +2. Navigate to **Rules & Commands** → **Project Rules** → **Add Rule** → **Remote Rule (GitHub)** +3. Enter: `https://github.com/Lightprotocol/skills.git` + + +``` +npx skills add Lightprotocol/skills +``` + + + + +Connect AI tools to the Light Protocol repository via MCP and DeepWiki. @@ -268,71 +197,37 @@ codex mcp add deepwiki -- npx -y mcp-remote@latest https://mcp.deepwiki.com/mcp ``` + + ## Resources - - - View security audits and formal verification. + + + Learn core concepts of the Light token program and ZK Compression Core. - - Learn core concepts of the Light token program. + + Syntax reference for #[light_account] constraints for PDA, mint, token, and associated token accounts. - - Learn core concepts of ZK Compression. + + RPC endpoints and addresses for programs, Merkle trees, and other accounts. - - -## SDK Reference - -### TypeScript - - - - Client SDK for Light Token and Compressed Accounts - - - Client for Light Token and Compressed Tokens. + + View security audits and formal verification. -### Rust +## SDK Reference - - - Rust client for Light Token and ZK Compression. - - - Core SDK for on-chain programs. - - - Testing framework for programs. - - +
diff --git a/images/helius-black.png b/images/helius-black.png new file mode 100644 index 00000000..af570ba6 Binary files /dev/null and b/images/helius-black.png differ diff --git a/images/helius-white.png b/images/helius-white.png new file mode 100644 index 00000000..155883f9 Binary files /dev/null and b/images/helius-white.png differ diff --git a/images/light-wordmark.svg b/images/light-wordmark.svg new file mode 100644 index 00000000..46f15005 --- /dev/null +++ b/images/light-wordmark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/learn/core-concepts/compressed-account-model.mdx b/learn/core-concepts/compressed-account-model.mdx index bb0355b3..51b5554f 100644 --- a/learn/core-concepts/compressed-account-model.mdx +++ b/learn/core-concepts/compressed-account-model.mdx @@ -20,6 +20,11 @@ Compressed accounts store state and are similar to regular Solana accounts but w These differences make compressed accounts rent-free and allow the protocol to store state as calldata on the Solana ledger instead of the costly on-chain account space. + +Compressed PDAs are compressed accounts with an address.
+Compressed Token accounts are compressed accounts that don't need an address. +
+ # In a Nutshell Transactions can use compressed account data inside Solana's virtual machine as if it were stored on-chain by combining state compression and zero-knowledge proofs: @@ -183,42 +188,6 @@ pub struct CompressedAccountData { - `data_hash`: Hash of the `data` field (32 bytes). - When computing the compressed account hash for the state tree, the protocol uses this fixed-size hash instead of the variable-length data bytes. -# Compressed Token Account - -Compressed token accounts store token balance, owner, and other information of token accounts (SPL, Token-2022, or Light). - -1. Any Solana token account can be compressed/decompressed at will. -2. We recommend to use compressed tokens for token distribution. - - - Learn how the Light token program uses ZK Compression under the hood - [here](/learn/light-token-standard). - - - - - - Diagram showing compressed token account structure with three components: Hash (identifier for compressed token account in purple box), Account (struct containing Data bytes, Executable flag, Lamports balance, and Address set to None), and AccountData (containing Mint, Owner, Amount, and Extensions fields marked as unimplemented) - - - - ```rust - pub struct TokenData { - pub mint: Pubkey, - pub owner: Pubkey, - pub amount: u64, - pub delegate: Option, - pub state: u8, - /// Placeholder for TokenExtension tlv data (unimplemented) - pub tlv: Option>, - } - ``` - - - # Next Steps @@ -149,7 +149,7 @@ Two **address tree versions** are currently supported: - **V1 address trees** height 26 (\~67 million addresses). - **V2 batched address trees** with height 40 (\~1 trillion addresses). -**V2** optimizes optimize compute unit consumption by up to 70% and is **currently on Devnet**. +**V2** optimizes compute unit consumption by up to 70%. When using V2 trees, RPC requests automatically choose the proof mechanism. diff --git a/learn/light-token-standard.mdx b/learn/light-token-standard.mdx index 3327c50b..5957e96c 100644 --- a/learn/light-token-standard.mdx +++ b/learn/light-token-standard.mdx @@ -1,6 +1,6 @@ --- title: Core Concepts to the Light Token Program (Beta) -sidebarTitle: Core Concepts (Light Token) +sidebarTitle: Core Concepts Light Token description: The Light Token Program is a high performance token program that reduces the cost of account creations by 200x, while being more CU efficient than SPL on hot paths. keywords: ["light token standard on solana", "token 2022 on solana", "token extensions on solana", "spl token on solana", "rent free tokens on solana"] --- diff --git a/learn/overview.mdx b/learn/overview.mdx new file mode 100644 index 00000000..baebc649 --- /dev/null +++ b/learn/overview.mdx @@ -0,0 +1,55 @@ +--- +title: Overview to Core Concepts +sidebarTitle: Overview +description: Learn about Light Token and ZK Compression Core. +keywords: ["zk compression on solana", "scalable solana infrastructure", "compressed accounts on solana", "privacy on solana", "zk on solana", "shielded transactions on solana"] +--- + + +## What is Light Token? + +Light token is a high-performance token standard that is functionally equivalent to SPL, but stores mint and token accounts more efficiently. This reduces account creation cost while being more CU efficient than SPL on hot paths. + + + + + + + + +## What is ZK Compression? + +ZK Compression is a Solana account primitive that lets you create tokens and PDAs without rent-exemption with L1 performance and security. +The Light Token Program uses ZK Compression under the hood. + +Storage cost of Solana accounts are reduced by combining generalized state compression and zero-knowledge proofs. + + + + State compression stores account data on the Solana ledger instead of in millions of on-chain accounts. Only a small fingerprint of all accounts is stored on-chain for cryptographic integrity. Using Light Protocol, programs can use the account data inside Solana's virtual machine as if it were stored on-chain. + + + + The protocol uses 128 byte zero-knowledge proofs (validity proofs) to verify the integrity of + the compressed accounts. By default, this is all done under the hood. You can fetch validity + proofs from RPC providers that support ZK Compression. + + + + \ No newline at end of file diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 7d55c284..326b9772 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -23,6 +23,7 @@ import InstructionCode from "/snippets/code-snippets/light-token/transfer-interf import RustActionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx"; +import CreateAndTransferCode from "/snippets/code-snippets/light-token/transfer-interface/anchor-program/create-and-transfer-example.mdx";
@@ -202,7 +203,10 @@ TransferInterfaceCpi::new( -# Full Code Example +# Full code example + + + View the [source code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/instruction/transfer_interface.rs) and [full example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/transfer-interface) with shared test utilities. @@ -212,6 +216,21 @@ TransferInterfaceCpi::new( + + +Uses the `#[light_account(init, associated_token::...)]` macro to create the destination ATA during the transfer. + + + View the [full example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/create-and-transfer) with test utilities. + + + + + + + + + # Next Steps diff --git a/light-token/defi/programs.mdx b/light-token/defi/programs.mdx index 719c5099..a2fb3948 100644 --- a/light-token/defi/programs.mdx +++ b/light-token/defi/programs.mdx @@ -34,11 +34,10 @@ You can find a complete rent-free AMM reference implementation [here](https://gi ```toml [dependencies] -light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] } -light-sdk-macros = { version = "0.18.0" } -light-token = { version = "0.3.0", features = ["anchor"] } - -light-anchor-spl = { version = "0.31" } # TokenInterface uses light_token::ID +light-sdk = { version = "0.19.0", features = ["anchor", "v2", "cpi-context"] } +light-sdk-macros = "0.19.0" +light-token = { version = "0.4.0", features = ["anchor"] } +light-anchor-spl = "0.31" # TokenInterface uses light_token::ID anchor-lang = "0.31" ``` diff --git a/light-token/defi/routers.mdx b/light-token/defi/routers.mdx index 2be65c93..9bca2778 100644 --- a/light-token/defi/routers.mdx +++ b/light-token/defi/routers.mdx @@ -137,7 +137,7 @@ pub trait LightProgramInterface { ```toml [dependencies] -light-client = {version = "0.18.0", features = ["v2"]} +light-client = { version = "0.19.0", features = ["v2"] } # AMM SDK that implements LightProgramInterface (provided by the AMM team) example-amm-sdk = "0.1" diff --git a/light-token/faq.mdx b/light-token/faq.mdx index 548c55e3..638ad500 100644 --- a/light-token/faq.mdx +++ b/light-token/faq.mdx @@ -134,6 +134,13 @@ Coming soon: DM us [@lightprotocol on X (Twitter)](http://x.com/lightprotocol) or [Discord](https://discord.com/invite/rpddh53TeG). + + + ### Get Started diff --git a/light-token/light-pda.mdx b/light-token/light-pda.mdx new file mode 100644 index 00000000..efa6395d --- /dev/null +++ b/light-token/light-pda.mdx @@ -0,0 +1,196 @@ +--- +title: "Light PDA" +description: "Create PDA accounts with sponsored rent-exemption for Anchor programs. Works with your existing Anchor implementations." +--- + +import FullCounterExample from '/snippets/code-snippets/light-token/counter/anchor-macro/full-example.mdx'; + +Light-PDAs are Solana PDAs with sponsored rent-exemption. +Programs create them using Anchor `#[account(init)]` with `#[light_account(init)]`. + +| | Regular PDA | Light-PDA | +|------------------|----------------------|-----------------| +| 100-byte account | ~1,600,000 lamports | ~11,500 lamports | + +## What changes + +| Area | Change | +|------|--------| +| State struct | Derive `LightAccount`, add `compression_info: CompressionInfo` | +| Accounts struct | Derive `LightAccounts`, add `#[light_account]` on init accounts | +| Program module | Add `#[light_program]` above `#[program]` | +| Instructions (reads, updates, closes) | No program changes. Client prepends a load instruction if account is cold. | + +Audit overhead is minimal as your program logic is mostly untouched. The rest is +macro-generated. + +--- + +## Step 1: Dependencies + +```toml +[dependencies] +light-account = { version = "0.20.0", features = ["anchor"] } +light-sdk = { version = "0.20.0", features = ["anchor", "v2", "cpi-context"] } +anchor-lang = "0.31" +``` + +## Step 2: State struct + +Add `compression_info` field and derive `LightAccount`: + +```rust +use light_account::{CompressionInfo, LightAccount}; + +#[derive(Default, Debug, InitSpace, LightAccount)] +#[account] +pub struct Counter { + /// Add this: + pub compression_info: CompressionInfo, + + pub owner: Pubkey, + pub count: u64, +} +``` + +## Step 3: Program module + +Add `#[light_program]` above `#[program]`. Define the CPI signer constant with your program ID: + +```rust +use light_account::{derive_light_cpi_signer, light_program, CpiSigner}; + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("YourProgramId11111111111111111111111111111111"); + +#[light_program] +#[program] +pub mod counter { + use super::*; + + pub fn create_counter<'info>( + ctx: Context<'_, '_, '_, 'info, CreateCounter<'info>>, + params: CreateCounterParams, + ) -> Result<()> { + ctx.accounts.counter.owner = ctx.accounts.owner.key(); + ctx.accounts.counter.count = params.count; + Ok(()) + } + + /// Standard Anchor — no Light-specific changes. + pub fn increment(ctx: Context) -> Result<()> { + ctx.accounts.counter.count = ctx.accounts.counter.count.checked_add(1).unwrap(); + Ok(()) + } + + /// Standard Anchor — no Light-specific changes. + pub fn close_counter(_ctx: Context) -> Result<()> { + Ok(()) + } +} +``` + +## Step 4: Accounts struct + +Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`. + +Only the `init` struct derives `LightAccounts`. The increment and close structs +are standard Anchor: + +```rust +use light_account::{CreateAccountsProof, LightAccounts}; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateCounterParams { + pub create_accounts_proof: CreateAccountsProof, + pub count: u64, +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateCounterParams)] +pub struct CreateCounter<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Read-only, used for PDA derivation. + pub owner: AccountInfo<'info>, + + /// CHECK: Validated by Light Protocol CPI. + pub compression_config: AccountInfo<'info>, + + /// CHECK: PDA rent sponsor for compression rent reimbursement. + #[account(mut)] + pub pda_rent_sponsor: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + space = 8 + ::INIT_SPACE, + seeds = [COUNTER_SEED, owner.key().as_ref()], + bump, + )] + #[light_account(init)] + pub counter: Account<'info, Counter>, + + pub system_program: Program<'info, System>, +} +``` + +## Full Example + + + + +View counter example on Github: [counter](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) + + +--- + +## How it works + +The SDK pays the rent-exemption cost. After extended inactivity, cold accounts +auto-compress. Your program only ever interacts with hot accounts. Clients can +safely load cold accounts back into the onchain Solana account space when needed +via `create_load_instructions`. + +| | Hot (active) | Cold (inactive) | +|---|---|---| +| Storage | On-chain | Compressed | +| Latency/CU | No change | +load instruction | +| Your program code | No change | No change | + +## FAQ + + When creating an +account for the first time, the SDK provides a proof that the account doesn't +exist in the cold address space. The SVM already verifies this for the onchain +space. Both address spaces are checked before creation, preventing re-init +attacks, even if the account is currently cold. + + +Miners (Forester nodes) compress accounts that have been inactive for an extended period of time (when their virtual rent balance drops below threshold). +In practice, having to load cold accounts should be rare. The common path (hot) has no extra overhead and does not increase CU or txn size. + + + +When accounts compress after extended inactivity, the on-chain rent-exemption is released back +to the rent sponsor. This creates a revolving lifecycle: active "hot" accounts hold a +rent-exempt lamports balance, inactive "cold" accounts release it back. The +rent sponsor must be derived from the program owner. For all mint, ATA, and +token accounts, the Light Token Program is the rent sponsor. For your own program-owned PDAs, the SDK derives a rent sponsor address automatically. + + + +**Hot path (e.g. reads, updates, closes):** No. Active accounts do not add CU overhead to your instructions. + +**First time init + loading cold accounts:** Yes, adds up to 15k-400k CU, +depending on number and type of accounts being initialized or loaded. + + +--- + + +API is in Beta and subject to change. + +Questions or need hands-on support? [Telegram](https://t.me/swen_light) | [email](mailto:support@lightprotocol.com) | [Discord](https://discord.com/invite/7cJ8BhAXhu) + diff --git a/light-token/pda-overview.mdx b/light-token/pda-overview.mdx new file mode 100644 index 00000000..3f845884 --- /dev/null +++ b/light-token/pda-overview.mdx @@ -0,0 +1,44 @@ +--- +title: "PDA accounts" +sidebarTitle: "Overview" +description: "Overview to Light-PDAs (Solana accounts with sponsored rent-exemption) and compressed PDAs (compressed accounts without rent-exemption)." +--- +import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; + +## Light-PDA + +Light-PDAs are Solana PDAs with sponsored rent-exemption created using Anchor `#[account(init)]` with `#[light_account(init)]`. +Can be implemented with minimal code changes to your existing Solana program and leaves program logic mostly untouched. + +| | Regular PDA | Light-PDA | +|------------------|----------------------|-----------------| +| 100-byte account | ~1,600,000 lamports | ~11,500 lamports | + + + + + +- Standard Anchor account type (`Account<'info, T>`) +- Reads, updates, and closes are unchanged +- Inactive accounts auto-compress; clients load them back when needed +- Use like any other PDA, e.g. in your DeFi program. + + + +--- + +## Compressed PDA + +Compressed PDAs are compressed accounts with an address. +Programs invoke the Light System program to create and update compressed accounts, instead of the System program. +Fully compatible with existing Solana programs, but requires custom logic. + +| | Regular PDA | Compressed PDA | +|------------------|----------------------|-----------------| +| 100-byte account | ~1,600,000 lamports | 15,000 lamports | + +- Derived using a program address and seed, like regular PDAs +- Compressed state requires a validity proof for every read and write (fetched by client from RPC that supports ZK Compression, such as Helius and Triton) +- Use for app and user state, or other accounts that are infrequently accessed + + \ No newline at end of file diff --git a/light-token/toolkits/for-payments.mdx b/light-token/toolkits/for-payments.mdx index 5877d295..69748e19 100644 --- a/light-token/toolkits/for-payments.mdx +++ b/light-token/toolkits/for-payments.mdx @@ -1,7 +1,7 @@ --- title: "Toolkit for Stablecoin Payments" -sidebarTitle: "For Stablecoin Payments" -description: "Guide to integrate light-token APIs with comparison to SPL." +sidebarTitle: "Integration Guide" +description: "Guide to integrate light-token APIs for stablecoin payments with comparison to SPL." keywords: ["stablecoin payments for enterprises", "infrastructure for stablecoin payments on solana", "payment rails for stablecoins on solana", "usdc payments for merchants on solana", "payment infrastructure for fintechs", "micropayments for apps on solana", "token extensions for payment companies", "rent free tokens for payment apps"] --- diff --git a/light-token/toolkits/for-wallets.mdx b/light-token/toolkits/for-wallets.mdx index 173b8470..e072b25c 100644 --- a/light-token/toolkits/for-wallets.mdx +++ b/light-token/toolkits/for-wallets.mdx @@ -1,6 +1,6 @@ --- -title: "Toolkit for Wallet Applications" -sidebarTitle: "For Wallets" +title: "Light Token Integration Guide for Wallet Applications" +sidebarTitle: "Integration Guide" description: "Guide for Wallet Applications to add Light-token support." keywords: ["wallet infrastructure on solana", "scalable wallet infrastructure on solana", "token wallet for developers", "rent free tokens for wallets"] --- diff --git a/light-token/welcome.mdx b/light-token/welcome.mdx index a2bbc397..8863edfb 100644 --- a/light-token/welcome.mdx +++ b/light-token/welcome.mdx @@ -5,18 +5,9 @@ description: Light token is a high-performance token standard that reduces the c keywords: ["rent free tokens on solana", "rent free solana accounts", "spl token alternative for developers", "token 2022 alternative for solana", "infrastructure for stablecoin payments on solana", "scalable solana infrastructure", "token infrastructure for fintechs", "scalable infrastructure for solana apps"] --- -import GuidesTable from "/snippets/overview-tables/compressed-tokens-guides-table.mdx"; -import AdvancedGuidesTable from "/snippets/overview-tables/compressed-tokens-advanced-guides-table.mdx"; -import SetupEnvironment from "/snippets/setup/setup-environment-tabs.mdx"; -import InstallDependencies from "/snippets/setup/install-dependencies-codegroup.mdx"; -import { LightTokenVsSplCalculator } from "/snippets/jsx/light-token-vs-spl-calculator.jsx"; -import { CompressibleRentCalculator } from "/snippets/jsx/compressible-rent-calculator.jsx"; -import { RentLifecycleVisualizer } from "/snippets/jsx/rent-lifecycle-visualizer.jsx"; -import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; -import CompressibleDefaultRentConfig from "/snippets/compressible-default-rent-config.mdx"; -import IntegrateLightTokenGuidesTable from "/snippets/overview-tables/integrate-light-token-guides-table.mdx"; import CookbookGuidesTable from "/snippets/overview-tables/cookbook-guides-table.mdx"; import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; +import SdkReference from "/snippets/overview-tables/sdk-reference.mdx"; **Note: This page is for the new Light Token Program Beta.** For production use today, use @@ -64,15 +55,13 @@ import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; ### Get Started - - - + @@ -80,21 +69,85 @@ import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; - Build rent-free AMMs and DeFi programs with minimal code changes. + Build rent-free AMMs and DeFi programs. - Add support for rent-free AMMs to your aggregator or router. + Add support for rent-free AMMs to your aggregator. + + + High-performance DeFi programs with Pinocchio. + + + +## Payments + + + + Guide to integrate light-token APIs with comparison to SPL. + + + Prevent onchain instructions from being executed more than once. + + + +## Wallets + + + + Guide for Wallet Applications to let users display and swap light-tokens. + + + Add rent-free tokens to Privy embedded wallets. -## Integration Toolkits +## Data Streaming - + + + Stream mint events from the network in real-time. + + + Stream token events from the network in real-time. + + + +## PDA Accounts + + + + Solana PDAs with sponsored rent-exemption that can be implemented with minimal code changes. + + + Syntax reference for #[light_account] constraints for PDA, mint, token, and associated token accounts. + + ## Cookbook +## Examples + +### Client + +| | | +| :--- | :--- | +| [**TypeScript**](/light-token/examples/client#typescript) | Actions and instructions for all token operations | +| [**Rust**](/light-token/examples/client#rust) | Actions and instructions for all token operations | + +### Program + +| | | +| :--- | :--- | +| [**Examples**](/light-token/examples/program#examples) | AMM reference, CPI create-and-transfer, Pinocchio swap | +| [**Macros**](/light-token/examples/program#macros) | Counter PDA, ATA, mint, and token account creation | +| [**Instructions**](/light-token/examples/program#instructions) | All CPI instructions: approve, burn, close, freeze, mint-to, transfer, and more | + +## SDK Reference + + + # Next Steps - **Source**: https://github.com/Lightprotocol/light-protocol/tree/main/programs/account-compression - +> **Program ID**: `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` | [Source](https://github.com/Lightprotocol/light-protocol/tree/main/programs/account-compression) + +## Account Compression Authority + +A PDA that signs CPI calls from the Light System Program to the Account Compression Program. Derived from the Light System Program ID with seed `b'cpi_authority'`. + +> **Address**: `HZH7qSLcpAeDqCopVU4e5XkhT9j3JFsQiq8CmruY3aru` + +## Agent skills + +Folders of instructions, scripts, and resources that AI agents can discover and use to build on Light Protocol more accurately and efficiently. +Light Protocol provides agent skills for rent-free Solana development: Light-PDA, token and mint accounts. +Skills for compressed PDAs and more are in development. + +| Use case | Skill | +|----------|-------| +| Build DeFi programs (AMMs, vaults, lending) with Anchor or Pinocchio | [defi-program](https://github.com/Lightprotocol/skills/tree/main/skills/defi-program) | +| Integrate rent-free markets into routers and aggregators | [defi-router](https://github.com/Lightprotocol/skills/tree/main/skills/defi-router) | +| Stream account state via Laserstream gRPC | [data-streaming](https://github.com/Lightprotocol/skills/tree/main/skills/data-streaming) | +| Wallets and payment flows with Light Token. Optional nullifier to prevent your on-chain instruction from being executed more than once. | [payments-and-wallets](https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets) | +| Airdrops, DePIN, token distribution | [airdrop](https://github.com/Lightprotocol/skills/tree/main/skills/airdrop) | +| Anti-double-spend nullifiers for privacy-preserving ZK programs | [zk-nullifier](https://github.com/Lightprotocol/skills/tree/main/skills/zk-nullifier) | +| Testing programs and clients on localnet, devnet, mainnet | [testing](https://github.com/Lightprotocol/skills/tree/main/skills/testing) | +| Help with debugging and questions via DeepWiki MCP | [ask-mcp](https://github.com/Lightprotocol/skills/tree/main/skills/ask-mcp) | + +Built on [Anthropic's Skills](https://github.com/anthropics/skills) and the [Agent Skills Specification](https://agentskills.io). + +> [Source Code](https://github.com/Lightprotocol/skills) | [Claude plugin](https://github.com/Lightprotocol/skills/tree/main/.claude-plugin) | [OpenClaw plugin](https://github.com/Lightprotocol/skills/blob/main/openclaw.plugin.json) ## Address tree @@ -32,59 +60,61 @@ Two address tree versions are currently supported: - **V1 address trees**: Height 26 (~67 million addresses) - **V2 batched address trees**: Height 40 (~1 trillion addresses), optimizing compute unit consumption by up to 70%. V2 is currently on Devnet. -## [Client](https://solana.com/docs/core/transactions#client) - -A computer program that accesses the Solana server network [cluster](https://solana.com/docs/core/transactions#cluster). +| Version | Address Tree | Address Queue | +|:--------|:-------------|:--------------| +| V2 | `amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx` | — | +| V1 (deprecated) | `amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2` | `aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F` | -## [**Cluster**](https://solana.com/docs/references/terminology#cluster) +## Address queue -A set of [validators](https://solana.com/docs/references/terminology#validator) maintaining a single [ledger](https://solana.com/docs/references/terminology#ledger). - -## Compressed PDA +A queue used in V1 address trees to buffer new addresses before Forester nodes insert them into the address tree. V2 address trees do not use a separate queue. -Compressed accounts at Program Derived Addresses. +> **Address (V1)**: `aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F` -## Compressed account +## [Client](https://solana.com/docs/core/transactions#client) -A data structure that holds arbitrary data, represented as a 32-byte hash stored in a leaf of a state Merkle tree. Compressed accounts do not require a rent exempt balance upon creation. +A computer program that accesses the Solana server network [cluster](https://solana.com/docs/core/transactions#cluster). -## Compressed Account hash +## [Cluster](https://solana.com/docs/references/terminology#cluster) -A 32-byte identifier uniquely that represents a compressed account's state, stored in a state tree. +A set of [validators](https://solana.com/docs/references/terminology#validator) maintaining a single [ledger](https://solana.com/docs/references/terminology#ledger). -## Token mint +## Cold account -An SPL token mint uniquely represents a token on the Solana network and stores global metadata about the token, including `mint_authority`, `supply`, and `decimals`. +A Light Token account, Light Token mint, or Light-PDA that has been compressed after extended inactivity. The account's state is cryptographically preserved on the Solana ledger as a compressed account. On-chain lookups return `is_initialized: false`. Clients call `create_load_instructions` to reinstate the account. -SPL tokens can be compressed if the mint has a token pool account set up. Anyone can create a token pool PDA for any given SPL mint. +## Compressible account -## Token Pool Account +A Solana account (Light Token account, Light Token mint, or Light-PDA) that automatically compresses when its rent balance drops below threshold. Forester nodes execute the compression. The account can be loaded back when accessed again. -SPL token account that holds SPL tokens corresponding to compressed tokens in circulation. Tokens are deposited during compression and withdrawn during decompression, owned by the Light Token Program's CPI authority PDA. +## Compressed account -## Concurrency +A data structure that holds arbitrary data, represented as a 32-byte hash stored in a leaf of a state Merkle tree. Compressed accounts do not require a rent exempt balance upon creation. -The ability to process multiple Merkle tree update requests simultaneously without invalidating each other, as long as they don't modify the same leaf. +## Compressed account hash -Concurrency in ZK Compression allows parallel operations on different tree leaves without requiring locks. +A 32-byte identifier that uniquely represents a compressed account's state, stored in a state tree. -## Compressed Token +## Compressed PDA -An SPL token in compressed form. Compressed tokens do not require an associated token account per holder. +Compressed accounts with an address stored in an address Merkle tree. +Full Solana PDA functionality and composability without rent-exemption. -## Compressed Token account +> [Docs](/compressed-pdas/overview) -An account type in the Light Token Program to store information about an individual's ownership of a specific token (mint). Compressed token accounts do not require a rent exempt balance upon creation. +## Compressed token -## Light Token Program +An SPL token stored as a compressed account hash in a state tree. Compressed tokens incur no rent and do not require a token account per holder. -Light Protocol's SPL-compatible token program that enables compression and decompression of token accounts. The program enforces SPL token layout standards and allows for arbitrary transitions between compressed and regular format. +> [Docs](/compressed-tokens/overview) -## [**Compute units**](https://solana.com/docs/references/terminology#compute-units) +## Compressed token account -The smallest unit of measure for consumption of computational resources of the transactions on the Solana blockchain. +A compressed account that stores a token balance for a specific mint. +Compressed token accounts do not require a rent exempt balance upon creation. +Compressed token accounts are compressed accounts that do not need an address. -## [**Compute unit budget**](https://solana.com/docs/references/terminology#compute-budget) +## [Compute unit budget](https://solana.com/docs/references/terminology#compute-budget) The maximum number of [compute units](https://solana.com/docs/references/terminology#compute-units) consumed per transaction. @@ -98,15 +128,29 @@ The total amount of compute units that all transactions in a single Solana block The maximum compute units that can be used to modify a single account within a block - the _write lock limit -_ is currently set at 12 million CU. +## [Compute units](https://solana.com/docs/references/terminology#compute-units) + +The smallest unit of measure for consumption of computational resources of the transactions on the Solana blockchain. + ## [Cross-program invocation (CPI)](https://solana.com/docs/core/transactions#cross-program-invocation-cpi) A call from one [program](https://solana.com/docs/core/transactions#onchain-program) to another. For more information, see [calling between programs](https://solana.com/docs/core/cpi). +## CPI context account + +An account passed in transactions that stores intermediate CPI state for compressed account operations. Each state tree has a dedicated CPI context account. + +Addresses are listed under the state tree entry. + +## CPI signer + +A PDA that signs CPI calls from your program to the Light System Program. Verified by the Light System Program during CPI. Derived from your program ID using `derive_light_cpi_signer`. + ## Decompression -The process of converting a compressed to a regular Solana account. SPL tokens are withdrawn from the token pool to an Associated Token Account and compressed token accounts are invalidated. +The process of converting a compressed account to a regular Solana account. For token decompression, SPL tokens are released from the SPL interface PDA to an associated token account, and the compressed token account is invalidated. ## Forester node / Forester @@ -118,19 +162,23 @@ A zero-knowledge SNARK that produces constant-size proofs using bilinear pairing ZK Compression uses Groth16 to generate 128 byte validity proofs to verify compressed account state transitions against the on-chain root. -## [**Hash**](https://solana.com/docs/references/terminology#hash) +## [Hash](https://solana.com/docs/references/terminology#hash) A hash is a digital fingerprint of a sequence of bytes representing arbitrary data, while requiring far less storage space than the original data. +## Hot account + +A Light Token account, Light Token mint, or Light-PDA that is active on-chain with a rent-exempt balance. Programs interact only with hot accounts. Clients call `create_load_instructions` to reinstate cold accounts before use. + ## Indexer -A service that tracks state changes of compressed accounts on the Solana ledger and provides RPC APIs for querying compressed accounts and generating validity proofs. +A service that tracks state changes of compressed accounts on the Solana ledger. The indexer exposes RPC APIs for querying compressed accounts and generating validity proofs. The ZK Compression indexer is named Photon and is maintained by Helius Labs. **Source**: https://github.com/helius-labs/photon -## [**Instruction**](https://solana.com/docs/references/terminology#instruction) +## [Instruction](https://solana.com/docs/references/terminology#instruction) A call to invoke a specific [instruction handler](https://solana.com/docs/references/terminology#instruction-handler) in a [program](https://solana.com/docs/references/terminology#program). @@ -142,32 +190,78 @@ For example, compressed accounts are created or updated with the `InvokeCpiInstr The numerical position (u32) of a compressed account within a state tree, used for Merkle proof generation. +## Ledger + +The ledger is an immutable historical record of all Solana transactions signed by clients since the genesis block. + +A helpful analogy to differentiate Solana ledger and state: + +- Ledger is the entire bank statement history. +- State is the current account balance, derived from all transactions in the bank statement history. + +## Light-PDA + +A Solana PDA with sponsored rent-exemption. Programs create Light-PDAs using Anchor `#[account(init)]` with `#[light_account(init)]`. Compresses automatically after extended inactivity; clients load the account back when it's accessed again. Program logic does not change for reads, updates, or closes. Only the init instruction and state struct require additions. + +> [Docs](/light-token/light-pda) | [Overview](/light-token/pda-overview) | [Example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) + ## Light System Program ZK Compression's core program that validates compressed account state transitions by verifying validity proofs and managing compressed state changes. The program enforces compressed account layout with ownership and sum checks, and is invoked to create and write to compressed accounts and PDAs. -**Source**: https://github.com/Lightprotocol/light-protocol/tree/main/programs/system +> **Program ID**: `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` | [Source](https://github.com/Lightprotocol/light-protocol/tree/main/programs/system) -## **Ledger** +## Light Token -The ledger is an immutable historical record of all Solana transactions signed by clients since the genesis block. +A token standard that reduces the cost of mint and token accounts by 200x compared to SPL. Functionally equivalent to SPL but stores accounts more efficiently. The Light Token SDK API is a superset of the SPL-token API. -A helpful analogy to differentiate Solana ledger and state: +Light Token accounts exist in two states: hot (active on-chain) and cold (compressed after inactivity). The Light Token Program sponsors rent-exemption for account creation. -- Ledger is the entire bank statement history. -- State is the current account balance, derived from all transactions in the bank statement history. +Different from ZK Compression, interactions with Light Token accounts do not require a validity proof for interactions. +ZK Compression is used under the hood for inactive token accounts (cold state). -## Merkle tree +> [Docs](/light-token/welcome) | [Example](https://github.com/Lightprotocol/examples-light-token) -A tree data structure to allow for cryptographic verification of the integrity of all leaves in a tree. +## Light Token account -Each leaf on a Merkle tree is a hash of that leaf's data. A Merkle tree compresses data by hashing pairs of data repeatedly into a single root hash, starting from the lowest level. Only this root hash is stored on chain. On Solana, this process is called state compression. +A Solana account that holds a token balance for a specific mint (SPL, Token 2022, or Light Token). The Light Token Program sponsors the rent-exemption cost. Follows the same layout as SPL token accounts. Compresses automatically after extended inactivity and can be loaded back when accessed. -## Merkle tree account +> [Docs](/light-token/cookbook/create-token-account) | [Example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-token-account) -The public key of the on-chain Merkle tree account used in ZK Compression. This identifier references the state tree that stores compressed account hashes. +## Light Token associated token account + +An associated token account created by the Light Token Program. The address is deterministically derived from the owner's address, Light Token Program ID, and mint address. Holds token balances of SPL, Token 2022, or Light Token mints with sponsored rent-exemption. + +> [Docs](/light-token/cookbook/create-ata) | [Example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-associated-token-account) + +## Light Token mint + +An on-chain mint account owned by the Light Token Program. Functionally equivalent to an SPL mint but with sponsored rent-exemption. Stores a compressed address in an address Merkle tree at creation to preserve mint state when the account compresses. + +> [Docs](/light-token/cookbook/create-mint) | [Example](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-mint) | [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/instruction/create_mint.rs) + +## Light Token Program + +Light Protocol's SPL-compatible token program. The Light Token Program compresses and decompresses token accounts, sponsors rent-exemption, and enforces SPL token layout standards. + +> **Program ID**: `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` | [Source](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token) + +## Load associated token account + +Reinstates a compressed account back to active on-chain state. `loadAta` unifies token balances from compressed tokens, SPL, and Token 2022 into a single Light Token associated token account. Returns `null` if there's nothing to load (idempotent). Creates the associated token account if it doesn't exist. + +> [Docs](/light-token/cookbook/load-ata) | [Source Code](https://github.com/Lightprotocol/light-protocol/blob/0c4e2417b2df2d564721b89e18d1aad3665120e7/js/compressed-token/src/v3/actions/load-ata.ts) + +## Lookup table + +A Solana [address lookup table](https://solana.com/docs/advanced/lookup-tables) that reduces transaction size by referencing accounts by index instead of full public key. Light Protocol provides pre-initialized lookup tables covering program IDs and protocol accounts. + +| Network | Address | +|:--------|:--------| +| Mainnet | `9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ` | +| Devnet | `qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V` | ## Merkle proof @@ -175,57 +269,98 @@ A cryptographic proof consisting of sibling node hashes required to verify that ZK Compression encodes Merkle proofs into zero-knowledge proofs (validity proofs). These verify compressed account operations with a constant 128-byte size without exposing the underlying variable-size Merkle proof data. +## Merkle tree + +A tree data structure to allow for cryptographic verification of the integrity of all leaves in a tree. + +Each leaf on a Merkle tree is a hash of that leaf's data. A Merkle tree compresses data by hashing pairs of data repeatedly into a single root hash, starting from the lowest level. Only this root hash is stored on chain. On Solana, this process is called state compression. + +## Merkle tree account + +The public key of the on-chain Merkle tree account used in ZK Compression. This identifier references the state tree that stores compressed account hashes. + ## Nullification The process of marking compressed accounts as spent to prevent double-spending. When compressed accounts are used as inputs in transactions, their previous states are invalidated by inserting their hashes into nullifier queues. Forester nodes process these queues to permanently update the corresponding Merkle tree leaves, ensuring each compressed account state can only be used once. +## Nullifier + +A nullifier is a unique identifier used to prevent an event from being executed more than once. It acts as a one-way, irreversible proof that a specific resource (such as a payment, note, token, or vote) has already been consumed. To implement nullifiers we need a data structure that ensures every nullifier is only created once and never deleted. On Solana a straight forward way to implement nullifiers is to create a PDA account with the nullifier as seed. + +The documentation provides two implementations with rent-free PDA accounts: + +**Nullifier PDA** — For use cases such as sending payments, where you want to prevent your on-chain instruction from being executed more than once. The nullifier program utility creates a rent-free PDA derived from id. If the id has been used before, the PDA already exists, causing the instruction to fail. + +| | | +|---|---| +| **Program ID** | `NFLx5WGPrTHHvdRNsidcrNcLxRruMC92E4yv7zhZBoT` | +| **Source code** | [github.com/Lightprotocol/nullifier-program](https://github.com/Lightprotocol/nullifier-program/) | +| **Rust SDK** | [light-nullifier-program](https://crates.io/crates/light-nullifier-program) | +| **TypeScript SDK** | [@lightprotocol/nullifier-program](https://www.npmjs.com/package/@lightprotocol/nullifier-program) | + +> [Docs](/compressed-pdas/guides/how-to-create-nullifier-pdas) | [Skill](https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets) | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/create_nullifier.rs) + +**ZK nullifier** — For ZK or privacy-preserving programs on Solana to prevent double-spending. Can be integrated with minimal code changes. + +> [Docs](/zk/overview) | [Skill](https://github.com/Lightprotocol/skills/tree/main/skills/zk-nullifier) | [Example](https://github.com/Lightprotocol/program-examples/tree/main/zk/zk-nullifier) + ## Nullifier queue A queue where compressed accounts hashes used as input for transactions are temporarily stored to prevent double spending. A Forester node empties the queue by inserting queue elements into a state Merkle tree. -## [**Program**](https://solana.com/docs/references/terminology#onchain-program) +## Output queue -Programs run executable code similar to smart contracts on other blockchains with optimizations specific to Solana. +A queue in V2 state trees that buffers compressed account state changes before Forester nodes incorporate them into the state tree. Replaces the nullifier queue used in V1 state trees. -Solana programs key characteristics include: - -- Solana programs are stateless and do not store state internally. Separate accounts store state for programs to execute on, such as program, user or token data. This makes Solana's account model [different from Ethereum's](https://solana.com/news/evm-to-svm). -- Programs are typically written in Rust. -- Programs interpret the [instructions](https://solana.com/docs/references/terminology#instruction) sent inside of each [transaction](https://solana.com/docs/references/terminology#transaction) to read and modify accounts over which it has control, hence update state. +Addresses are listed under the state tree entry. ## Parallelism The ability of the SVM to execute multiple transactions simultaneously, as long as they modify different regular and/or compressed accounts. -## [**Program derived addresses (PDA)**](https://solana.com/docs/references/terminology#program-derived-account-pda) +## Poseidon hash -PDAs are special account addresses derived deterministically using optional seeds, a bump seed, and a program ID. +A cryptographic hash function optimized for zero-knowledge proof systems that works natively with finite field arithmetic. -They are off the Ed25519 curve, meaning they have no private key. The PDA itself, once derived, is 32 bytes, matching a regular public key. +The Poseidon hash is designed to minimize computational complexity in ZK circuits. ZK Compression uses Poseidon hashes to generate the account hashes stored as leaves in state trees. -## [**Prioritization fee**](https://solana.com/docs/references/terminology#prioritization-fee) +## [Prioritization fee](https://solana.com/docs/references/terminology#prioritization-fee) An additional fee user can specify in the compute budget [instruction](https://solana.com/docs/references/terminology#instruction) to prioritize their [transactions](https://solana.com/docs/references/terminology#transaction). The priority fee is derived from the compute unit limit and the compute unit price. The price per compute unit set by the user in micro-lamports (1 lamport = 1,000,000 micro-lamports), rounded up to the nearest lamport. -## Poseidon hash +## [Program](https://solana.com/docs/references/terminology#onchain-program) -A cryptographic hash function optimized for zero-knowledge proof systems that works natively with finite field arithmetic. +Programs run executable code similar to smart contracts on other blockchains with optimizations specific to Solana. -The Poseidon hash is designed to minimize computational complexity in ZK circuits. ZK Compression uses Poseidon hashes to generate the account hashes stored as leaves in state trees. +Solana programs key characteristics include: + +- Solana programs are stateless and do not store state internally. Separate accounts store state for programs to execute on, such as program, user or token data. This makes Solana's account model [different from Ethereum's](https://solana.com/news/evm-to-svm). +- Programs are typically written in Rust. +- Programs interpret the [instructions](https://solana.com/docs/references/terminology#instruction) sent inside of each [transaction](https://solana.com/docs/references/terminology#transaction) to read and modify accounts over which it has control, hence update state. + +## [Program derived addresses (PDA)](https://solana.com/docs/references/terminology#program-derived-account-pda) + +PDAs are special account addresses derived deterministically using optional seeds, a bump seed, and a program ID. + +They are off the Ed25519 curve, meaning they have no private key. The PDA itself, once derived, is 32 bytes, matching a regular public key. ## Proof verification The on-chain process of validating zero-knowledge proofs to confirm the correctness of compressed account state transitions. +## Registered Program PDA + +A PDA that grants a program access control to the Account Compression Program. Required as an account in transactions that modify compressed state. + ## Rent A fee paid in SOL for the creation of [Accounts](https://solana.com/docs/references/terminology#account) to store data on the blockchain, tied to account size. When accounts do not have enough balance to pay rent, they may be Garbage Collected. -## [**Rent exempt**](https://solana.com/docs/references/terminology#rent-exempt) +## [Rent exempt](https://solana.com/docs/references/terminology#rent-exempt) An account that maintains a minimum lamport balance proportional to the amount of data stored on the account. @@ -239,6 +374,14 @@ The minimum balance is paid by the creator and is calculated as follows: Minimum Rent Balance = 2 × 0.00000348 SOL/byte/year × Account Size (Bytes) ``` +## Rent sponsorship + + + + + + + ## Remote Procedure Calls (RPC) A bridge between users (or applications) and the blockchain to facilitate interactions and data retrieval. @@ -250,22 +393,28 @@ The [ZK Compression RPC API](https://www.zkcompression.com/developers/json-rpc-m here](/api-reference/json-rpc-methods/overview). -## [**Smart contract**](https://solana.com/docs/references/terminology#smart-contract) +## [Smart contract](https://solana.com/docs/references/terminology#smart-contract) Smart contracts on Solana are called programs with key characteristics and optimizations. -## Solana Account Model +## Solana account model The native framework to store and manage data on the Solana blockchain. -Solana's Account Model separates program logic from state to optimize for parallel and faster transactions. Separate accounts store state for programs to execute on, such as program, user or token data. This makes Solana's Account Model [different from Ethereum's](https://solana.com/news/evm-to-svm). +Solana's account model separates program logic from state to optimize for parallel and faster transactions. Separate accounts store state for programs to execute on, such as program, user or token data. This makes Solana's account model [different from Ethereum's](https://solana.com/news/evm-to-svm). -ZK Compression extends Solana's Account Model with Compressed Accounts. +ZK Compression extends Solana's account model with compressed accounts. -## [**Solana Program Library (SPL)**](https://solana.com/docs/references/terminology#solana-program-library-spl) +## [Solana Program Library (SPL)](https://solana.com/docs/references/terminology#solana-program-library-spl) A [library of programs](https://spl.solana.com/) on Solana such as spl-token that facilitates tasks such as creating and using tokens. +## SPL interface PDA + +A PDA owned by the Light Token Program's CPI authority that holds SPL tokens corresponding to compressed tokens and Light Token balances in circulation. Each SPL interface PDA is associated with a specific mint. + +> **Address**: `GXtd2izAiMJPwMEjfgTRH3d7k9mjn4Jq3JrWFv9gySYy` + ## State A snapshot representing the current status of all accounts and programs on Solana. @@ -274,6 +423,23 @@ The state is derived from the ledger by sequentially applying every transaction. State is kept in RAM by validators for transaction validation. +## State compression + +A process to lower the amount of data stored on chain using Merkle trees. + +The process of state compression involves the following steps: + +1. Millions of accounts are compressed into a 'fingerprint' - the Merkle tree root hash +2. This 'fingerprint' is stored in one Solana account +3. The account history is stored on the Solana ledger +4. The latest compressed data is fetched from an indexer +5. To verify the data, recompute the hashes and compare the final hash to the on chain root hash + + + Learn more [on generalized state compression + here](https://solana.com/developers/courses/state-compression/generalized-state-compression). + + ## State root The root hash of a Merkle tree that serves as a cryptographic fingerprint representing all compressed accounts in the tree. @@ -291,34 +457,26 @@ Two state tree versions with different proof mechanisms are currently supported: - **V1 state trees**: Always require the full 128-byte validity proof. With a depth of 26, a single V1 state tree stores approximately 67 million compressed accounts. - **V2 batched state trees**: Support `prove_by_index` optimization that verifies account existence with one byte instead of 128 bytes, optimizing compute unit consumption by up to 70%. -## State compression - -A process to lower the amount of data stored on chain using Merkle trees. - -The process of state compression involves the following steps: - -1. Millions of accounts are compressed into a 'fingerprint' - the Merkle tree root hash -2. This 'fingerprint' is stored in one Solana account -3. The account history is stored on the Solana ledger -4. The latest compressed data is fetched from an indexer -5. To verify the data, recompute the hashes and compare the final hash to the on chain root hash - - - Learn more [on generalized state compression - here](https://solana.com/developers/courses/state-compression/generalized-state-compression). - +| Instance | State Tree | Output Queue | CPI Context | +|:---------|:-----------|:-------------|:------------| +| V2 #1 | `bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU` | `oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto` | `cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y` | +| V2 #2 | `bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi` | `oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg` | `cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B` | +| V2 #3 | `bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb` | `oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ` | `cpi3mbwMpSX8FAGMZVP85AwxqCaQMfEk9Em1v8QK9Rf` | +| V2 #4 | `bmt4d3p1a4YQgk9PeZv5s4DBUmbF5NxqYpk9HGjQsd8` | `oq4ypwvVGzCUMoiKKHWh4S1SgZJ9vCvKpcz6RT6A8dq` | `cpi4yyPDc4bCgHAnsenunGA8Y77j3XEDyjgfyCKgcoc` | +| V2 #5 | `bmt5yU97jC88YXTuSukYHa8Z5Bi2ZDUtmzfkDTA2mG2` | `oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P` | `cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6` | +| V1 | `smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho` | `nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X` | `cpi2cdhkH5roePvcudTgUL8ppEBfTay1desGh8G8QxK` | ## Token account A token account is an account type in Solana's Token Programs that stores information about an individual's ownership of a specific token (mint). Each token account is associated with a single mint and tracks details like the token balance and owner. -## [**Token mint**](https://solana.com/docs/references/terminology#token-mint) +## [Token mint](https://solana.com/docs/references/terminology#token-mint) A [mint account](https://solana.com/docs/tokens/basics/create-mint) is an account type in Solana's Token Programs that can produce (or 'mint') tokens. -Different tokens are distinguished by their unique token mint addresses. Token mints uniquely represents a token on the network and stores global metadata about the token, including the `mint_authority`, supply, and decimals. +Different tokens are distinguished by their unique token mint addresses. Token mints uniquely represent a token on the network and store global metadata about the token, including the `mint_authority`, supply, and decimals. SPL tokens can be compressed if the mint has an SPL interface PDA. -## [**Transaction**](https://solana.com/docs/references/terminology#transaction) +## [Transaction](https://solana.com/docs/references/terminology#transaction) One or more [instructions](https://solana.com/docs/references/terminology#instruction) signed by a [client](https://solana.com/docs/references/terminology#client) using one or more [keypairs](https://solana.com/docs/references/terminology#keypair) and executed atomically with only two possible outcomes: success or failure. @@ -332,7 +490,13 @@ The _validity proof_ is - constant 128 byte in size (other than Merkle proofs with varying proof size), fitting well in Solana's 1232 byte transaction limit - verified against the respective on chain fingerprint to ensure the provided data was previously emitted -- provided and generated by indexers that support the [ZK Compression RPC API](https://www.zkcompression.com/developers/json-rpc-methods) which extend Solana's [JSON RPC API](https://solana.com/docs/rpc) to interact with compressed accounts. . +- provided and generated by indexers that support the [ZK Compression RPC API](https://www.zkcompression.com/developers/json-rpc-methods) which extend Solana's [JSON RPC API](https://solana.com/docs/rpc) to interact with compressed accounts. + +## Wrap and Unwrap SPL, Token-2022, and Light Token Accounts + +**Wrap** moves tokens from an SPL or Token 2022 account into a Light Token associated token account. **Unwrap** moves tokens from a Light Token associated token account back to an SPL or Token 2022 account. Use wrap/unwrap to interact with applications that only support SPL or Token 2022. + +> [Docs](/light-token/cookbook/wrap-unwrap) | [Source Code](https://github.com/Lightprotocol/light-protocol/blob/0c4e2417b2df2d564721b89e18d1aad3665120e7/js/compressed-token/src/v3/actions/wrap.ts) ## Zero-knowledge proof (ZKP) @@ -343,12 +507,6 @@ ZK Compression uses a Groth16 SNARK zk proof - for its constant _validity_ proof size, to ensures the integrity of many compressed accounts, not for private or confidential transactions, and - to store data in zk friendly data structures. Applications on Solana can prove custom off chain computations over zk compressed state (native zk compute). -## ZK-SNARK - -Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge, a cryptographic proof system that enables proving knowledge of information without revealing the information itself. - -zk-SNARKs produce constant-size proofs that can be verified efficiently without interaction between prover and verifier. ZK Compression uses the Groth16 zk-SNARK construction to generate validity proofs for compressed account state transitions. - ## ZK Compression A generalized compression framework to compress and verify arbitrary data with zero-knowledge proofs, to @@ -356,3 +514,12 @@ A generalized compression framework to compress and verify arbitrary data with z - enable the Compressed Account Model, the rent-free equivalent to Solana's Account Model, - solve Solana's state growth problem, and - build a foundation for native zk compute. + +The Light Token program uses ZK Compression under the hood for inactive token accounts (cold state). +A Light Token account, Light Token mint, or Light-PDA that has been compressed after extended inactivity. The account’s state is cryptographically preserved on the Solana ledger as a compressed account. On-chain lookups return is_initialized: false. Clients call create_load_instructions to reinstate the account. + +## ZK-SNARK + +Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge. A cryptographic proof system where a prover demonstrates knowledge of information without revealing the information itself. + +zk-SNARKs produce constant-size proofs that can be verified efficiently without interaction between prover and verifier. ZK Compression uses the Groth16 zk-SNARK construction to generate validity proofs for compressed account state transitions. diff --git a/resources/addresses-and-urls.mdx b/resources/addresses-and-urls.mdx index c7f3308e..e71c08bb 100644 --- a/resources/addresses-and-urls.mdx +++ b/resources/addresses-and-urls.mdx @@ -174,13 +174,12 @@ main(); # Next Steps -Start building with Compressed Tokens or PDAs ::INIT_SPACE, + seeds = [COUNTER_SEED, owner.key().as_ref()], + bump, +)] +#[light_account(init)] // ← Light: register for rent-free creation +pub counter: Account<'info, Counter>, +``` + +Examples: [Github](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) + +--- + +### `#[light_account(init, token::...)]` + +Creates a rent-free token account via CPI to the Light Token program. The account type is `UncheckedAccount` because Light Token program initializes it via CPI. + +**attribute** + +``` +#[light_account(init, + token::authority = , + token::mint = , + token::owner = , + token::bump = +)] +``` + +```rust +#[account( + mut, + seeds = [VAULT_SEED, mint.key().as_ref()], + bump, +)] +#[light_account(init, + token::authority = [VAULT_SEED, self.mint.key()], + token::mint = mint, + token::owner = vault_authority, + token::bump = params.vault_bump +)] +pub vault: UncheckedAccount<'info>, +``` + +Examples: [Github](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account) + +--- + +### `#[light_account(init, associated_token::...)]` + +Creates a rent-free associated token account via CPI. + +**attribute** + +``` +#[light_account(init, + associated_token::authority = , + associated_token::mint = , + associated_token::bump = +)] +``` + +```rust +#[account(mut)] +#[light_account(init, + associated_token::authority = associated_token_account_owner, + associated_token::mint = associated_token_account_mint, + associated_token::bump = params.associated_token_account_bump +)] +pub associated_token_account: UncheckedAccount<'info>, +``` + +Examples: [Github](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-associated-token-account) + +--- + +### `#[light_account(init, mint::...)]` + +Creates a rent-free mint via CPI. + +**attribute (required)** + +``` +#[light_account(init, mint, + mint::signer = , + mint::authority = , + mint::decimals = , + mint::seeds = , +)] +``` + +**attribute (optional)** + +``` +mint::bump = +mint::freeze_authority = +mint::authority_seeds = +mint::name = // requires symbol + uri +mint::symbol = // requires name + uri +mint::uri = // requires name + symbol +mint::update_authority = // requires name, symbol, uri +mint::additional_metadata = // requires name, symbol, uri +``` + +```rust +#[account(mut)] +#[light_account(init, mint, + mint::signer = mint_signer, + mint::authority = fee_payer, + mint::decimals = 9, + mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], +)] +pub mint: UncheckedAccount<'info>, +``` + + + +```rust +#[account(mut)] +#[light_account(init, mint, + mint::signer = mint_signer, + mint::authority = fee_payer, + mint::decimals = 9, + mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump, + mint::name = params.name.clone(), + mint::symbol = params.symbol.clone(), + mint::uri = params.uri.clone(), + mint::update_authority = authority, + mint::additional_metadata = params.additional_metadata.clone() +)] +pub mint: UncheckedAccount<'info>, +``` + + + +Examples: [Github](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint) + +--- + +## Required accounts + +Add these to your `init` struct alongside the fields above. The `LightAccounts` derive macro appends additional protocol accounts to the instruction at runtime — you don't add those manually. + + + + +| Account | Description | +|:-|:-| +| `compression_config` | Per-program PDA. Stores rent sponsor address and compression settings. Initialize once via `InitializeRentFreeConfig`. | +| `pda_rent_sponsor` | Per-program PDA. Receives rent-exemption lamports when accounts compress. Must be mutable. | +| `system_program` | Solana System Program. Transfers lamports for rent-exemption. | + + + + +| Account | Description | +|:-|:-| +| `light_token_compressible_config` | Protocol PDA. Stores rent-config for Light Token accounts. | +| `light_token_rent_sponsor` | Protocol PDA. Holds the lamport pool that sponsors rent-exemption. Reclaims lamports when accounts compress. | +| `light_token_program` | Light Token program. CPI target for token, ATA, and mint creation. | +| `light_token_cpi_authority` | Signer PDA. Authorizes CPI calls from your program to the Light Token program. | +| `system_program` | Solana System Program. Transfers lamports for rent-exemption. | + + + \ No newline at end of file diff --git a/resources/error-cheatsheet.mdx b/resources/error-cheatsheet.mdx index 46a84d90..30338b04 100644 --- a/resources/error-cheatsheet.mdx +++ b/resources/error-cheatsheet.mdx @@ -16,7 +16,7 @@ description: "Complete error code reference for ZK Compression. Search error cod
- For help with debugging use the [MCP Configuration](/learn/ai-tools-guide#mcp) or AskDevin via + For help with debugging use the [MCP Configuration](/ai-tools/guide#mcp) or AskDevin via
- For help with debugging use the [MCP Configuration](/learn/ai-tools-guide#mcp) or AskDevin via + For help with debugging use the [MCP Configuration](/ai-tools/guide#mcp) or AskDevin via Ask DeepWiki diff --git a/skill.md b/skill.md index 4fa9a22f..4c8c9582 100644 --- a/skill.md +++ b/skill.md @@ -90,14 +90,14 @@ npx skills add https://zkcompression.com | Use case | Skill | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -| Build DeFi programs (AMMs, vaults, lending) with Anchor or Pinocchio | [defi-program](ai-tools/skills/defi-program/) | -| Integrate rent-free markets into routers and aggregators | [defi-router](ai-tools/skills/defi-router/) | -| Stream account state via Laserstream gRPC | [data-streaming](ai-tools/skills/data-streaming/) | -| Wallets and payment flows with light-token. Includes privy, wallet adapter, mobile wallet adapter signing. Optional nullifier to prevent your onchain instruction from being executed more than once. | [payments-and-wallets](ai-tools/skills/payments-and-wallets/) | -| Airdrops, DePIN, token distribution | [airdrop](ai-tools/skills/airdrop/) | -| Anti-double-spend nullifiers for Privacy-preserving ZK programs | [zk-nullifier](ai-tools/skills/zk-nullifier/) | -| Testing programs and clients on localnet, devnet, mainnet | [testing](ai-tools/skills/testing/) | -| Help with Debugging and Questions via DeepWiki MCP | [ask-mcp](ai-tools/skills/ask-mcp/) | +| Build DeFi programs (AMMs, vaults, lending) with Anchor or Pinocchio | [defi-program](https://github.com/Lightprotocol/skills/tree/main/skills/defi-program) | +| Integrate rent-free markets into routers and aggregators | [defi-router](https://github.com/Lightprotocol/skills/tree/main/skills/defi-router) | +| Stream account state via Laserstream gRPC | [data-streaming](https://github.com/Lightprotocol/skills/tree/main/skills/data-streaming) | +| Wallets and payment flows with light-token. Includes privy, wallet adapter, mobile wallet adapter signing. Optional nullifier to prevent your onchain instruction from being executed more than once. | [payments-and-wallets](https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets) | +| Airdrops, DePIN, token distribution | [airdrop](https://github.com/Lightprotocol/skills/tree/main/skills/airdrop) | +| Anti-double-spend nullifiers for Privacy-preserving ZK programs | [zk-nullifier](https://github.com/Lightprotocol/skills/tree/main/skills/zk-nullifier) | +| Testing programs and clients on localnet, devnet, mainnet | [testing](https://github.com/Lightprotocol/skills/tree/main/skills/testing) | +| Help with Debugging and Questions via DeepWiki MCP | [ask-mcp](https://github.com/Lightprotocol/skills/tree/main/skills/ask-mcp) | Skills for compressed PDAs and more are in development. diff --git a/snippets/code-snippets/light-token/counter/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/counter/anchor-macro/full-example.mdx new file mode 100644 index 00000000..dd4680f2 --- /dev/null +++ b/snippets/code-snippets/light-token/counter/anchor-macro/full-example.mdx @@ -0,0 +1,266 @@ + + + +```rust lib.rs expandable +use anchor_lang::prelude::*; +use light_account::{ + CompressionInfo, LightAccount, LightAccounts, CreateAccountsProof, + derive_light_cpi_signer, light_program, CpiSigner, +}; + +declare_id!("YourProgramId11111111111111111111111111111111"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("YourProgramId11111111111111111111111111111111"); + +pub const COUNTER_SEED: &[u8] = b"counter"; + +#[derive(Default, Debug, InitSpace, LightAccount)] +#[account] +pub struct Counter { + pub compression_info: CompressionInfo, + pub owner: Pubkey, + pub count: u64, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateCounterParams { + pub create_accounts_proof: CreateAccountsProof, + pub count: u64, +} + +#[light_program] +#[program] +pub mod counter { + use super::*; + + pub fn create_counter<'info>( + ctx: Context<'_, '_, '_, 'info, CreateCounter<'info>>, + params: CreateCounterParams, + ) -> Result<()> { + ctx.accounts.counter.owner = ctx.accounts.owner.key(); + ctx.accounts.counter.count = params.count; + Ok(()) + } + + /// Standard Anchor — no Light-specific changes. + pub fn increment(ctx: Context) -> Result<()> { + ctx.accounts.counter.count = ctx.accounts.counter.count.checked_add(1).unwrap(); + Ok(()) + } + + /// Standard Anchor — no Light-specific changes. + pub fn close_counter(_ctx: Context) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateCounterParams)] +pub struct CreateCounter<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Read-only, used for PDA derivation. + pub owner: AccountInfo<'info>, + + /// CHECK: Validated by Light Protocol CPI. + pub compression_config: AccountInfo<'info>, + + /// CHECK: PDA rent sponsor for compression rent reimbursement. + #[account(mut)] + pub pda_rent_sponsor: AccountInfo<'info>, + + #[account( + init, + payer = fee_payer, + space = 8 + ::INIT_SPACE, + seeds = [COUNTER_SEED, owner.key().as_ref()], + bump, + )] + #[light_account(init)] + pub counter: Account<'info, Counter>, + + pub system_program: Program<'info, System>, +} + +/// Standard Anchor +#[derive(Accounts)] +pub struct Increment<'info> { + pub owner: Signer<'info>, + + #[account( + mut, + seeds = [COUNTER_SEED, owner.key().as_ref()], + bump, + has_one = owner, + )] + pub counter: Account<'info, Counter>, +} + +/// Standard Anchor close +#[derive(Accounts)] +pub struct CloseCounter<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + pub owner: Signer<'info>, + + #[account( + mut, + close = fee_payer, + seeds = [COUNTER_SEED, owner.key().as_ref()], + bump, + has_one = owner, + )] + pub counter: Account<'info, Counter>, +} +``` + + + + +```rust counter.rs expandable +//! Light-PDA lifecycle test: create → increment → close. + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{ + get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, +}; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + ProgramTestConfig, Rpc, +}; +use light_token::instruction::RENT_SPONSOR; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +const PROGRAM_ID: Pubkey = counter::ID; + +/// Setup: create test RPC and initialize rent-free config for the counter program. +async fn setup() -> (LightProgramTest, Keypair, Pubkey) { + let config = ProgramTestConfig::new_v2(true, Some(vec![("counter", PROGRAM_ID)])); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &PROGRAM_ID); + + // Register this program for rent-free accounts. One-time setup per program. + let (init_config_ix, compression_config) = InitializeRentFreeConfig::new( + &PROGRAM_ID, + &payer.pubkey(), + &program_data_pda, + RENT_SPONSOR, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("initialize rent-free config"); + + (rpc, payer, compression_config) +} + +#[tokio::test] +async fn test_counter_lifecycle() { + let (mut rpc, payer, compression_config) = setup().await; + + // ── Create ─────────────────────────────────────────────────────────── + let (counter_pda, _) = Pubkey::find_program_address( + &[counter::COUNTER_SEED, payer.pubkey().as_ref()], + &PROGRAM_ID, + ); + + let proof_result = get_create_accounts_proof( + &rpc, + &PROGRAM_ID, + vec![CreateAccountsProofInput::pda(counter_pda)], + ) + .await + .unwrap(); + + let create_accounts = counter::accounts::CreateCounter { + fee_payer: payer.pubkey(), + owner: payer.pubkey(), + compression_config, + counter: counter_pda, + system_program: solana_sdk::system_program::ID, + }; + + let create_data = counter::instruction::CreateCounter { + params: counter::CreateCounterParams { + create_accounts_proof: proof_result.create_accounts_proof, + count: 0, + }, + }; + + let create_ix = Instruction { + program_id: PROGRAM_ID, + accounts: [ + create_accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: create_data.data(), + }; + + rpc.create_and_send_transaction(&[create_ix], &payer.pubkey(), &[&payer]) + .await + .expect("create_counter"); + + // Verify initial state. + let account = rpc.get_account(counter_pda).await.unwrap().unwrap(); + let ctr: counter::Counter = + anchor_lang::AccountDeserialize::try_deserialize(&mut account.data.as_slice()).unwrap(); + assert_eq!(ctr.count, 0); + assert_eq!(ctr.owner, payer.pubkey()); + + // ── Increment (standard Anchor) ──────────────────────────── + let inc_accounts = counter::accounts::Increment { + owner: payer.pubkey(), + counter: counter_pda, + }; + let inc_data = counter::instruction::Increment {}; + let inc_ix = Instruction { + program_id: PROGRAM_ID, + accounts: inc_accounts.to_account_metas(None), + data: inc_data.data(), + }; + + rpc.create_and_send_transaction(&[inc_ix], &payer.pubkey(), &[&payer]) + .await + .expect("increment"); + + let account = rpc.get_account(counter_pda).await.unwrap().unwrap(); + let ctr: counter::Counter = + anchor_lang::AccountDeserialize::try_deserialize(&mut account.data.as_slice()).unwrap(); + assert_eq!(ctr.count, 1); + + // ── Close (standard Anchor) ──────────────────────────────── + let close_accounts = counter::accounts::CloseCounter { + fee_payer: payer.pubkey(), + owner: payer.pubkey(), + counter: counter_pda, + }; + let close_data = counter::instruction::CloseCounter {}; + let close_ix = Instruction { + program_id: PROGRAM_ID, + accounts: close_accounts.to_account_metas(None), + data: close_data.data(), + }; + + rpc.create_and_send_transaction(&[close_ix], &payer.pubkey(), &[&payer]) + .await + .expect("close_counter"); + + // Account should no longer exist. + let account = rpc.get_account(counter_pda).await.unwrap(); + assert!(account.is_none(), "counter should be closed"); +} +``` + + + diff --git a/snippets/code-snippets/light-token/transfer-interface/anchor-program/create-and-transfer-example.mdx b/snippets/code-snippets/light-token/transfer-interface/anchor-program/create-and-transfer-example.mdx new file mode 100644 index 00000000..868d48b6 --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/anchor-program/create-and-transfer-example.mdx @@ -0,0 +1,249 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_sdk::interface::CreateAccountsProof; +use light_token::anchor::{derive_light_cpi_signer, light_program, CpiSigner, LightAccounts}; +use light_token::instruction::TransferInterfaceCpi; + +declare_id!("672fL1Nm191MbPoygNM9DRiG2psBELn97XUpGbU3jW7E"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("672fL1Nm191MbPoygNM9DRiG2psBELn97XUpGbU3jW7E"); + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct TransferParams { + pub create_accounts_proof: CreateAccountsProof, + pub dest_associated_token_account_bump: u8, + pub amount: u64, + pub decimals: u8, +} + +#[light_program] +#[program] +pub mod create_and_transfer { + use super::*; + + pub fn transfer<'info>( + ctx: Context<'_, '_, '_, 'info, Transfer<'info>>, + params: TransferParams, + ) -> Result<()> { + TransferInterfaceCpi::new( + params.amount, + params.decimals, + ctx.accounts.source.to_account_info(), + ctx.accounts.destination.to_account_info(), + ctx.accounts.authority.to_account_info(), + ctx.accounts.payer.to_account_info(), + ctx.accounts.light_token_cpi_authority.to_account_info(), + ctx.accounts.system_program.to_account_info(), + ) + .invoke() + .map_err(|e| anchor_lang::prelude::ProgramError::from(e))?; + Ok(()) + } +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: TransferParams)] +pub struct Transfer<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + pub authority: Signer<'info>, + + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub source: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub recipient: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + #[light_account(init, + associated_token::authority = recipient, + associated_token::mint = mint, + associated_token::bump = params.dest_associated_token_account_bump + )] + pub destination: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, + + /// CHECK: Validated by light-token CPI + pub light_token_compressible_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_cpi_authority: AccountInfo<'info>, +} +``` + +```rust test.rs +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + Indexer, ProgramTestConfig, Rpc, +}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{ + derive_token_ata, find_mint_address, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR, +}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_signer::Signer; +use create_and_transfer::{TransferParams, ID}; + +#[tokio::test] +async fn test_transfer() { + let config = ProgramTestConfig::new_v2(true, Some(vec![("create_and_transfer", ID)])) + .with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &ID); + + let (init_config_ix, _config_pda) = InitializeRentFreeConfig::new( + &ID, + &payer.pubkey(), + &program_data_pda, + RENT_SPONSOR, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let (mint_pda, _mint_seed) = setup_create_mint( + &mut rpc, + &payer, + payer.pubkey(), // mint_authority + 9, // decimals + ) + .await; + + println!("Mint created at: {}", mint_pda); + + let sender = Keypair::new(); + let (sender_associated_token_account, _sender_associated_token_account_bump) = + derive_token_ata(&sender.pubkey(), &mint_pda); + + let create_sender_associated_token_account_ix = + light_token::instruction::CreateAssociatedTokenAccount::new( + payer.pubkey(), + sender.pubkey(), + mint_pda, + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction( + &[create_sender_associated_token_account_ix], + &payer.pubkey(), + &[&payer], + ) + .await + .unwrap(); + + let mint_amount: u64 = 1_000_000_000; + mint_tokens(&mut rpc, &payer, mint_pda, sender_associated_token_account, mint_amount).await; + + println!( + "Minted {} tokens to sender: {}", + mint_amount, sender_associated_token_account + ); + + let recipient = Keypair::new(); + let (recipient_associated_token_account, recipient_associated_token_account_bump) = + derive_token_ata(&recipient.pubkey(), &mint_pda); + + let transfer_proof_result = get_create_accounts_proof(&rpc, &ID, vec![]).await.unwrap(); + + let transfer_accounts = create_and_transfer::accounts::Transfer { + payer: payer.pubkey(), + authority: sender.pubkey(), + mint: mint_pda, + source: sender_associated_token_account, + recipient: recipient.pubkey(), + destination: recipient_associated_token_account, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, + rent_sponsor: RENT_SPONSOR, + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + }; + + let transfer_amount: u64 = 500_000_000; // 0.5 tokens + let decimals: u8 = 9; + + let transfer_ix = Instruction { + program_id: ID, + accounts: [ + transfer_accounts.to_account_metas(None), + transfer_proof_result.remaining_accounts, + ] + .concat(), + data: create_and_transfer::instruction::Transfer { + params: TransferParams { + create_accounts_proof: transfer_proof_result.create_accounts_proof, + dest_associated_token_account_bump: recipient_associated_token_account_bump, + amount: transfer_amount, + decimals, + }, + } + .data(), + }; + + let sig = rpc + .create_and_send_transaction(&[transfer_ix], &payer.pubkey(), &[&payer, &sender]) + .await + .unwrap(); + + println!("Transfer Tx: {}", sig); + + use light_token_interface::state::Token; + + let recipient_account = rpc + .get_account(recipient_associated_token_account) + .await + .unwrap() + .unwrap(); + let recipient_token: Token = + borsh::BorshDeserialize::deserialize(&mut &recipient_account.data[..]).unwrap(); + + assert_eq!(recipient_token.amount, transfer_amount); + assert_eq!(recipient_token.owner, recipient.pubkey().to_bytes()); + + println!( + "Recipient balance: {}, owner: {}", + recipient_token.amount, + recipient.pubkey() + ); + + let sender_account = rpc + .get_account(sender_associated_token_account) + .await + .unwrap() + .unwrap(); + let sender_token: Token = + borsh::BorshDeserialize::deserialize(&mut &sender_account.data[..]).unwrap(); + + assert_eq!(sender_token.amount, mint_amount - transfer_amount); + println!("Sender remaining balance: {}", sender_token.amount); +} +``` + \ No newline at end of file diff --git a/snippets/jsx/liquid-glass-pill.jsx b/snippets/jsx/liquid-glass-pill.jsx index 05670a24..2e721839 100644 --- a/snippets/jsx/liquid-glass-pill.jsx +++ b/snippets/jsx/liquid-glass-pill.jsx @@ -1,6 +1,6 @@ // Styles in style.css - uses .glass-pill classes -export const LiquidGlassPill = ({ title }) => { +export const LiquidGlassPill = ({ title, children }) => { return (
@@ -8,6 +8,7 @@ export const LiquidGlassPill = ({ title }) => {

{title}

+ {children}
); diff --git a/snippets/jsx/partner-logos.jsx b/snippets/jsx/partner-logos.jsx new file mode 100644 index 00000000..94465bc1 --- /dev/null +++ b/snippets/jsx/partner-logos.jsx @@ -0,0 +1,24 @@ +// Styles in style.css - uses .partner-logos classes + +export const PartnerLogos = () => { + return ( +
+ Light Protocol + × + Helius + Helius +
+ ); +}; diff --git a/snippets/overview-tables/compressed-pdas-guides-table.mdx b/snippets/overview-tables/compressed-pdas-guides-table.mdx index b72a52b2..8ee1299f 100644 --- a/snippets/overview-tables/compressed-pdas-guides-table.mdx +++ b/snippets/overview-tables/compressed-pdas-guides-table.mdx @@ -1,7 +1,50 @@ -| Guide | Description | -| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -| [Create Compressed Accounts](/compressed-pdas/guides/how-to-create-compressed-accounts) | Create compressed accounts with address | -| [Update Compressed Accounts](/compressed-pdas/guides/how-to-update-compressed-accounts) | Update compressed accounts | -| [Close Compressed Accounts](/compressed-pdas/guides/how-to-close-compressed-accounts) | Close compressed accounts, retain the address | -| [Reinitialize Compressed Accounts](/compressed-pdas/guides/how-to-reinitialize-compressed-accounts) | Reinitialize closed compressed accounts with the same address and new values | -| [Burn Compressed Accounts](/compressed-pdas/guides/how-to-burn-compressed-accounts) | Burn compressed accounts and their address permanently | +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Create + Initialize compressed PDAs in your program
+ Update + Modify state in compressed accounts
+ Close + Reclaim lamports from compressed accounts
+ Reinitialize + Reset and reuse compressed accounts
+ Burn + Permanently delete compressed accounts
+ Nullifier PDAs + Prevent replay attacks with one-time use accounts
diff --git a/snippets/overview-tables/compressed-tokens-advanced-guides-table.mdx b/snippets/overview-tables/compressed-tokens-advanced-guides-table.mdx index ac09b01c..48166ba4 100644 --- a/snippets/overview-tables/compressed-tokens-advanced-guides-table.mdx +++ b/snippets/overview-tables/compressed-tokens-advanced-guides-table.mdx @@ -1,7 +1,4 @@ | Guide | Description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| [Create an Airdrop without Claim](/compressed-tokens/advanced-guides/airdrop) | ZK Compression is the most efficient way to distribute SPL tokens. Distribute via Webapp or customize with claim. | -| [Combine Instructions in One Transaction](/compressed-tokens/advanced-guides/how-to-combine-operations-in-one-transaction) | Execute multiple token instructions within a single transaction | -| [For Wallet Applications](/compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens) | Add compressed token support in your wallet application | -| [Use Token-2022 with Compression](/compressed-tokens/advanced-guides/use-token-2022-with-compression) | Create compressed Token-2022 mints with metadata and other extensions | +| [Create an Airdrop without Claim](/compressed-tokens/airdrop) | ZK Compression is the most efficient way to distribute SPL tokens. Distribute via Webapp or customize with claim. | | [Privy Guide](/compressed-tokens/for-privy) | Integrate compressed tokens with Privy embedded wallets for rent-free token accounts | diff --git a/snippets/overview-tables/compressed-tokens-guides-table.mdx b/snippets/overview-tables/compressed-tokens-guides-table.mdx index a9997b8c..555d7b53 100644 --- a/snippets/overview-tables/compressed-tokens-guides-table.mdx +++ b/snippets/overview-tables/compressed-tokens-guides-table.mdx @@ -1,11 +1,11 @@ | Guide | Description | | :---------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | -| [Create Compressed Token Accounts](/compressed-tokens/guides/create-compressed-token-accounts) | Create compressed and learn difference to regular token accounts | -| [Mint Compressed Tokens](/compressed-tokens/guides/mint-compressed-tokens) | Create new compressed tokens to existing mint | -| [Transfer Compressed Tokens](/compressed-tokens/guides/transfer-compressed-tokens) | Move compressed tokens between compressed accounts | -| [Decompress and Compress Tokens](/compressed-tokens/guides/compress-decompress) | Convert SPL tokens between regular and compressed format | -| [Compress Complete SPL Token Accounts](/compressed-tokens/guides/compress-spl-token-account) | Compress complete SPL token accounts and reclaim rent afterwards | -| [Create a Mint with Token Pool for Compression](/compressed-tokens/guides/create-mint-with-token-pool) | Create new SPL mint with token pool for compression | -| [Create Token Pools for Mint Accounts](/compressed-tokens/guides/add-token-pools-to-mint-accounts) | Create token pool for compression for existing SPL mints | -| [Merge Compressed Accounts](/compressed-tokens/guides/merge-compressed-token-accounts) | Consolidate multiple compressed accounts of the same mint into one | -| [Approve and Revoke Delegate Authority](/compressed-tokens/guides/delegate) | Approve or revoke delegates for compressed token accounts | +| [Create Compressed Token Accounts](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Create compressed and learn difference to regular token accounts | +| [Mint Compressed Tokens](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Create new compressed tokens to existing mint | +| [Transfer Compressed Tokens](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Move compressed tokens between compressed accounts | +| [Decompress and Compress Tokens](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Convert SPL tokens between regular and compressed format | +| [Compress Complete SPL Token Accounts](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Compress complete SPL token accounts and reclaim rent afterwards | +| [Create a Mint with Interface PDA for Compression](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Create new SPL mint with token pool for compression | +| [Create Token Pools for Mint Accounts](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Create token pool for compression for existing SPL mints | +| [Merge Compressed Accounts](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Consolidate multiple compressed accounts of the same mint into one | +| [Approve and Revoke Delegate Authority](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) | Approve or revoke delegates for compressed token accounts | diff --git a/snippets/overview-tables/examples-guides-table.mdx b/snippets/overview-tables/examples-guides-table.mdx new file mode 100644 index 00000000..d0d9dbb3 --- /dev/null +++ b/snippets/overview-tables/examples-guides-table.mdx @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + +
+ Client + TypeScript and Rust client examples for light-token SDK
+ Program + Anchor program examples for light-token CPI, Macros and more
\ No newline at end of file diff --git a/snippets/overview-tables/light-token-program-examples-table.mdx b/snippets/overview-tables/light-token-program-examples-table.mdx index 57943e36..6865d378 100644 --- a/snippets/overview-tables/light-token-program-examples-table.mdx +++ b/snippets/overview-tables/light-token-program-examples-table.mdx @@ -4,6 +4,8 @@ |---------|-------------| | [cp-swap-reference](https://github.com/Lightprotocol/cp-swap-reference) | Fork of Raydium AMM that creates markets without paying rent-exemption | | [create-and-transfer](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/create-and-transfer) | Create account via macro and transfer via CPI | +| [pinocchio-swap](https://github.com/Lightprotocol/examples-light-token/tree/main/pinocchio/swap) | Light Token swap reference implementation | + ### Macros | | Description | diff --git a/snippets/overview-tables/program-examples-table.mdx b/snippets/overview-tables/program-examples-table.mdx index 86761063..e736f453 100644 --- a/snippets/overview-tables/program-examples-table.mdx +++ b/snippets/overview-tables/program-examples-table.mdx @@ -1,12 +1,15 @@ | Example | Description | |:---------|:------------| +| [Account Comparison](https://github.com/Lightprotocol/program-examples/tree/main/account-comparison) | Compare compressed accounts with standard Solana accounts | | [basic-operations/anchor](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/anchor) | Anchor programs to create, update, close, reinitialize and burn compressed accounts with Rust and TypeScript tests | | [basic-operations/native](https://github.com/Lightprotocol/program-examples/tree/main/basic-operations/native) | Native Solana program implementation to create, update, close, reinitialize and burn compressed accounts with Rust tests | | [Counter (Anchor)](https://github.com/Lightprotocol/program-examples/tree/main/counter/anchor) | Full compressed account lifecycle (create, increment, decrement, reset, close) using Anchor framework | | [Counter (Native)](https://github.com/Lightprotocol/program-examples/tree/main/counter/native) | Native Solana program implementation with Rust tests | | [Counter (Pinocchio)](https://github.com/Lightprotocol/program-examples/tree/main/counter/pinocchio) | Pinocchio implementation using light-sdk-pinocchio with Rust tests | | [Create-and-Update](https://github.com/Lightprotocol/program-examples/tree/main/create-and-update) | Create new compressed accounts and update existing ones within a single instruction and one validity proof | +| [merkle-distributor](https://github.com/Lightprotocol/distributor) | SPL token distribution with compressed PDAs for claim tracking, vesting, and clawback | +| [Nullifier Program](https://github.com/Lightprotocol/nullifier-program) | System for payments, AI agents and more to prevent your onchain instruction from being executed more than once | | [Read-Only](https://github.com/Lightprotocol/program-examples/tree/main/read-only) | Create compressed accounts and read them on-chain | -| [Account Comparison](https://github.com/Lightprotocol/program-examples/tree/main/account-comparison) | Compare compressed accounts with standard Solana accounts | -| [Nullifier Program](https://github.com/Lightprotocol/nullifier-program) | For some use cases, such as sending payments, you might want to prevent your onchain instruction from being executed more than once. Creates a rent-free PDA derived from an id. If the id has been used before, the PDA already exists, causing the instruction to fail | -| [ZK-ID](https://github.com/Lightprotocol/program-examples/tree/main/zk-id) | Program that uses zero-knowledge proofs for identity verification with compressed accounts | +| [simple-claim](https://github.com/Lightprotocol/program-examples-airdrop-implementations/tree/main/simple-claim) | Distributes compressed tokens that decompress to SPL on claim | +| [ZK-ID](https://github.com/Lightprotocol/program-examples/tree/main/zk-id) | Zero-knowledge proofs for identity verification with compressed accounts | +| [ZK-Nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk-nullifier) | Implementation of nullifiers for zk programs on Solana to prevent double spending | diff --git a/snippets/overview-tables/sdk-reference-compressed-pdas.mdx b/snippets/overview-tables/sdk-reference-compressed-pdas.mdx new file mode 100644 index 00000000..99739874 --- /dev/null +++ b/snippets/overview-tables/sdk-reference-compressed-pdas.mdx @@ -0,0 +1,32 @@ +### Client + + + + TypeScript RPC client for compressed accounts, validity proofs, and address derivation. + + + Rust RPC client and indexer for compressed accounts. + + + +### Program + + + + Core SDK for compressed accounts in Anchor programs. + + + Procedural macros for LightAccount derivation. + + + Local testing framework for programs. + + diff --git a/snippets/overview-tables/sdk-reference.mdx b/snippets/overview-tables/sdk-reference.mdx new file mode 100644 index 00000000..09d94598 --- /dev/null +++ b/snippets/overview-tables/sdk-reference.mdx @@ -0,0 +1,45 @@ +### Client + + + + TypeScript RPC client for Light Token and Compressed Accounts. + + + Rust RPC client for Light Token and ZK Compression. + + + TypeScript token operations for Light Token and Compressed Tokens. + + + Rust token operations for Light Token. + + + +### Program + + + + Core SDK for on-chain programs. + + + Procedural macros for Light accounts. + + + CPI instructions for Light Token program. + + + Local testing framework for programs. + + diff --git a/snippets/setup/compressed-tokens-mint-prereq.mdx b/snippets/setup/compressed-tokens-mint-prereq.mdx index a45998ff..b7d3ccea 100644 --- a/snippets/setup/compressed-tokens-mint-prereq.mdx +++ b/snippets/setup/compressed-tokens-mint-prereq.mdx @@ -1,5 +1,5 @@ Make sure the SPL mint has a token pool for compression.
The script creates this token pool for you. -For development, you can create a new mint with token pool via [`createMint()`](/compressed-tokens/guides/create-mint-with-token-pool) or add a token pool to an existing mint via [`createTokenPool()`](/compressed-tokens/guides/add-token-pools-to-mint-accounts). +For development, you can create a new mint with token pool via [`createMint()`](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook) or add a token pool to an existing mint via [`createTokenPool()`](https://github.com/Lightprotocol/examples-zk-compression/tree/main/compressed-token-cookbook).
diff --git a/snippets/versions/rust-native-token.mdx b/snippets/versions/rust-native-token.mdx new file mode 100644 index 00000000..e77600fa --- /dev/null +++ b/snippets/versions/rust-native-token.mdx @@ -0,0 +1,7 @@ +```toml +[dependencies] +light-token = "0.4.0" +light-compressible = "0.4.0" +light-token-interface = "0.3.0" +light-compressed-account = "0.9.0" +``` diff --git a/snippets/versions/rust-sdk-macros.mdx b/snippets/versions/rust-sdk-macros.mdx new file mode 100644 index 00000000..cd5fc463 --- /dev/null +++ b/snippets/versions/rust-sdk-macros.mdx @@ -0,0 +1,5 @@ +```toml +[dependencies] +light-sdk = { version = "0.19.0", features = ["anchor", "v2", "cpi-context"] } +light-sdk-macros = "0.19.0" +``` diff --git a/snippets/versions/rust-sdk.mdx b/snippets/versions/rust-sdk.mdx new file mode 100644 index 00000000..110d90ab --- /dev/null +++ b/snippets/versions/rust-sdk.mdx @@ -0,0 +1,4 @@ +```toml +[dependencies] +light-sdk = "0.19.0" +``` diff --git a/snippets/versions/rust-test.mdx b/snippets/versions/rust-test.mdx new file mode 100644 index 00000000..ab8e5c69 --- /dev/null +++ b/snippets/versions/rust-test.mdx @@ -0,0 +1,5 @@ +```toml +[dev-dependencies] +light-program-test = { version = "0.19.0", features = ["v2"] } +light-client = { version = "0.19.0", features = ["v2"] } +``` diff --git a/snippets/versions/rust-token-defi.mdx b/snippets/versions/rust-token-defi.mdx new file mode 100644 index 00000000..dbaa0a59 --- /dev/null +++ b/snippets/versions/rust-token-defi.mdx @@ -0,0 +1,6 @@ +```toml +[dependencies] +light-sdk = { version = "0.19.0", features = ["anchor", "v2", "cpi-context"] } +light-sdk-macros = "0.19.0" +light-token = { version = "0.4.0", features = ["anchor"] } +``` diff --git a/snippets/versions/rust-token.mdx b/snippets/versions/rust-token.mdx new file mode 100644 index 00000000..e51ec9cc --- /dev/null +++ b/snippets/versions/rust-token.mdx @@ -0,0 +1,5 @@ +```toml +[dependencies] +light-sdk = "0.19.0" +light-token = "0.4.0" +``` diff --git a/style.css b/style.css index 642acdf1..d89567fc 100644 --- a/style.css +++ b/style.css @@ -219,3 +219,49 @@ html.dark .hero-title { font-size: 3rem; } } + +/* Partner logos inside hero pill */ +.partner-logos { + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + margin-top: 8px; + pointer-events: none; +} + +.partner-logos img { + height: 24px; + width: auto; +} + +.partner-divider { + color: #9ca3af; + font-size: 12px; +} + +/* Light wordmark is white SVG - invert for light mode */ +.light-wordmark { + filter: invert(1); +} + +html.dark .light-wordmark { + filter: none; +} + +/* Helius logo switching */ +.logo-dark { + display: none; +} + +html.dark .logo-light { + display: none; +} + +html.dark .logo-dark { + display: block; +} + +html.dark .partner-divider { + color: #6b7280; +} diff --git a/support.mdx b/support.mdx index a2dc3cf0..d40ffb06 100644 --- a/support.mdx +++ b/support.mdx @@ -45,7 +45,7 @@ Install our MCP server for advanced AI assistance. title="AI Tools Guide" icon="chevron-right" color="#0066ff" - href="/learn/ai-tools-guide#MCP" + href="/ai-tools/guide#mcp" horizontal >
diff --git a/welcome.mdx b/welcome.mdx index 16f17ef4..b68b8adb 100644 --- a/welcome.mdx +++ b/welcome.mdx @@ -9,51 +9,6 @@ import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; ![](/images/banner-image.png) -| **Creation Cost** | Solana | ZK Compression | -| :---------------- | :------------------ | :------------------- | -| **Token Account** | ~2,000,000 lamports | ~**5,000** lamports | -| **100-byte PDA** | ~1,600,000 lamports | ~**15,000** lamports | - -## Features - - - High performance token standard for rent-free DeFi and Payments. - - - - - - For App State. - - - For Token Distribution. - - - -## Quickstart - - - - - - - - - - ## What is ZK Compression? ZK Compression is a framework that reduces the storage cost of Solana accounts by combining generalized state compression and zero-knowledge proofs. @@ -68,57 +23,4 @@ ZK Compression is a framework that reduces the storage cost of Solana accounts b the compressed accounts. By default, this is all done under the hood. You can fetch validity proofs from RPC providers that support ZK Compression. - - -## Resources - - - - Browse ZK Compression's JSON RPC methods. - - - Explore our TypeScript and Rust SDKs. - - - Install the ZK Compression CLI for local development. - - - -## Learn & Community - - - - Learn about ZK Compression's core concepts. - - - Read our external audit and formal verification reports. - - - Join our Discord for support and discussions. - - - -## Next Steps - - + \ No newline at end of file diff --git a/zk/overview.mdx b/zk/overview.mdx index c56e4714..781e76dd 100644 --- a/zk/overview.mdx +++ b/zk/overview.mdx @@ -16,11 +16,11 @@ Building a ZK Solana program requires: 4. An indexer to serve Merkle proofs 5. Encrypted state -## Nullifiers on Solana +## Nullifiers for ZK on Solana A nullifier is a deterministically derived hash to ensure an action can only be performed once. The nullifier cannot be linked to the action or user. -For example Zcash uses nullifiers to prevent double spending. +For example Zcash uses nullifiers to prevent double spending. To implement nullifiers we need a data structure that ensures every nullifier is only created once and never deleted. On Solana a straight forward way to implement nullifiers is to create a PDA account with the nullifier as seed.