diff --git a/light-token/toolkits/for-streaming-tokens.mdx b/light-token/toolkits/for-streaming-tokens.mdx index 8b181e66..1bc431d7 100644 --- a/light-token/toolkits/for-streaming-tokens.mdx +++ b/light-token/toolkits/for-streaming-tokens.mdx @@ -5,153 +5,45 @@ description: "Light token accounts follow the same layout as SPL-token accounts, keywords: ["streaming tokens for solana apps", "scalable token distribution on solana", "token streaming for developers"] --- -import ToolkitsSetup from "/snippets/setup/toolkits-setup.mdx"; +import WarmUpAction from "/snippets/code-snippets/light-token/warm-up/warm-up-action.mdx"; +import WarmUpInstruction from "/snippets/code-snippets/light-token/warm-up/warm-up-instruction.mdx"; -When a market becomes inactive, its token accounts and related PDAs will be compressed - their state is committed and effectively frozen until a client decompresses it. +When a market becomes inactive, its token accounts and related PDAs will +be compressed automatically (cold storage). +The state is cryptographically preserved on the Solana ledger. While compressed, pure on-chain lookups will return uninitialized. -Your indexer should keep tracking, quoting, and routing markets even if the on-chain account shows `is_initialized: false`, `is_compressed: true`. To trade a cold market, the first client must prepend an idempotent decompress "warm up" instruction. +Your indexer should keep tracking, quoting, and routing markets even if the +on-chain account shows `is_initialized: false`, `is_compressed: true`. +To trade a cold market, the first client must prepend an +idempotent decompress "warm up" instruction. Find the source code - [here](https://github.com/Lightprotocol/light-protocol/blob/main/js/compressed-token/tests/e2e/load-ata-standard.test.ts). + [here](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/indexing-tokens). -## Setup - - - -### Load Compressed Tokens to Hot Balance - - -```typescript Action -import { Keypair } from "@solana/web3.js"; -import { createRpc, bn } from "@lightprotocol/stateless.js"; -import { - createMint, - mintTo, - loadAta, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token"; - -async function main() { - const rpc = createRpc(); - const payer = Keypair.generate(); - await rpc.requestAirdrop(payer.publicKey, 10e9); - - const owner = Keypair.generate(); - await rpc.requestAirdrop(owner.publicKey, 1e9); - - const mintAuthority = Keypair.generate(); - const mintKeypair = Keypair.generate(); - const { mint } = await createMint( - rpc, - payer, - mintAuthority.publicKey, - 9, - mintKeypair, - ); - - await mintTo(rpc, payer, mint, owner.publicKey, mintAuthority, bn(1000)); - - // Get light-token ATA address - const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - - // Load compressed tokens to hot balance - // Creates ATA if needed, returns null if nothing to load - const signature = await loadAta(rpc, tokenAta, owner, mint, payer); - - if (signature) { - console.log("Loaded tokens to hot balance"); - console.log("Transaction:", signature); - } else { - console.log("Nothing to load"); - } -} - -main().catch(console.error); -``` - -```typescript Instruction -import { Keypair, ComputeBudgetProgram } from "@solana/web3.js"; -import { - createRpc, - bn, - buildAndSignTx, - sendAndConfirmTx, - dedupeSigner, -} from "@lightprotocol/stateless.js"; -import { - createMint, - mintTo, - createLoadAtaInstructions, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token"; - -async function main() { - const rpc = createRpc(); - const payer = Keypair.generate(); - await rpc.requestAirdrop(payer.publicKey, 10e9); - - const owner = Keypair.generate(); - await rpc.requestAirdrop(owner.publicKey, 1e9); - - const mintAuthority = Keypair.generate(); - const mintKeypair = Keypair.generate(); - const { mint } = await createMint( - rpc, - payer, - mintAuthority.publicKey, - 9, - mintKeypair - ); - - await mintTo(rpc, payer, mint, owner.publicKey, mintAuthority, bn(1000)); - - // Get light-token ATA address - const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - - // Create load instructions - const ixs = await createLoadAtaInstructions( - rpc, - tokenAta, - owner.publicKey, - mint, - payer.publicKey - ); - - if (ixs.length === 0) { - console.log("Nothing to load"); - return; - } - - // Build, sign, and send transaction - const { blockhash } = await rpc.getLatestBlockhash(); - const additionalSigners = dedupeSigner(payer, [owner]); - - const tx = buildAndSignTx( - [ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }), ...ixs], - payer, - blockhash, - additionalSigners - ); - - const signature = await sendAndConfirmTx(rpc, tx); - console.log("Loaded tokens to hot balance"); - console.log("Transaction:", signature); -} +### Warm up a cold market and trade -main().catch(console.error); -``` +Prepend idempotent decompress instructions before your trade. +`loadAta` returns null and `createLoadAtaInstructions` returns an empty array +when the account is already hot, so this pattern is safe to use unconditionally. - + + + + + + + + -# Stream Light-Mint Accounts +# Stream light-mint accounts +/> \ No newline at end of file diff --git a/scripts/copy-light-token-snippets.sh b/scripts/copy-light-token-snippets.sh index 862a7069..ec99f36c 100755 --- a/scripts/copy-light-token-snippets.sh +++ b/scripts/copy-light-token-snippets.sh @@ -13,6 +13,7 @@ RECIPES=("create-mint" "create-ata" "mint-to" "transfer-interface" "load-ata" "w wrap_typescript() { local input_file="$1" local output_file="$2" + mkdir -p "$(dirname "$output_file")" echo '```typescript' > "$output_file" cat "$input_file" >> "$output_file" echo '```' >> "$output_file" @@ -40,6 +41,19 @@ for recipe in "${RECIPES[@]}"; do fi done +# Indexing toolkit snippets +INDEXING_DIR="/home/tilo/Workspace/examples-light-token/toolkits/indexing-tokens" +echo "Processing: indexing-tokens toolkit" + +for variant in "warm-up-action" "warm-up-instruction"; do + src="$INDEXING_DIR/$variant.ts" + if [ -f "$src" ]; then + wrap_typescript "$src" "$SNIPPETS_DIR/warm-up/$variant.mdx" + else + echo " WARNING: Not found - $src" + fi +done + echo "" echo "Done! Created snippets in: $SNIPPETS_DIR" echo "" diff --git a/snippets/code-snippets/light-token/warm-up/warm-up-action.mdx b/snippets/code-snippets/light-token/warm-up/warm-up-action.mdx new file mode 100644 index 00000000..0af83305 --- /dev/null +++ b/snippets/code-snippets/light-token/warm-up/warm-up-action.mdx @@ -0,0 +1,57 @@ +```typescript +import "dotenv/config"; +import { Keypair } from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { + createMintInterface, + createAtaInterface, + mintToCompressed, + loadAta, + transferInterface, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")), + ), +); + +(async function () { + // Inactive Light Tokens are cryptographically preserved on the Solana ledger + // as compressed tokens (cold storage) + // Setup: Get compressed tokens in light-token associated token account + const { mint } = await createMintInterface(rpc, payer, payer, null, 9); + await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]); + + const recipient = Keypair.generate(); + await createAtaInterface(rpc, payer, mint, recipient.publicKey); + + const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); + const recipientAta = getAssociatedTokenAddressInterface(mint, recipient.publicKey); + + // Warm up: load compressed tokens to associated token account + // Returns null if already hot + await loadAta(rpc, senderAta, payer, mint, payer); + + // Transfer tokens from hot balance + const tx = await transferInterface( + rpc, + payer, + senderAta, + mint, + recipientAta, + payer, + 500n, + ); + + console.log("Tx:", tx); +})();``` diff --git a/snippets/code-snippets/light-token/warm-up/warm-up-instruction.mdx b/snippets/code-snippets/light-token/warm-up/warm-up-instruction.mdx new file mode 100644 index 00000000..1ddee2f3 --- /dev/null +++ b/snippets/code-snippets/light-token/warm-up/warm-up-instruction.mdx @@ -0,0 +1,77 @@ +```typescript +import "dotenv/config"; +import { Keypair } from "@solana/web3.js"; +import { + createRpc, + buildAndSignTx, + sendAndConfirmTx, +} from "@lightprotocol/stateless.js"; +import { + createMintInterface, + createAtaInterface, + mintToCompressed, + createLoadAtaInstructions, + createTransferInterfaceInstruction, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")), + ), +); + +(async function () { + // Inactive Light Tokens are cryptographically preserved on the Solana ledger + // as compressed tokens (cold storage) + // Setup: Get compressed tokens in light-token associated token account + const { mint } = await createMintInterface(rpc, payer, payer, null, 9); + await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]); + + const recipient = Keypair.generate(); + await createAtaInterface(rpc, payer, mint, recipient.publicKey); + + const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); + const recipientAta = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey, + ); + + // Warm up: load compressed tokens (cold) to sender's hot balance + // Returns [] if already hot — safe to call unconditionally + const loadIxs = await createLoadAtaInstructions( + rpc, + senderAta, + payer.publicKey, + mint, + payer.publicKey, + ); + + if (loadIxs.length > 0) { + const blockhash = await rpc.getLatestBlockhash(); + const loadTx = buildAndSignTx(loadIxs, payer, blockhash.blockhash); + await sendAndConfirmTx(rpc, loadTx); + } + + // Trade: transfer from hot balance + const transferIx = createTransferInterfaceInstruction( + senderAta, + recipientAta, + payer.publicKey, + 500n, + ); + + const blockhash = await rpc.getLatestBlockhash(); + const tradeTx = buildAndSignTx([transferIx], payer, blockhash.blockhash); + const signature = await sendAndConfirmTx(rpc, tradeTx); + + console.log("Tx:", signature); +})();```