diff --git a/.github/workflows/typescript-tests.yml b/.github/workflows/typescript-tests.yml index 9d24f627..32ef431c 100644 --- a/.github/workflows/typescript-tests.yml +++ b/.github/workflows/typescript-tests.yml @@ -18,8 +18,8 @@ env: NODE_VERSION: "22" jobs: - typescript-tests: - name: TypeScript Tests + localnet-examples: + name: Localnet Examples (TypeScript) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -27,26 +27,23 @@ jobs: - name: Setup environment uses: ./.github/actions/setup with: - example: typescript-client + example: . solana-cli-version: ${{ env.SOLANA_CLI_VERSION }} photon-indexer: "true" - - name: Install dependencies - working-directory: typescript-client + - name: Install dependencies (workspaces) run: npm install + - name: Enforce latest Light SDK dependency spec + run: npm run ci:deps:light-sdk + - name: Start test validator run: | - light test-validator & + npx -y @lightprotocol/zk-compression-cli@beta test-validator & for i in $(seq 1 60); do curl -s http://127.0.0.1:8784/health && break sleep 2 done - - name: Run actions - working-directory: typescript-client - run: npm run test:actions - - - name: Run instructions - working-directory: typescript-client - run: npm run test:instructions + - name: Run localnet examples sweep + run: npm run ci:examples:localnet diff --git a/package.json b/package.json index 5dd05121..5d44933e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "toolkits/sponsor-rent-top-ups/typescript" ], "scripts": { - "toolkit:payments": "npm run -w toolkits/payments-and-wallets" + "toolkit:payments": "npm run -w toolkits/payments-and-wallets", + "ci:deps:light-sdk": "node scripts/ci/verify-light-sdk-deps.mjs", + "ci:examples:localnet": "bash scripts/ci/run-localnet-examples.sh" }, "dependencies": { "@lightprotocol/compressed-token": "beta", diff --git a/scripts/ci/run-localnet-examples.sh b/scripts/ci/run-localnet-examples.sh new file mode 100644 index 00000000..c126f835 --- /dev/null +++ b/scripts/ci/run-localnet-examples.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +export LIGHT_PROTOCOL_BETA=true + +PASS=0 +FAIL=0 + +run_script() { + local workspace="$1" + local script="$2" + + echo "=== RUN ${workspace} :: ${script} ===" + if npm run -w "${workspace}" "${script}"; then + PASS=$((PASS + 1)) + echo "=== PASS ${workspace} :: ${script} ===" + else + FAIL=$((FAIL + 1)) + echo "=== FAIL ${workspace} :: ${script} ===" + fi +} + +# Localnet-only TypeScript examples (self-contained, no external credentials). +run_script "typescript-client" "create-mint:action" +run_script "typescript-client" "create-mint:instruction" +run_script "typescript-client" "create-spl-mint:action" +run_script "typescript-client" "create-spl-mint:instruction" +run_script "typescript-client" "create-t22-mint:action" +run_script "typescript-client" "create-t22-mint:instruction" +run_script "typescript-client" "create-interface-pda:action" +run_script "typescript-client" "create-interface-pda:instruction" +run_script "typescript-client" "create-ata:action" +run_script "typescript-client" "create-ata:instruction" +run_script "typescript-client" "mint-to:action" +run_script "typescript-client" "mint-to:instruction" +run_script "typescript-client" "transfer-interface:action" +run_script "typescript-client" "transfer-interface:instruction" +run_script "typescript-client" "wrap:action" +run_script "typescript-client" "wrap:instruction" +run_script "typescript-client" "unwrap:action" +run_script "typescript-client" "unwrap:instruction" +run_script "typescript-client" "load-ata:action" +run_script "typescript-client" "load-ata:instruction" +run_script "typescript-client" "delegate:approve" +run_script "typescript-client" "delegate:revoke" + +run_script "toolkits/payments-and-wallets" "register-spl-mint" +run_script "toolkits/payments-and-wallets" "receive" +run_script "toolkits/payments-and-wallets" "send-instruction" +run_script "toolkits/payments-and-wallets" "send-action" +run_script "toolkits/payments-and-wallets" "send-and-receive" +run_script "toolkits/payments-and-wallets" "get-balance" +run_script "toolkits/payments-and-wallets" "get-history" +run_script "toolkits/payments-and-wallets" "wrap" +run_script "toolkits/payments-and-wallets" "unwrap" + +run_script "toolkits/sponsor-rent-top-ups/typescript" "sponsor-top-ups" + +echo "SUMMARY PASS=${PASS} FAIL=${FAIL}" +if [[ "${FAIL}" -ne 0 ]]; then + exit 1 +fi diff --git a/scripts/ci/verify-light-sdk-deps.mjs b/scripts/ci/verify-light-sdk-deps.mjs new file mode 100644 index 00000000..a8392def --- /dev/null +++ b/scripts/ci/verify-light-sdk-deps.mjs @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +import { readFileSync } from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const root = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "..", + ".." +); +const rootPkg = JSON.parse( + readFileSync(path.join(root, "package.json"), "utf8") +); + +const targets = [".", ...(rootPkg.workspaces ?? [])]; +const sdkDeps = [ + "@lightprotocol/compressed-token", + "@lightprotocol/stateless.js", +]; + +const violations = []; + +for (const target of targets) { + const pkgPath = path.join(root, target, "package.json"); + const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); + const depSets = [ + pkg.dependencies ?? {}, + pkg.devDependencies ?? {}, + pkg.peerDependencies ?? {}, + ]; + + for (const depName of sdkDeps) { + const current = depSets.find(set => depName in set)?.[depName]; + if (!current) continue; + if (current !== "beta") { + violations.push( + `${target}/package.json -> ${depName} must be "beta", found "${current}"`, + ); + } + } +} + +if (violations.length > 0) { + console.error("Light SDK dependency policy violations:"); + for (const msg of violations) console.error(`- ${msg}`); + process.exit(1); +} + +console.log("Light SDK dependency policy check passed."); diff --git a/toolkits/payments-and-wallets/comparison-spl-light.md b/toolkits/payments-and-wallets/comparison-spl-light.md index f1f4493f..744646da 100644 --- a/toolkits/payments-and-wallets/comparison-spl-light.md +++ b/toolkits/payments-and-wallets/comparison-spl-light.md @@ -165,8 +165,10 @@ const tx = new Transaction().add( ```typescript const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); +await getOrCreateAtaInterface(rpc, payer, mint, recipient); +const destination = getAssociatedTokenAddressInterface(mint, recipient); -await transferInterface(rpc, payer, sourceAta, mint, recipient, owner, amount); +await transferInterface(rpc, payer, sourceAta, mint, destination, owner, amount); ``` **Light (instruction-level):** @@ -175,16 +177,21 @@ await transferInterface(rpc, payer, sourceAta, mint, recipient, owner, amount); import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; import { createTransferInterfaceInstructions, + getOrCreateAtaInterface, + getAssociatedTokenAddressInterface, sliceLast, } from "@lightprotocol/compressed-token/unified"; +await getOrCreateAtaInterface(rpc, payer, mint, recipient); +const destination = getAssociatedTokenAddressInterface(mint, recipient); + const batches = await createTransferInterfaceInstructions( rpc, payer.publicKey, mint, amount, owner.publicKey, - recipient + destination ); const { rest: loadBatches, last: transferBatch } = sliceLast(batches); diff --git a/toolkits/payments-and-wallets/get-balance.ts b/toolkits/payments-and-wallets/get-balance.ts index daa27a30..a690b209 100644 --- a/toolkits/payments-and-wallets/get-balance.ts +++ b/toolkits/payments-and-wallets/get-balance.ts @@ -56,27 +56,29 @@ const payer = Keypair.fromSecretKey( const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); await wrap(rpc, payer, splAta, senderAta, payer, mint, BigInt(1000)); - // Transfer to recipient const recipient = Keypair.generate(); await createAtaInterface(rpc, payer, mint, recipient.publicKey); + const recipientAta = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey + ); await transferInterface( rpc, payer, senderAta, mint, - recipient.publicKey, + recipientAta, payer, 100 ); - // Get recipient's balance - const recipientAta = getAssociatedTokenAddressInterface( + const recipientAtaForBalance = getAssociatedTokenAddressInterface( mint, recipient.publicKey ); const { parsed: account } = await getAtaInterface( rpc, - recipientAta, + recipientAtaForBalance, recipient.publicKey, mint ); diff --git a/toolkits/payments-and-wallets/get-history.ts b/toolkits/payments-and-wallets/get-history.ts index d404dd14..6e19f73d 100644 --- a/toolkits/payments-and-wallets/get-history.ts +++ b/toolkits/payments-and-wallets/get-history.ts @@ -55,14 +55,18 @@ const payer = Keypair.fromSecretKey( const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); await wrap(rpc, payer, splAta, senderAta, payer, mint, BigInt(1000)); - // Transfer to recipient const recipient = Keypair.generate(); + await createAtaInterface(rpc, payer, mint, recipient.publicKey); + const recipientAta = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey + ); await transferInterface( rpc, payer, senderAta, mint, - recipient.publicKey, + recipientAta, payer, 100 ); diff --git a/toolkits/payments-and-wallets/package.json b/toolkits/payments-and-wallets/package.json index 1d1713d8..e773c971 100644 --- a/toolkits/payments-and-wallets/package.json +++ b/toolkits/payments-and-wallets/package.json @@ -15,7 +15,7 @@ "unwrap": "tsx unwrap.ts" }, "dependencies": { - "@lightprotocol/compressed-token": "^0.23.0-beta.9", - "@lightprotocol/stateless.js": "^0.23.0-beta.9" + "@lightprotocol/compressed-token": "beta", + "@lightprotocol/stateless.js": "beta" } } diff --git a/toolkits/payments-and-wallets/receive.ts b/toolkits/payments-and-wallets/receive.ts index 1f871e84..854646f3 100644 --- a/toolkits/payments-and-wallets/receive.ts +++ b/toolkits/payments-and-wallets/receive.ts @@ -60,24 +60,22 @@ const payer = Keypair.fromSecretKey( const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); await wrap(rpc, payer, splAta, senderAta, payer, mint, BigInt(1_000_000)); - // Transfer to a fresh recipient so they have cold tokens const recipient = Keypair.generate(); + await createAtaInterface(rpc, payer, mint, recipient.publicKey); + const recipientAta = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey + ); await transferInterface( rpc, payer, senderAta, mint, - recipient.publicKey, + recipientAta, payer, 500 ); - // --- Receive: load creates ATA if needed + pulls cold state to hot --- - const recipientAta = getAssociatedTokenAddressInterface( - mint, - recipient.publicKey - ); - // Returns TransactionInstruction[][]. Each inner array is one txn. // Almost always one. Empty = noop. const instructions = await createLoadAtaInstructions( diff --git a/toolkits/payments-and-wallets/register-spl-mint.ts b/toolkits/payments-and-wallets/register-spl-mint.ts index 492f0538..5532de6c 100644 --- a/toolkits/payments-and-wallets/register-spl-mint.ts +++ b/toolkits/payments-and-wallets/register-spl-mint.ts @@ -1,13 +1,12 @@ import "dotenv/config"; import { Keypair, - PublicKey, Transaction, sendAndConfirmTransaction, } from "@solana/web3.js"; import { createRpc } from "@lightprotocol/stateless.js"; import { LightTokenProgram } from "@lightprotocol/compressed-token"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { TOKEN_PROGRAM_ID, createMint } from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -24,8 +23,16 @@ const payer = Keypair.fromSecretKey( ); (async function () { - // Replace with your existing SPL mint (e.g. USDC) - const mint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS"); + const mint = await createMint( + rpc, + payer, + payer.publicKey, + null, + 9, + undefined, + undefined, + TOKEN_PROGRAM_ID + ); // One-time: Register the SPL interface PDA for this mint. // This creates the omnibus account that holds SPL tokens when wrapped to light-token. diff --git a/toolkits/payments-and-wallets/send-action.ts b/toolkits/payments-and-wallets/send-action.ts index d98566cb..abbe2fe5 100644 --- a/toolkits/payments-and-wallets/send-action.ts +++ b/toolkits/payments-and-wallets/send-action.ts @@ -58,14 +58,18 @@ const payer = Keypair.fromSecretKey( await wrap(rpc, payer, splAta, cTokenAta, payer, mint, BigInt(1_000_000)); const recipient = Keypair.generate(); + await createAtaInterface(rpc, payer, mint, recipient.publicKey); + const recipientAta = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey + ); - // Handles loading cold balances, creates recipient ATA, transfers. const sig = await transferInterface( rpc, payer, cTokenAta, mint, - recipient.publicKey, + recipientAta, payer, 100 ); diff --git a/toolkits/payments-and-wallets/send-and-receive.ts b/toolkits/payments-and-wallets/send-and-receive.ts index 9f726f2d..7300ac82 100644 --- a/toolkits/payments-and-wallets/send-and-receive.ts +++ b/toolkits/payments-and-wallets/send-and-receive.ts @@ -59,12 +59,17 @@ const payer = Keypair.fromSecretKey( // 4. Transfer from payer to recipient const recipient = Keypair.generate(); + await createAtaInterface(rpc, payer, mint, recipient.publicKey); + const recipientAta = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey + ); const txId = await transferInterface( rpc, payer, senderAta, mint, - recipient.publicKey, + recipientAta, payer, 100 ); diff --git a/toolkits/payments-and-wallets/send-instruction.ts b/toolkits/payments-and-wallets/send-instruction.ts index 0a0633aa..ed4afa37 100644 --- a/toolkits/payments-and-wallets/send-instruction.ts +++ b/toolkits/payments-and-wallets/send-instruction.ts @@ -64,16 +64,19 @@ const payer = Keypair.fromSecretKey( await wrap(rpc, payer, splAta, cTokenAta, payer, mint, BigInt(1_000_000)); const recipient = Keypair.generate(); + await createAtaInterface(rpc, payer, mint, recipient.publicKey); + const destination = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey + ); - // Returns TransactionInstruction[][]. Each inner array is one txn. - // Almost always this returns one atomic transaction. const instructions = await createTransferInterfaceInstructions( rpc, payer.publicKey, mint, 100, payer.publicKey, - recipient.publicKey + destination ); for (const ixs of instructions) { diff --git a/toolkits/sign-with-privy/CLAUDE.md b/toolkits/sign-with-privy/CLAUDE.md index 8e6c4bb9..d57aae2f 100644 --- a/toolkits/sign-with-privy/CLAUDE.md +++ b/toolkits/sign-with-privy/CLAUDE.md @@ -65,10 +65,10 @@ Both apps follow the same flow: build an unsigned `Transaction`, serialize with Standalone async functions, each creating their own `PrivyClient` and `createRpc`: -- `transfer.ts` — `createTransferInterfaceInstruction`, auto-loads cold balance via `createLoadAtaInstructionsFromInterface` +- `transfer.ts` — `createTransferInterfaceInstruction`, auto-loads cold balance via `createLoadAtaInstructions` - `wrap.ts` — `createWrapInstruction` with SPL interface lookup via `getSplInterfaceInfos` - `unwrap.ts` — `createUnwrapInstruction` from `@lightprotocol/compressed-token/unified` -- `load.ts` — `createLoadAtaInstructionsFromInterface` to consolidate cold + SPL + T22 into light-token ATA +- `load.ts` — `createLoadAtaInstructions` to consolidate cold + SPL + T22 into light-token ATA - `light-balance.ts` — queries hot (`getAtaInterface`) and cold (`getCompressedTokenBalancesByOwnerV2`) for a single mint; returns hot, cold, unified - `balances.ts` — queries hot (`getAtaInterface`), cold (`getCompressedTokenBalancesByOwnerV2`), SPL T22 (`getTokenAccountsByOwner` + raw data parsing) - `get-transaction-history.ts` — `getSignaturesForOwnerInterface` @@ -79,7 +79,7 @@ Standalone async functions, each creating their own `PrivyClient` and `createRpc - `mint-spl-and-wrap.ts` — `createMintInterface` + mint + wrap + transfer to treasury (filesystem wallet) - `mint-spl.ts` — `createMintToInstruction` to existing mint (filesystem wallet) - `register-spl-interface.ts` — `createSplInterface` on existing mint (filesystem wallet) -- `decompress.ts` — `decompressInterface` from light-token ATA to T22 ATA (filesystem wallet) +- `decompress.ts` — `loadAta` (or unwrap) from light-token ATA to T22 ATA (filesystem wallet) ### Transaction routing (React `TransferForm`) diff --git a/toolkits/sign-with-privy/nodejs/README.md b/toolkits/sign-with-privy/nodejs/README.md index 216af255..ce184d1b 100644 --- a/toolkits/sign-with-privy/nodejs/README.md +++ b/toolkits/sign-with-privy/nodejs/README.md @@ -16,7 +16,7 @@ Light Token gives you rent-free token accounts on Solana. Light-token accounts h | [**Transfer**](#operations) | `transferChecked()` | `createTransferInterfaceInstruction()` | | [**Wrap**](#operations) | N/A | `createWrapInstruction()` | | [**Unwrap**](#operations) | N/A | `createUnwrapInstruction()` | -| [**Load**](#operations) | N/A | `createLoadAtaInstructionsFromInterface()` | +| [**Load**](#operations) | N/A | `createLoadAtaInstructions()` | | [**Get balance**](#operations) | `getAccount()` | `getAtaInterface()` | | [**Transaction history**](#operations) | `getSignaturesForAddress()` | `getSignaturesForOwnerInterface()` | diff --git a/toolkits/sign-with-privy/react/src/hooks/useTransfer.ts b/toolkits/sign-with-privy/react/src/hooks/useTransfer.ts index c2295398..87266588 100644 --- a/toolkits/sign-with-privy/react/src/hooks/useTransfer.ts +++ b/toolkits/sign-with-privy/react/src/hooks/useTransfer.ts @@ -2,7 +2,11 @@ import { useState } from 'react'; import { PublicKey } from '@solana/web3.js'; import { createTransferInterfaceInstructions, + createAssociatedTokenAccountInterfaceIdempotentInstruction, + getAssociatedTokenAddressInterface, + DEFAULT_COMPRESSIBLE_CONFIG, } from '@lightprotocol/compressed-token/unified'; +import { LIGHT_TOKEN_PROGRAM_ID } from '@lightprotocol/stateless.js'; import { createRpc } from '@lightprotocol/stateless.js'; import type { ConnectedStandardSolanaWallet } from '@privy-io/js-sdk-core'; import { useSignTransaction } from '@privy-io/react-auth/solana'; @@ -40,13 +44,31 @@ export function useTransfer() { const mintPubkey = new PublicKey(mint); const recipient = new PublicKey(toAddress); const tokenAmount = Math.round(amount * Math.pow(10, decimals)); + const destination = getAssociatedTokenAddressInterface( + mintPubkey, + recipient, + ); + const createAtaIx = + createAssociatedTokenAccountInterfaceIdempotentInstruction( + owner, + destination, + recipient, + mintPubkey, + LIGHT_TOKEN_PROGRAM_ID, + undefined, + { compressibleConfig: DEFAULT_COMPRESSIBLE_CONFIG }, + ); - // Returns TransactionInstruction[][]. - // Each inner array is one transaction. - // Almost always returns just one. const instructions = await createTransferInterfaceInstructions( - rpc, owner, mintPubkey, tokenAmount, owner, recipient, + rpc, + owner, + mintPubkey, + tokenAmount, + owner, + destination, ); + const lastBatch = instructions[instructions.length - 1]; + lastBatch.unshift(createAtaIx); const signature = await signAndSendBatches(instructions, { rpc, diff --git a/toolkits/sign-with-wallet-adapter/react/src/hooks/useTransfer.ts b/toolkits/sign-with-wallet-adapter/react/src/hooks/useTransfer.ts index f56e523b..a7f37b52 100644 --- a/toolkits/sign-with-wallet-adapter/react/src/hooks/useTransfer.ts +++ b/toolkits/sign-with-wallet-adapter/react/src/hooks/useTransfer.ts @@ -2,7 +2,11 @@ import { useState } from 'react'; import { PublicKey } from '@solana/web3.js'; import { createTransferInterfaceInstructions, + createAssociatedTokenAccountInterfaceIdempotentInstruction, + getAssociatedTokenAddressInterface, + DEFAULT_COMPRESSIBLE_CONFIG, } from '@lightprotocol/compressed-token/unified'; +import { LIGHT_TOKEN_PROGRAM_ID } from '@lightprotocol/stateless.js'; import { createRpc } from '@lightprotocol/stateless.js'; import { signAndSendBatches, type SignTransactionFn } from './signAndSendBatches'; @@ -35,13 +39,31 @@ export function useTransfer() { const mintPubkey = new PublicKey(mint); const recipient = new PublicKey(toAddress); const tokenAmount = Math.round(amount * Math.pow(10, decimals)); + const destination = getAssociatedTokenAddressInterface( + mintPubkey, + recipient, + ); + const createAtaIx = + createAssociatedTokenAccountInterfaceIdempotentInstruction( + owner, + destination, + recipient, + mintPubkey, + LIGHT_TOKEN_PROGRAM_ID, + undefined, + { compressibleConfig: DEFAULT_COMPRESSIBLE_CONFIG }, + ); - // Returns TransactionInstruction[][]. - // Each inner array is one transaction. - // Almost always returns just one. const instructions = await createTransferInterfaceInstructions( - rpc, owner, mintPubkey, tokenAmount, owner, recipient, + rpc, + owner, + mintPubkey, + tokenAmount, + owner, + destination, ); + const lastBatch = instructions[instructions.length - 1]; + lastBatch.unshift(createAtaIx); const signature = await signAndSendBatches(instructions, { rpc, diff --git a/toolkits/sponsor-rent-top-ups/typescript/package.json b/toolkits/sponsor-rent-top-ups/typescript/package.json index c1982450..b3c5ae1c 100644 --- a/toolkits/sponsor-rent-top-ups/typescript/package.json +++ b/toolkits/sponsor-rent-top-ups/typescript/package.json @@ -7,7 +7,7 @@ "sponsor-top-ups": "tsx sponsor-top-ups.ts" }, "dependencies": { - "@lightprotocol/compressed-token": "^0.23.0-beta.9", - "@lightprotocol/stateless.js": "^0.23.0-beta.9" + "@lightprotocol/compressed-token": "beta", + "@lightprotocol/stateless.js": "beta" } } diff --git a/toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts b/toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts index 292f2232..509b5149 100644 --- a/toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts +++ b/toolkits/sponsor-rent-top-ups/typescript/sponsor-top-ups.ts @@ -1,7 +1,11 @@ import "dotenv/config"; import { Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; import { createRpc } from "@lightprotocol/stateless.js"; -import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified"; +import { + createTransferInterfaceInstructions, + getAssociatedTokenAddressInterface, + getOrCreateAtaInterface, +} from "@lightprotocol/compressed-token/unified"; import { homedir } from "os"; import { readFileSync } from "fs"; import { setup } from "./setup.js"; @@ -26,16 +30,19 @@ const sender = Keypair.generate(); const { mint } = await setup(rpc, sponsor, sender); const recipient = Keypair.generate(); + await getOrCreateAtaInterface(rpc, sponsor, mint, recipient.publicKey); + const destination = getAssociatedTokenAddressInterface( + mint, + recipient.publicKey, + ); - // Loads cold balances, creates recipient ATA if needed. - // Returns TransactionInstruction[][] — each inner array is one transaction. const instructions = await createTransferInterfaceInstructions( rpc, - sponsor.publicKey, // payer: sponsor covers rent top-ups + sponsor.publicKey, mint, 500_000, - sender.publicKey, // authority: user signs to authorize transfer - recipient.publicKey, + sender.publicKey, + destination, ); for (const ixs of instructions) { diff --git a/typescript-client/actions/create-spl-interface.ts b/typescript-client/actions/create-spl-interface.ts index e0ccdb3b..b922e4fd 100644 --- a/typescript-client/actions/create-spl-interface.ts +++ b/typescript-client/actions/create-spl-interface.ts @@ -1,7 +1,8 @@ import "dotenv/config"; -import { Keypair, PublicKey } from "@solana/web3.js"; +import { Keypair } from "@solana/web3.js"; import { createRpc } from "@lightprotocol/stateless.js"; import { createSplInterface } from "@lightprotocol/compressed-token"; +import { TOKEN_PROGRAM_ID, createMint } from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -18,7 +19,16 @@ const payer = Keypair.fromSecretKey( ); (async function () { - const existingMint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS"); + const existingMint = await createMint( + rpc, + payer, + payer.publicKey, + null, + 9, + undefined, + undefined, + TOKEN_PROGRAM_ID + ); // Register SPL interface PDA to enable interop with Light Tokens const tx = await createSplInterface(rpc, payer, existingMint); diff --git a/typescript-client/actions/delegate-approve.ts b/typescript-client/actions/delegate-approve.ts index bb0c59d6..830f3f19 100644 --- a/typescript-client/actions/delegate-approve.ts +++ b/typescript-client/actions/delegate-approve.ts @@ -3,9 +3,10 @@ import { Keypair } from "@solana/web3.js"; import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, - mintToCompressed, + mintTo, approve, } from "@lightprotocol/compressed-token"; +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -22,10 +23,17 @@ const payer = Keypair.fromSecretKey( ); (async function () { - const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await mintToCompressed(rpc, payer, mint, payer, [ - { recipient: payer.publicKey, amount: 1000n }, - ]); + const { mint } = await createMintInterface( + rpc, + payer, + payer, + null, + 9, + undefined, + undefined, + TOKEN_PROGRAM_ID + ); + await mintTo(rpc, payer, mint, payer.publicKey, payer, 1000); const delegate = Keypair.generate(); const tx = await approve(rpc, payer, mint, 500, payer, delegate.publicKey); diff --git a/typescript-client/actions/delegate-revoke.ts b/typescript-client/actions/delegate-revoke.ts index 7eb3d068..fbb36e49 100644 --- a/typescript-client/actions/delegate-revoke.ts +++ b/typescript-client/actions/delegate-revoke.ts @@ -3,10 +3,11 @@ import { Keypair } from "@solana/web3.js"; import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, - mintToCompressed, + mintTo, approve, revoke, } from "@lightprotocol/compressed-token"; +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -23,10 +24,17 @@ const payer = Keypair.fromSecretKey( ); (async function () { - const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await mintToCompressed(rpc, payer, mint, payer, [ - { recipient: payer.publicKey, amount: 1000n }, - ]); + const { mint } = await createMintInterface( + rpc, + payer, + payer, + null, + 9, + undefined, + undefined, + TOKEN_PROGRAM_ID + ); + await mintTo(rpc, payer, mint, payer.publicKey, payer, 1000); const delegate = Keypair.generate(); await approve(rpc, payer, mint, 500, payer, delegate.publicKey); diff --git a/typescript-client/actions/transfer-interface.ts b/typescript-client/actions/transfer-interface.ts index 71bce5b3..911a2ec7 100644 --- a/typescript-client/actions/transfer-interface.ts +++ b/typescript-client/actions/transfer-interface.ts @@ -41,14 +41,12 @@ const payer = Keypair.fromSecretKey( recipient.publicKey ); - // Transfer tokens between light-token associated token accounts - // destination is recipient wallet; transferInterface creates recipient ATA idempotently const tx = await transferInterface( rpc, payer, senderAta, mint, - recipient.publicKey, + recipientAta, sender, 500_000_000 ); diff --git a/typescript-client/actions/unwrap.ts b/typescript-client/actions/unwrap.ts index 2900f471..c712122d 100644 --- a/typescript-client/actions/unwrap.ts +++ b/typescript-client/actions/unwrap.ts @@ -4,13 +4,13 @@ import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, createAtaInterface, - mintToInterface, getAssociatedTokenAddressInterface, } from "@lightprotocol/compressed-token"; -import { unwrap } from "@lightprotocol/compressed-token/unified"; +import { unwrap, wrap } from "@lightprotocol/compressed-token/unified"; import { createAssociatedTokenAccount, - TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + mintTo, } from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -29,23 +29,33 @@ const payer = Keypair.fromSecretKey( (async function () { // Setup: Create and mint tokens to light-token associated token account - const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await createAtaInterface(rpc, payer, mint, payer.publicKey); - const destination = getAssociatedTokenAddressInterface( - mint, - payer.publicKey + const { mint } = await createMintInterface( + rpc, + payer, + payer, + null, + 9, + undefined, + undefined, + TOKEN_PROGRAM_ID ); - await mintToInterface(rpc, payer, mint, destination, payer, 1000); - - // Unwrap light-token to SPL associated token account const splAta = await createAssociatedTokenAccount( rpc, payer, mint, payer.publicKey, undefined, - TOKEN_2022_PROGRAM_ID + TOKEN_PROGRAM_ID ); + await mintTo(rpc, payer, mint, splAta, payer, 1000, [], undefined, TOKEN_PROGRAM_ID); + await createAtaInterface(rpc, payer, mint, payer.publicKey); + const destination = getAssociatedTokenAddressInterface( + mint, + payer.publicKey + ); + await wrap(rpc, payer, splAta, destination, payer, mint, BigInt(1000)); + + // Unwrap light-token to SPL associated token account const tx = await unwrap(rpc, payer, splAta, payer, mint, 500); console.log("Tx:", tx); diff --git a/typescript-client/actions/wrap.ts b/typescript-client/actions/wrap.ts index 83cf4873..921d8b6a 100644 --- a/typescript-client/actions/wrap.ts +++ b/typescript-client/actions/wrap.ts @@ -3,9 +3,7 @@ import { Keypair } from "@solana/web3.js"; import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, - createAtaInterface, mintToInterface, - decompressInterface, wrap, getAssociatedTokenAddressInterface, createAtaInterfaceIdempotent, @@ -31,13 +29,16 @@ const payer = Keypair.fromSecretKey( (async function () { // Setup: Get SPL tokens (needed to wrap) - const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await createAtaInterface(rpc, payer, mint, payer.publicKey); - const destination = getAssociatedTokenAddressInterface( - mint, - payer.publicKey + const { mint } = await createMintInterface( + rpc, + payer, + payer, + null, + 9, + undefined, + undefined, + TOKEN_2022_PROGRAM_ID ); - await mintToInterface(rpc, payer, mint, destination, payer, 1000); const splAta = await createAssociatedTokenAccount( rpc, payer, @@ -46,9 +47,18 @@ const payer = Keypair.fromSecretKey( undefined, TOKEN_2022_PROGRAM_ID ); - await decompressInterface(rpc, payer, payer, mint, 1000); + await mintToInterface( + rpc, + payer, + mint, + splAta, + payer, + 1000, + [], + undefined, + TOKEN_2022_PROGRAM_ID + ); - // Wrap SPL tokens to light-token associated token account const lightTokenAta = getAssociatedTokenAddressInterface( mint, payer.publicKey diff --git a/typescript-client/instructions/create-spl-interface.ts b/typescript-client/instructions/create-spl-interface.ts index 7a6736dd..1b027509 100644 --- a/typescript-client/instructions/create-spl-interface.ts +++ b/typescript-client/instructions/create-spl-interface.ts @@ -1,13 +1,17 @@ import "dotenv/config"; import { Keypair, - PublicKey, + SystemProgram, Transaction, sendAndConfirmTransaction, } from "@solana/web3.js"; import { createRpc } from "@lightprotocol/stateless.js"; import { LightTokenProgram } from "@lightprotocol/compressed-token"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { + MINT_SIZE, + TOKEN_PROGRAM_ID, + createInitializeMint2Instruction, +} from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -24,18 +28,40 @@ const payer = Keypair.fromSecretKey( ); (async function () { - const existingMint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS"); + const mintKeypair = Keypair.generate(); + const decimals = 9; + const rentExemptBalance = await rpc.getMinimumBalanceForRentExemption( + MINT_SIZE + ); + + const createMintAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_PROGRAM_ID, + space: MINT_SIZE, + }); + + const initializeMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, + null, + TOKEN_PROGRAM_ID + ); - // Register SPL interface PDA to enable interop with Light Tokens const ix = await LightTokenProgram.createSplInterface({ feePayer: payer.publicKey, - mint: existingMint, + mint: mintKeypair.publicKey, tokenProgramId: TOKEN_PROGRAM_ID, }); - const tx = new Transaction().add(ix); - const signature = await sendAndConfirmTransaction(rpc, tx, [payer]); + const tx = new Transaction().add(createMintAccountIx, initializeMintIx, ix); + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); - console.log("Mint:", existingMint.toBase58()); + console.log("Mint:", mintKeypair.publicKey.toBase58()); console.log("Tx:", signature); })(); diff --git a/typescript-client/instructions/load-ata.ts b/typescript-client/instructions/load-ata.ts index 6e6c2529..5c06f79a 100644 --- a/typescript-client/instructions/load-ata.ts +++ b/typescript-client/instructions/load-ata.ts @@ -51,8 +51,10 @@ const payer = Keypair.fromSecretKey( if (ixs.length === 0) return console.log("Nothing to load"); - const blockhash = await rpc.getLatestBlockhash(); - const tx = buildAndSignTx(ixs, payer, blockhash.blockhash); - const signature = await sendAndConfirmTx(rpc, tx); - console.log("Tx:", signature); + for (const batch of ixs) { + const blockhash = await rpc.getLatestBlockhash(); + const tx = buildAndSignTx(batch, payer, blockhash.blockhash); + const signature = await sendAndConfirmTx(rpc, tx); + console.log("Tx:", signature); + } })(); diff --git a/typescript-client/instructions/unwrap.ts b/typescript-client/instructions/unwrap.ts index 342f3338..c04613b9 100644 --- a/typescript-client/instructions/unwrap.ts +++ b/typescript-client/instructions/unwrap.ts @@ -8,13 +8,14 @@ import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, createAtaInterface, - mintToInterface, getAssociatedTokenAddressInterface, createUnwrapInstructions, + wrap, } from "@lightprotocol/compressed-token/unified"; import { createAssociatedTokenAccount, - TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + mintTo, } from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -33,24 +34,33 @@ const payer = Keypair.fromSecretKey( (async function () { // Setup: Create and mint tokens to light-token associated token account - const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await createAtaInterface(rpc, payer, mint, payer.publicKey); - const destination = getAssociatedTokenAddressInterface( - mint, - payer.publicKey + const { mint } = await createMintInterface( + rpc, + payer, + payer, + null, + 9, + undefined, + undefined, + TOKEN_PROGRAM_ID ); - await mintToInterface(rpc, payer, mint, destination, payer, 1000); - - // Create destination SPL ATA const splAta = await createAssociatedTokenAccount( rpc, payer, mint, payer.publicKey, undefined, - TOKEN_2022_PROGRAM_ID + TOKEN_PROGRAM_ID + ); + await mintTo(rpc, payer, mint, splAta, payer, 1000, [], undefined, TOKEN_PROGRAM_ID); + await createAtaInterface(rpc, payer, mint, payer.publicKey); + const destination = getAssociatedTokenAddressInterface( + mint, + payer.publicKey ); + await wrap(rpc, payer, splAta, destination, payer, mint, BigInt(1000)); + // Create destination SPL ATA // Returns TransactionInstruction[][]. Each inner array is one txn. // Handles loading cold state + unwrapping in one go. const instructions = await createUnwrapInstructions( diff --git a/typescript-client/instructions/wrap.ts b/typescript-client/instructions/wrap.ts index 49eb70d7..f4711c07 100644 --- a/typescript-client/instructions/wrap.ts +++ b/typescript-client/instructions/wrap.ts @@ -8,9 +8,7 @@ import { import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, - createAtaInterface, mintToInterface, - decompressInterface, createWrapInstruction, getAssociatedTokenAddressInterface, createAtaInterfaceIdempotent, @@ -37,13 +35,16 @@ const payer = Keypair.fromSecretKey( (async function () { // Setup: Get SPL tokens (needed to wrap) - const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await createAtaInterface(rpc, payer, mint, payer.publicKey); - const destination = getAssociatedTokenAddressInterface( - mint, - payer.publicKey + const { mint } = await createMintInterface( + rpc, + payer, + payer, + null, + 9, + undefined, + undefined, + TOKEN_2022_PROGRAM_ID ); - await mintToInterface(rpc, payer, mint, destination, payer, 1000); const splAta = await createAssociatedTokenAccount( rpc, payer, @@ -52,15 +53,26 @@ const payer = Keypair.fromSecretKey( undefined, TOKEN_2022_PROGRAM_ID ); - await decompressInterface(rpc, payer, payer, mint, 1000); + await mintToInterface( + rpc, + payer, + mint, + splAta, + payer, + 1000, + [], + undefined, + TOKEN_2022_PROGRAM_ID + ); - // Create wrap instruction const lightTokenAta = getAssociatedTokenAddressInterface( mint, payer.publicKey ); await createAtaInterfaceIdempotent(rpc, payer, mint, payer.publicKey); + // Create wrap instruction + const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); const splInterfaceInfo = splInterfaceInfos.find( (info) => info.isInitialized diff --git a/typescript-client/package.json b/typescript-client/package.json index d94ae090..7e698d6c 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -4,8 +4,8 @@ "type": "module", "description": "Working examples for light-token operations on Solana devnet", "dependencies": { - "@lightprotocol/compressed-token": "^0.23.0-beta.9", - "@lightprotocol/stateless.js": "^0.23.0-beta.9", + "@lightprotocol/compressed-token": "beta", + "@lightprotocol/stateless.js": "beta", "@solana/web3.js": "^1.98.0", "dotenv": "^16.0.0" }, @@ -23,17 +23,14 @@ "create-spl-mint:instruction": "tsx instructions/create-spl-mint.ts", "create-t22-mint:action": "tsx actions/create-t22-mint.ts", "create-t22-mint:instruction": "tsx instructions/create-t22-mint.ts", - "create-interface-pda:action": "tsx actions/create-interface-pda.ts", - "create-interface-pda:instruction": "tsx instructions/create-interface-pda.ts", + "create-interface-pda:action": "tsx actions/create-spl-interface.ts", + "create-interface-pda:instruction": "tsx instructions/create-spl-interface.ts", "mint-to:action": "tsx actions/mint-to.ts", "mint-to:instruction": "tsx instructions/mint-to.ts", "transfer-interface:action": "tsx actions/transfer-interface.ts", "transfer-interface:instruction": "tsx instructions/transfer-interface.ts", "create-ata:action": "tsx actions/create-ata.ts", "create-ata:instruction": "tsx instructions/create-ata.ts", - "compress:action": "tsx actions/compress.ts", - "compress:batch": "tsx actions/compress-batch.ts", - "decompress:action": "tsx actions/decompress.ts", "wrap:action": "tsx actions/wrap.ts", "wrap:instruction": "tsx instructions/wrap.ts", "unwrap:action": "tsx actions/unwrap.ts", @@ -41,7 +38,6 @@ "load-ata:action": "tsx actions/load-ata.ts", "load-ata:instruction": "tsx instructions/load-ata.ts", "delegate:approve": "tsx actions/delegate-approve.ts", - "delegate:revoke": "tsx actions/delegate-revoke.ts", - "merge-accounts": "tsx actions/merge-token-accounts.ts" + "delegate:revoke": "tsx actions/delegate-revoke.ts" } }