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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 24 additions & 132 deletions light-token/toolkits/for-streaming-tokens.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,158 +5,50 @@
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

Check warning on line 11 in light-token/toolkits/for-streaming-tokens.mdx

View check run for this annotation

Mintlify / Mintlify Validation (luminouslabs-cc5545c6) - vale-spellcheck

light-token/toolkits/for-streaming-tokens.mdx#L11

Did you really mean 'PDAs'?
be compressed automatically (cold storage).
The state is cryptographically preserved on the Solana ledger.

Check warning on line 13 in light-token/toolkits/for-streaming-tokens.mdx

View check run for this annotation

Mintlify / Mintlify Validation (luminouslabs-cc5545c6) - vale-spellcheck

light-token/toolkits/for-streaming-tokens.mdx#L13

Did you really mean 'Solana'?
While compressed, pure on-chain lookups will return uninitialized.

Check warning on line 14 in light-token/toolkits/for-streaming-tokens.mdx

View check run for this annotation

Mintlify / Mintlify Validation (luminouslabs-cc5545c6) - vale-spellcheck

light-token/toolkits/for-streaming-tokens.mdx#L14

Did you really mean 'lookups'?

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.
Comment on lines +18 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hyphenate "warm up" when used as a compound modifier.

Per static analysis, "warm up" should be hyphenated as "warm-up" when used as an adjective modifying "instruction".

📝 Proposed fix
 To trade a cold market, the first client must prepend an
-idempotent decompress "warm up" instruction.
+idempotent decompress "warm-up" instruction.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
To trade a cold market, the first client must prepend an
idempotent decompress "warm up" instruction.
To trade a cold market, the first client must prepend an
idempotent decompress "warm-up" instruction.
🧰 Tools
🪛 LanguageTool

[grammar] ~19-~19: Use a hyphen to join words.
Context: ...t prepend an idempotent decompress "warm up" instruction. Find the sour...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In `@light-token/toolkits/for-streaming-tokens.mdx` around lines 18 - 19, The
phrase "warm up" used as a compound modifier in the sentence containing
idempotent decompress "warm up" instruction should be hyphenated; update the
text so it reads idempotent decompress "warm-up" instruction (or idempotent
decompress warm-up instruction) to use the compound modifier form, leaving the
rest of the sentence unchanged.


<Info>
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).
</Info>

## Setup

<ToolkitsSetup />

<Steps>
<Step>
### Load Compressed Tokens to Hot Balance

<CodeGroup>
```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.

</CodeGroup>
<Tabs>
<Tab title="Action">
<WarmUpAction />
</Tab>
<Tab title="Instruction">
<WarmUpInstruction />
</Tab>
</Tabs>

</Step>
</Steps>

# Stream Light-Mint Accounts
# Stream light-mint accounts

<Card
title="Toolkit to stream light-mints"
icon="chevron-right"
color="#0066ff"
href="/light-token/toolkits/for-streaming-mints"
horizontal
/>
/>
14 changes: 14 additions & 0 deletions scripts/copy-light-token-snippets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 ""
Expand Down
57 changes: 57 additions & 0 deletions snippets/code-snippets/light-token/warm-up/warm-up-action.mdx
Original file line number Diff line number Diff line change
@@ -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);
})();```
Comment on lines +56 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing newline before closing code fence.

The closing `)();``` should have a newline before the fence for proper markdown rendering.

📝 Proposed fix
     console.log("Tx:", tx);
-})();```
+})();
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("Tx:", tx);
})();```
console.log("Tx:", tx);
})();
🤖 Prompt for AI Agents
In `@snippets/code-snippets/light-token/warm-up/warm-up-action.mdx` around lines
56 - 57, The code block ends without a blank line before the closing fence
causing markdown rendering issues; edit the snippet containing the IIFE that
logs the transaction (the line with console.log("Tx:", tx); and the trailing
IIFE end `})();`) and insert a newline after `})();` so the closing
triple-backtick is on its own line (i.e., add a blank line/newline between
`})();` and the closing ``` fence).

77 changes: 77 additions & 0 deletions snippets/code-snippets/light-token/warm-up/warm-up-instruction.mdx
Original file line number Diff line number Diff line change
@@ -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);
})();```
Comment on lines +76 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing newline before closing code fence.

Same issue as the action snippet - the closing fence needs a preceding newline for proper markdown rendering.

📝 Proposed fix
     console.log("Tx:", signature);
-})();```
+})();
+```
🤖 Prompt for AI Agents
In `@snippets/code-snippets/light-token/warm-up/warm-up-instruction.mdx` around
lines 76 - 77, The closing Markdown code fence is missing a preceding newline in
the warm-up snippet—locate the snippet containing console.log("Tx:",
signature);})(); and add a newline (blank line) before the final ``` fence so
the code block closes properly; ensure the sequence ends with the code line, a
newline, then ``` to fix rendering.

Loading