From 6e317431514d6a742dc391741e1959a7cdd49b6d Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Thu, 19 Feb 2026 00:25:22 +0000 Subject: [PATCH 1/6] refactor(cli): rename compress/decompress to wrap/unwrap and update commands - Rename compress-sol/compress-spl to wrap-sol/wrap-spl - Rename decompress-sol/decompress-spl to unwrap-sol/unwrap-spl - Remove deprecated commands: approve-and-mint-to, create-token-pool, merge-token-accounts - Add new commands: create-interface-pda, create-token-account - Export unwrap and decompressMint from compressed-token SDK - Update tests and README to match new command names --- cli/README.md | 59 ++++++++--- cli/package.json | 15 ++- cli/src/commands/approve-and-mint-to/index.ts | 81 ---------------- cli/src/commands/balance/index.ts | 4 +- .../index.ts | 16 +-- cli/src/commands/create-mint/index.ts | 22 +++-- .../commands/create-token-account/index.ts | 92 ++++++++++++++++++ cli/src/commands/init/index.ts | 2 +- .../commands/merge-token-accounts/index.ts | 58 ----------- cli/src/commands/mint-to/index.ts | 14 ++- cli/src/commands/token-balance/index.ts | 55 ++++++----- cli/src/commands/transfer/index.ts | 16 ++- .../{decompress-sol => unwrap-sol}/index.ts | 20 ++-- .../{decompress-spl => unwrap-spl}/index.ts | 35 ++++--- .../{compress-sol => wrap-sol}/index.ts | 20 ++-- .../{compress-spl => wrap-spl}/index.ts | 48 +++++---- .../approve-and-mint-to/index.test.ts | 46 --------- cli/test/commands/balance/index.test.ts | 16 +-- .../index.test.ts | 6 +- .../commands/create-interface-pda/index.ts | 48 +++++++++ cli/test/commands/create-mint/index.test.ts | 5 +- .../create-token-account/index.test.ts | 36 +++++++ .../merge-token-accounts/index.test.ts | 55 ----------- cli/test/commands/mint-to/index.test.ts | 8 +- cli/test/commands/token-balance/index.test.ts | 15 +-- cli/test/commands/transfer/index.test.ts | 13 ++- .../index.test.ts | 58 +++++------ cli/test/commands/unwrap-sol/index.ts | 57 +++++++++++ .../index.test.ts | 24 ++--- cli/test/commands/unwrap-spl/index.ts | 92 ++++++++++++++++++ .../{compress-sol => wrap-sol}/index.test.ts | 20 ++-- cli/test/commands/wrap-sol/index.ts | 59 +++++++++++ .../{compress-spl => wrap-spl}/index.test.ts | 35 +++---- cli/test/commands/wrap-spl/index.ts | 97 +++++++++++++++++++ cli/test/helpers/helpers.ts | 65 ++++++++++--- js/compressed-token/src/index.ts | 2 + 36 files changed, 818 insertions(+), 496 deletions(-) delete mode 100644 cli/src/commands/approve-and-mint-to/index.ts rename cli/src/commands/{create-token-pool => create-interface-pda}/index.ts (65%) create mode 100644 cli/src/commands/create-token-account/index.ts delete mode 100644 cli/src/commands/merge-token-accounts/index.ts rename cli/src/commands/{decompress-sol => unwrap-sol}/index.ts (64%) rename cli/src/commands/{decompress-spl => unwrap-spl}/index.ts (65%) rename cli/src/commands/{compress-sol => wrap-sol}/index.ts (66%) rename cli/src/commands/{compress-spl => wrap-spl}/index.ts (59%) delete mode 100644 cli/test/commands/approve-and-mint-to/index.test.ts rename cli/test/commands/{create-token-pool => create-interface-pda}/index.test.ts (89%) create mode 100644 cli/test/commands/create-interface-pda/index.ts create mode 100644 cli/test/commands/create-token-account/index.test.ts delete mode 100644 cli/test/commands/merge-token-accounts/index.test.ts rename cli/test/commands/{decompress-sol => unwrap-sol}/index.test.ts (57%) create mode 100644 cli/test/commands/unwrap-sol/index.ts rename cli/test/commands/{decompress-spl => unwrap-spl}/index.test.ts (62%) create mode 100644 cli/test/commands/unwrap-spl/index.ts rename cli/test/commands/{compress-sol => wrap-sol}/index.test.ts (76%) create mode 100644 cli/test/commands/wrap-sol/index.ts rename cli/test/commands/{compress-spl => wrap-spl}/index.test.ts (58%) create mode 100644 cli/test/commands/wrap-spl/index.ts diff --git a/cli/README.md b/cli/README.md index 4ed917026c..2f940212a0 100644 --- a/cli/README.md +++ b/cli/README.md @@ -1,6 +1,6 @@ # ZK Compression CLI -CLI to interact with compressed accounts and compressed tokens on Solana. +CLI to interact with compressed accounts and Light Tokens on Solana. ## Requirements @@ -119,7 +119,7 @@ solana address ### Commands -#### Create a compressed token mint +#### Create a Light Token mint ```bash light create-mint @@ -140,7 +140,25 @@ FLAGS random keypair. ``` -#### Mint compressed tokens to a Solana wallet +#### Create a token account + +```bash +light create-token-account YOUR_MINT_ADDRESS +``` + +``` +USAGE + $ light create-token-account MINT [--owner ] + +ARGUMENTS + MINT Base58 encoded mint address. + +FLAGS + --owner= Owner of the token account. Defaults to the fee + payer's public key. +``` + +#### Mint Light Tokens to a Solana wallet ```bash light mint-to --mint "YOUR_MINT_ADDRESS" --to "YOUR_WALLET_ADDRESS" --amount 4200000000 @@ -159,7 +177,7 @@ FLAGS --to= (required) Recipient address. ``` -#### Transfer compressed tokens from one wallet to another +#### Transfer Light Tokens from one wallet to another ```bash light transfer --mint "YOUR_MINT_ADDRESS" --to "RECIPIENT_WALLET_ADDRESS" --amount 4200000000 @@ -179,36 +197,53 @@ FLAGS ``` -#### Assign native SOL to a compressed account +#### Wrap SOL into compressed account ```bash -light compress-sol --amount 1000 --to "YOUR_WALLET_ADDRESS_BASE58" +light wrap-sol --amount 1000 --to "YOUR_WALLET_ADDRESS_BASE58" ``` ``` USAGE - $ light compress-sol --to --amount + $ light wrap-sol --to --amount FLAGS - --amount= (required) Amount to compress in lamports. + --amount= (required) Amount to wrap in lamports. --to= (required) Specify the recipient address. ``` -#### Decompress into native SOL +#### Unwrap SOL from compressed account ```bash -light decompress-sol --amount 42 --to "YOUR_WALLET_ADDRESS_BASE58" +light unwrap-sol --amount 42 --to "YOUR_WALLET_ADDRESS_BASE58" ``` ``` USAGE - $ light decompress-sol --to --amount + $ light unwrap-sol --to --amount FLAGS - --amount= (required) Amount to decompress in lamports. + --amount= (required) Amount to unwrap in lamports. --to= (required) Specify the recipient address. ``` +#### Get token balance + +```bash +light token-balance --mint "YOUR_MINT_ADDRESS" --owner "OWNER_ADDRESS" +``` + +``` +USAGE + $ light token-balance --mint --owner + +FLAGS + --mint= (required) Mint address of the token account. + --owner= (required) Address of the token owner. +``` + +Displays light token account (hot), compressed light token (cold), and total balances. + ### Support - Always feel free to join the [Developer Discord](https://discord.gg/D2cEphnvcY) for help! diff --git a/cli/package.json b/cli/package.json index c29bcd265e..a9b8c2ab94 100644 --- a/cli/package.json +++ b/cli/package.json @@ -105,21 +105,20 @@ "test-utils": "mocha ./test/utils/index.test.ts -t 10000000 --exit", "test-config": "mocha ./test/commands/config/index.test.ts -t 10000000 --exit", "test-create-mint": "mocha ./test/commands/create-mint/index.test.ts -t 10000000 --exit", - "test-merge-token-accounts": "mocha ./test/commands/merge-token-accounts/index.test.ts -t 10000000 --exit", - "test-create-token-pool": "mocha ./test/commands/create-token-pool/index.test.ts -t 10000000 --exit", - "test-approve-and-mint-to": "mocha ./test/commands/approve-and-mint-to/index.test.ts -t 10000000 --exit", + "test-create-interface-pda": "mocha ./test/commands/create-interface-pda/index.test.ts -t 10000000 --exit", + "test-create-token-account": "mocha ./test/commands/create-token-account/index.test.ts -t 10000000 --exit", "test-mint-to": "mocha ./test/commands/mint-to/index.test.ts -t 10000000 --exit", "test-transfer": "mocha ./test/commands/transfer/index.test.ts -t 10000000 --exit", "test-token-balance": "mocha test/commands/token-balance/index.test.ts -t 10000000 --exit", - "test-compress-sol": "mocha ./test/commands/compress-sol/index.test.ts -t 10000000 --exit", + "test-wrap-sol": "mocha ./test/commands/wrap-sol/index.test.ts -t 10000000 --exit", "test-balance": "mocha ./test/commands/balance/index.test.ts -t 10000000 --exit", - "test-decompress-sol": "mocha ./test/commands/decompress-sol/index.test.ts -t 10000000 --exit", - "test-compress-spl": "mocha ./test/commands/compress-spl/index.test.ts -t 10000000 --exit", - "test-decompress-spl": "mocha ./test/commands/decompress-spl/index.test.ts -t 10000000 --exit", + "test-unwrap-sol": "mocha ./test/commands/unwrap-sol/index.test.ts -t 10000000 --exit", + "test-wrap-spl": "mocha ./test/commands/wrap-spl/index.test.ts -t 10000000 --exit", + "test-unwrap-spl": "mocha ./test/commands/unwrap-spl/index.test.ts -t 10000000 --exit", "test-test-validator": "mocha ./test/commands/test-validator/index.test.ts -t 10000000 --exit", "kill": "killall solana-test-validator || true && killall solana-test-val || true && sleep 1", "test-cli": "pnpm test-config && pnpm kill", - "test": "pnpm kill && pnpm test-cli && pnpm test-utils && pnpm test-create-mint && pnpm test-mint-to && pnpm test-transfer && pnpm test-merge-token-accounts && pnpm test-create-token-pool && pnpm test-compress-spl && pnpm test-decompress-spl && pnpm test-token-balance && pnpm test-compress-sol && pnpm test-balance && pnpm test-decompress-sol && pnpm test-approve-and-mint-to && pnpm test-test-validator", + "test": "pnpm kill && pnpm test-cli && pnpm test-utils && pnpm test-create-mint && pnpm test-create-token-account && pnpm test-mint-to && pnpm test-transfer && pnpm test-create-interface-pda && pnpm test-wrap-spl && pnpm test-unwrap-spl && pnpm test-token-balance && pnpm test-wrap-sol && pnpm test-balance && pnpm test-unwrap-sol && pnpm test-test-validator", "install-local": "pnpm build && pnpm global remove @lightprotocol/zk-compression-cli || true && pnpm global add $PWD", "version": "oclif readme && git add README.md" }, diff --git a/cli/src/commands/approve-and-mint-to/index.ts b/cli/src/commands/approve-and-mint-to/index.ts deleted file mode 100644 index ca92b0f311..0000000000 --- a/cli/src/commands/approve-and-mint-to/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Command, Flags } from "@oclif/core"; -import { - CustomLoader, - defaultSolanaWalletKeypair, - generateSolanaTransactionURL, - getKeypairFromFile, - rpc, -} from "../../utils/utils"; -import { Keypair, PublicKey } from "@solana/web3.js"; -import { approveAndMintTo } from "@lightprotocol/compressed-token"; - -class ApproveAndMintToCommand extends Command { - static summary = - "Mint tokens to a compressed account via external mint authority"; - - static examples = [ - "$ light approve-and-mint-to --mint PublicKey --to PublicKey --amount 1000", - ]; - - static flags = { - "mint-authority": Flags.string({ - description: - "Specify the filepath of the mint authority keypair. Defaults to your local solana wallet.", - required: false, - }), - mint: Flags.string({ - description: "Specify the mint address.", - required: true, - }), - to: Flags.string({ - description: "Specify the recipient address.", - required: true, - }), - amount: Flags.integer({ - description: "Amount to mint, in tokens.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(ApproveAndMintToCommand); - const { mint, to, amount } = flags; - if (!mint || !to || !amount) { - throw new Error("Invalid arguments"); - } - - const loader = new CustomLoader(`Performing approve-and-mint-to...\n`); - loader.start(); - - try { - const mintPublicKey = new PublicKey(mint); - const toPublicKey = new PublicKey(to); - const payer = defaultSolanaWalletKeypair(); - let mintAuthority: Keypair = payer; - if (flags["mint-authority"] !== undefined) { - mintAuthority = await getKeypairFromFile(flags["mint-authority"]); - } - - const txId = await approveAndMintTo( - rpc(), - payer, - mintPublicKey, - toPublicKey, - mintAuthority, - amount, - ); - loader.stop(false); - console.log( - "\u001B[1mMint tx:\u001B[0m", - generateSolanaTransactionURL("tx", txId, "custom"), - ); - console.log("approve-and-mint-to successful"); - } catch (error) { - this.error(`Failed to approve-and-mint-to!\n${error}`); - } - } -} - -export default ApproveAndMintToCommand; diff --git a/cli/src/commands/balance/index.ts b/cli/src/commands/balance/index.ts index ea5c33820c..d029b8ca23 100644 --- a/cli/src/commands/balance/index.ts +++ b/cli/src/commands/balance/index.ts @@ -3,7 +3,7 @@ import { CustomLoader, rpc } from "../../utils/utils"; import { PublicKey } from "@solana/web3.js"; class BalanceCommand extends Command { - static summary = "Get compressed SOL balance"; + static summary = "Get wrapped SOL balance"; static examples = ["$ light balance --owner=
"]; static flags = { @@ -38,7 +38,7 @@ class BalanceCommand extends Command { } console.log( - "\u001B[1mCompressed SOL balance:\u001B[0m", + "\u001B[1mWrapped SOL balance:\u001B[0m", totalAmount.toString(), ); } catch (error) { diff --git a/cli/src/commands/create-token-pool/index.ts b/cli/src/commands/create-interface-pda/index.ts similarity index 65% rename from cli/src/commands/create-token-pool/index.ts rename to cli/src/commands/create-interface-pda/index.ts index 66794c389a..5873dea9e8 100644 --- a/cli/src/commands/create-token-pool/index.ts +++ b/cli/src/commands/create-interface-pda/index.ts @@ -9,10 +9,10 @@ import { import { PublicKey } from "@solana/web3.js"; import { createSplInterface } from "@lightprotocol/compressed-token"; -class RegisterMintCommand extends Command { - static summary = "Register an existing mint with the CompressedToken program"; +class CreateInterfacePdaCommand extends Command { + static summary = "Create an SPL interface PDA for an existing mint"; - static examples = ["$ light create-token-pool --mint-decimals 5"]; + static examples = ["$ light create-interface-pda --mint "]; static flags = { mint: Flags.string({ @@ -24,9 +24,9 @@ class RegisterMintCommand extends Command { static args = {}; async run() { - const { flags } = await this.parse(RegisterMintCommand); + const { flags } = await this.parse(CreateInterfacePdaCommand); - const loader = new CustomLoader(`Performing create-token-pool...\n`); + const loader = new CustomLoader(`Creating SPL interface PDA...\n`); loader.start(); try { const payer = defaultSolanaWalletKeypair(); @@ -38,11 +38,11 @@ class RegisterMintCommand extends Command { "\x1b[1mMint tx:\x1b[0m ", generateSolanaTransactionURL("tx", txId, "custom"), ); - console.log("create-token-pool successful"); + console.log("create-interface-pda successful"); } catch (error) { - this.error(`Failed to create-token-pool!\n${error}`); + this.error(`Failed to create-interface-pda!\n${error}`); } } } -export default RegisterMintCommand; +export default CreateInterfacePdaCommand; diff --git a/cli/src/commands/create-mint/index.ts b/cli/src/commands/create-mint/index.ts index 1ed4fcf2b4..b7288975ef 100644 --- a/cli/src/commands/create-mint/index.ts +++ b/cli/src/commands/create-mint/index.ts @@ -6,13 +6,16 @@ import { getKeypairFromFile, rpc, } from "../../utils/utils"; -import { createMint } from "@lightprotocol/compressed-token"; -import { Keypair, PublicKey } from "@solana/web3.js"; +import { + createMintInterface, + decompressMint, +} from "@lightprotocol/compressed-token"; +import { Keypair } from "@solana/web3.js"; const DEFAULT_DECIMAL_COUNT = 9; class CreateMintCommand extends Command { - static summary = "Create a new compressed token mint"; + static summary = "Create a new Light Token mint"; static examples = ["$ light create-mint --mint-decimals 5"]; @@ -23,7 +26,8 @@ class CreateMintCommand extends Command { required: false, }), "mint-authority": Flags.string({ - description: "Address of the mint authority. Defaults to the fee payer", + description: + "Path to the mint authority keypair file. Defaults to the fee payer.", required: false, }), "mint-decimals": Flags.integer({ @@ -44,14 +48,16 @@ class CreateMintCommand extends Command { const payer = defaultSolanaWalletKeypair(); const mintDecimals = this.getMintDecimals(flags); const mintKeypair = await this.getMintKeypair(flags); - const mintAuthority = await this.getMintAuthority(flags, payer.publicKey); - const { mint, transactionSignature } = await createMint( + const mintAuthority = await this.getMintAuthority(flags, payer); + const { mint, transactionSignature } = await createMintInterface( rpc(), payer, mintAuthority, + null, mintDecimals, mintKeypair, ); + await decompressMint(rpc(), payer, mint); loader.stop(false); console.log("\x1b[1mMint public key:\x1b[0m ", mint.toBase58()); console.log( @@ -76,9 +82,9 @@ class CreateMintCommand extends Command { return await getKeypairFromFile(mintKeypairFilePath); } - async getMintAuthority(flags: any, feePayer: PublicKey): Promise { + async getMintAuthority(flags: any, feePayer: Keypair): Promise { return flags["mint-authority"] - ? new PublicKey(flags["mint-authority"]) + ? await getKeypairFromFile(flags["mint-authority"]) : feePayer; } } diff --git a/cli/src/commands/create-token-account/index.ts b/cli/src/commands/create-token-account/index.ts new file mode 100644 index 0000000000..4aa4278ac3 --- /dev/null +++ b/cli/src/commands/create-token-account/index.ts @@ -0,0 +1,92 @@ +import { Args, Command, Flags } from "@oclif/core"; +import { + CustomLoader, + defaultSolanaWalletKeypair, + generateSolanaTransactionURL, + rpc, +} from "../../utils/utils"; +import { PublicKey } from "@solana/web3.js"; +import { + createAtaInterfaceIdempotent, + decompressMint, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; + +class CreateTokenAccountCommand extends Command { + static summary = + "Create an associated token account for a given mint and owner."; + + static examples = [ + "$ light create-token-account ", + "$ light create-token-account --owner ", + ]; + + static args = { + mint: Args.string({ + description: "Base58 encoded mint address.", + required: true, + }), + }; + + static flags = { + owner: Flags.string({ + description: + "Owner of the token account. Defaults to the fee payer's public key.", + required: false, + }), + }; + + async run() { + const { args, flags } = await this.parse(CreateTokenAccountCommand); + + const loader = new CustomLoader( + `Performing create-token-account...\n`, + ); + loader.start(); + + try { + const payer = defaultSolanaWalletKeypair(); + const mintPublicKey = new PublicKey(args.mint); + const ownerPublicKey = flags.owner + ? new PublicKey(flags.owner) + : payer.publicKey; + + try { + await decompressMint(rpc(), payer, mintPublicKey); + } catch { + // Mint may already be decompressed; ignore. + } + + await createAtaInterfaceIdempotent( + rpc(), + payer, + mintPublicKey, + ownerPublicKey, + ); + + const ataAddress = getAssociatedTokenAddressInterface( + mintPublicKey, + ownerPublicKey, + ); + + loader.stop(false); + console.log( + "\x1b[1mToken account:\x1b[0m ", + ataAddress.toBase58(), + ); + console.log( + "\x1b[1mAccount address:\x1b[0m ", + generateSolanaTransactionURL( + "address", + ataAddress.toBase58(), + "custom", + ), + ); + console.log("create-token-account successful"); + } catch (error) { + this.error(`Failed to create-token-account!\n${error}`); + } + } +} + +export default CreateTokenAccountCommand; diff --git a/cli/src/commands/init/index.ts b/cli/src/commands/init/index.ts index 726123d5d0..b9740c7d0b 100644 --- a/cli/src/commands/init/index.ts +++ b/cli/src/commands/init/index.ts @@ -37,7 +37,7 @@ import { } from "case-anything"; export default class InitCommand extends Command { - static description = "Initialize a compressed account project."; + static description = "Initialize a Light Protocol project."; static args = { name: Args.string({ diff --git a/cli/src/commands/merge-token-accounts/index.ts b/cli/src/commands/merge-token-accounts/index.ts deleted file mode 100644 index ef0d2bdf20..0000000000 --- a/cli/src/commands/merge-token-accounts/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Command, Flags } from "@oclif/core"; -import { - CustomLoader, - defaultSolanaWalletKeypair, - generateSolanaTransactionURL, - getKeypairFromFile, - rpc, -} from "../../utils/utils"; -import { mergeTokenAccounts } from "@lightprotocol/compressed-token"; -import { PublicKey } from "@solana/web3.js"; - -class MergeTokenAccountsCommand extends Command { - static summary = "Merge all token accounts for a specific mint."; - - static examples = ["$ light merge-token-accounts --mint PublicKey"]; - - static flags = { - mint: Flags.string({ - description: "Mint to merge accounts for", - required: true, - }), - "fee-payer": Flags.string({ - description: - "Specify the fee-payer account. Defaults to the client keypair.", - required: false, - }), - }; - - async run() { - const { flags } = await this.parse(MergeTokenAccountsCommand); - - const loader = new CustomLoader(`Merging token accounts...\n`); - loader.start(); - - try { - const mint = new PublicKey(flags.mint); - - let payer = defaultSolanaWalletKeypair(); - if (flags["fee-payer"]) { - payer = await getKeypairFromFile(flags["fee-payer"]); - } - - const txId = await mergeTokenAccounts(rpc(), payer, mint, payer); - - loader.stop(false); - console.log( - `\x1b[1mMerge tx:\x1b[0m `, - generateSolanaTransactionURL("tx", txId, "custom"), - ); - - console.log("Token accounts merged successfully"); - } catch (error) { - this.error(`Failed to merge token accounts!\n${error}`); - } - } -} - -export default MergeTokenAccountsCommand; diff --git a/cli/src/commands/mint-to/index.ts b/cli/src/commands/mint-to/index.ts index fa126e0d35..e300a4dbb5 100644 --- a/cli/src/commands/mint-to/index.ts +++ b/cli/src/commands/mint-to/index.ts @@ -7,7 +7,11 @@ import { rpc, } from "../../utils/utils"; import { Keypair, PublicKey } from "@solana/web3.js"; -import { mintTo } from "@lightprotocol/compressed-token"; +import { + mintToInterface, + createAtaInterfaceIdempotent, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; class MintToCommand extends Command { static summary = "Mint tokens to an account."; @@ -60,11 +64,13 @@ class MintToCommand extends Command { mintAuthority = await getKeypairFromFile(flags["mint-authority"]); } - const txId = await mintTo( + await createAtaInterfaceIdempotent(rpc(), payer, mintPublicKey, toPublicKey); + const destination = getAssociatedTokenAddressInterface(mintPublicKey, toPublicKey); + const txId = await mintToInterface( rpc(), payer, mintPublicKey, - toPublicKey, + destination, mintAuthority, amount, ); @@ -75,7 +81,7 @@ class MintToCommand extends Command { ); console.log("mint-to successful"); } catch (error) { - this.error(`Failed to create-mint!\n${error}`); + this.error(`Failed to mint-to!\n${error}`); } } } diff --git a/cli/src/commands/token-balance/index.ts b/cli/src/commands/token-balance/index.ts index 19471f1c61..23f4de486f 100644 --- a/cli/src/commands/token-balance/index.ts +++ b/cli/src/commands/token-balance/index.ts @@ -1,9 +1,10 @@ import { Command, Flags } from "@oclif/core"; import { CustomLoader, rpc } from "../../utils/utils"; import { PublicKey } from "@solana/web3.js"; +import { getAssociatedTokenAddressInterface } from "@lightprotocol/compressed-token"; class TokenBalanceCommand extends Command { - static summary = "Get balance"; + static summary = "Get token balance (light token account + compressed)"; static examples = [ "$ light token-balance --mint=
--owner=
", @@ -11,11 +12,11 @@ class TokenBalanceCommand extends Command { static flags = { owner: Flags.string({ - description: "Address of the compressed token owner.", + description: "Address of the token owner.", required: true, }), mint: Flags.string({ - description: "Mint address of the compressed token account.", + description: "Mint address of the token account.", required: true, }), }; @@ -30,38 +31,40 @@ class TokenBalanceCommand extends Command { const refMint = new PublicKey(flags["mint"]); const refOwner = new PublicKey(flags["owner"]); + // Fetch light token account (hot) balance + let hotBalance = BigInt(0); + const ataAddress = getAssociatedTokenAddressInterface(refMint, refOwner); + try { + const ataBalance = await rpc().getTokenAccountBalance(ataAddress); + hotBalance = BigInt(ataBalance.value.amount); + } catch { + // Token account may not exist; treat as zero. + } + + // Fetch compressed (cold) balance + let coldBalance = BigInt(0); const tokenAccounts = await rpc().getCompressedTokenAccountsByOwner( refOwner, { mint: refMint }, ); + tokenAccounts.items.forEach((account: any) => { + coldBalance += BigInt(account.parsed.amount.toString()); + }); + loader.stop(false); - // Handle case when no token accounts are found - if (tokenAccounts.items.length === 0) { - console.log("\x1b[1mBalance:\x1b[0m 0"); - console.log("No token accounts found"); - return; - } + const totalBalance = hotBalance + coldBalance; - const compressedTokenAccounts = tokenAccounts.items.filter((acc: any) => - acc.parsed.mint.equals(refMint), + console.log( + `\x1b[1mLight token account balance:\x1b[0m ${hotBalance.toString()}`, + ); + console.log( + `\x1b[1mCompressed light token balance:\x1b[0m ${coldBalance.toString()}`, + ); + console.log( + `\x1b[1mTotal balance:\x1b[0m ${totalBalance.toString()}`, ); - - if (compressedTokenAccounts.length === 0) { - console.log("\x1b[1mBalance:\x1b[0m 0"); - console.log("No token accounts found for this mint"); - return; - } - - let totalBalance = BigInt(0); - - compressedTokenAccounts.forEach((account: any) => { - const amount = account.parsed.amount; - totalBalance += BigInt(amount.toString()); - }); - - console.log(`\x1b[1mBalance:\x1b[0m ${totalBalance.toString()}`); } catch (error) { this.error(`Failed to get balance!\n${error}`); } diff --git a/cli/src/commands/transfer/index.ts b/cli/src/commands/transfer/index.ts index 32ec740494..9aae910c5f 100644 --- a/cli/src/commands/transfer/index.ts +++ b/cli/src/commands/transfer/index.ts @@ -6,7 +6,11 @@ import { getKeypairFromFile, rpc, } from "../../utils/utils"; -import { transfer } from "@lightprotocol/compressed-token"; +import { + transferInterface, + createAtaInterfaceIdempotent, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; import { PublicKey } from "@solana/web3.js"; class TransferCommand extends Command { @@ -57,13 +61,17 @@ class TransferCommand extends Command { if (flags["fee-payer"] !== undefined) { payer = await getKeypairFromFile(flags["fee-payer"]); } - const txId = await transfer( + await createAtaInterfaceIdempotent(rpc(), payer, mintPublicKey, toPublicKey); + const source = getAssociatedTokenAddressInterface(mintPublicKey, payer.publicKey); + const destination = getAssociatedTokenAddressInterface(mintPublicKey, toPublicKey); + const txId = await transferInterface( rpc(), payer, + source, mintPublicKey, - amount, + destination, payer, - toPublicKey, + amount, ); loader.stop(false); console.log( diff --git a/cli/src/commands/decompress-sol/index.ts b/cli/src/commands/unwrap-sol/index.ts similarity index 64% rename from cli/src/commands/decompress-sol/index.ts rename to cli/src/commands/unwrap-sol/index.ts index c32cae20bf..ee3407d037 100644 --- a/cli/src/commands/decompress-sol/index.ts +++ b/cli/src/commands/unwrap-sol/index.ts @@ -8,10 +8,10 @@ import { import { PublicKey } from "@solana/web3.js"; import { decompress } from "@lightprotocol/stateless.js"; -class DecompressSolCommand extends Command { - static summary = "Decompress SOL."; +class UnwrapSolCommand extends Command { + static summary = "Unwrap SOL from compressed account."; - static examples = ["$ light decompress-sol --to PublicKey --amount 10"]; + static examples = ["$ light unwrap-sol --to PublicKey --amount 10"]; static flags = { to: Flags.string({ @@ -19,7 +19,7 @@ class DecompressSolCommand extends Command { required: true, }), amount: Flags.integer({ - description: "Amount to decompress, in lamports.", + description: "Amount to unwrap, in lamports.", required: true, }), }; @@ -27,14 +27,14 @@ class DecompressSolCommand extends Command { static args = {}; async run() { - const { flags } = await this.parse(DecompressSolCommand); + const { flags } = await this.parse(UnwrapSolCommand); const to = flags["to"]; const amount = flags["amount"]; if (!to || !amount) { throw new Error("Invalid arguments"); } - const loader = new CustomLoader(`Performing decompress-sol...\n`); + const loader = new CustomLoader(`Performing unwrap-sol...\n`); loader.start(); try { @@ -44,14 +44,14 @@ class DecompressSolCommand extends Command { const txId = await decompress(rpc(), payer, amount, toPublicKey); loader.stop(false); console.log( - "\x1b[32mdecompress-sol:\x1b[0m ", + "\x1b[32munwrap-sol:\x1b[0m ", generateSolanaTransactionURL("tx", txId, "custom"), ); - console.log("decompress-sol successful"); + console.log("unwrap-sol successful"); } catch (error) { - this.error(`Failed to decompress-sol!\n${error}`); + this.error(`Failed to unwrap-sol!\n${error}`); } } } -export default DecompressSolCommand; +export default UnwrapSolCommand; diff --git a/cli/src/commands/decompress-spl/index.ts b/cli/src/commands/unwrap-spl/index.ts similarity index 65% rename from cli/src/commands/decompress-spl/index.ts rename to cli/src/commands/unwrap-spl/index.ts index de64e9f006..b998f06d1f 100644 --- a/cli/src/commands/decompress-spl/index.ts +++ b/cli/src/commands/unwrap-spl/index.ts @@ -6,16 +6,15 @@ import { rpc, } from "../../utils/utils"; import { PublicKey } from "@solana/web3.js"; -import { decompress } from "@lightprotocol/compressed-token"; +import { unwrap, CompressedTokenProgram } from "@lightprotocol/compressed-token"; import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; -import { CompressedTokenProgram } from "@lightprotocol/compressed-token"; -/// TODO: add ability to decompress from non-fee payer -class DecompressSplCommand extends Command { - static summary = "Decompress into SPL tokens."; +/// TODO: add ability to unwrap from non-fee payer +class UnwrapSplCommand extends Command { + static summary = "Unwrap Light Tokens into SPL token account."; static examples = [ - "$ light decompress-spl --mint PublicKey --to PublicKey --amount 10", + "$ light unwrap-spl --mint PublicKey --to PublicKey --amount 10", ]; static flags = { @@ -25,11 +24,11 @@ class DecompressSplCommand extends Command { }), to: Flags.string({ description: - "Specify the recipient address. (owner of destination token account)", + "Specify the recipient address. (owner of destination SPL token account)", required: true, }), amount: Flags.integer({ - description: "Amount to decompress, in tokens.", + description: "Amount to unwrap, in tokens.", required: true, }), }; @@ -37,7 +36,7 @@ class DecompressSplCommand extends Command { static args = {}; async run() { - const { flags } = await this.parse(DecompressSplCommand); + const { flags } = await this.parse(UnwrapSplCommand); const to = flags["to"]; const mint = flags["mint"]; const amount = flags["amount"]; @@ -45,7 +44,7 @@ class DecompressSplCommand extends Command { throw new Error("Invalid arguments"); } - const loader = new CustomLoader(`Performing decompress-spl...\n`); + const loader = new CustomLoader(`Performing unwrap-spl...\n`); loader.start(); let txId; try { @@ -68,13 +67,13 @@ class DecompressSplCommand extends Command { tokenProgramId, ); - txId = await decompress( + txId = await unwrap( rpc(), payer, - mintPublicKey, - amount, - payer, recipientAta.address, + payer, + mintPublicKey, + BigInt(amount), ); loader.stop(false); @@ -82,12 +81,12 @@ class DecompressSplCommand extends Command { "\x1b[32mtxId:\x1b[0m ", generateSolanaTransactionURL("tx", txId, "custom"), ); - console.log("decompress-spl successful"); + console.log("unwrap-spl successful"); } catch (error) { - console.log("decompress-spl failed", txId); - this.error(`Failed to decompress-spl!\n${error}`); + console.log("unwrap-spl failed", txId); + this.error(`Failed to unwrap-spl!\n${error}`); } } } -export default DecompressSplCommand; +export default UnwrapSplCommand; diff --git a/cli/src/commands/compress-sol/index.ts b/cli/src/commands/wrap-sol/index.ts similarity index 66% rename from cli/src/commands/compress-sol/index.ts rename to cli/src/commands/wrap-sol/index.ts index 7b397a1489..ea6ab70119 100644 --- a/cli/src/commands/compress-sol/index.ts +++ b/cli/src/commands/wrap-sol/index.ts @@ -8,10 +8,10 @@ import { import { PublicKey } from "@solana/web3.js"; import { compress } from "@lightprotocol/stateless.js"; -class CompressSolCommand extends Command { - static summary = "Compress SOL."; +class WrapSolCommand extends Command { + static summary = "Wrap SOL into compressed account."; - static examples = ["$ light compress-sol --to PublicKey --amount 10"]; + static examples = ["$ light wrap-sol --to PublicKey --amount 10"]; static flags = { to: Flags.string({ @@ -19,7 +19,7 @@ class CompressSolCommand extends Command { required: true, }), amount: Flags.integer({ - description: "Amount to compress, in lamports.", + description: "Amount to wrap, in lamports.", required: true, }), }; @@ -27,14 +27,14 @@ class CompressSolCommand extends Command { static args = {}; async run() { - const { flags } = await this.parse(CompressSolCommand); + const { flags } = await this.parse(WrapSolCommand); const to = flags["to"]; const amount = flags["amount"]; if (!to || !amount) { throw new Error("Invalid arguments"); } - const loader = new CustomLoader(`Performing compress-sol...\n`); + const loader = new CustomLoader(`Performing wrap-sol...\n`); loader.start(); let txId; try { @@ -48,12 +48,12 @@ class CompressSolCommand extends Command { "\x1b[32mtxId:\x1b[0m ", generateSolanaTransactionURL("tx", txId, "custom"), ); - console.log("compress-sol successful"); + console.log("wrap-sol successful"); } catch (error) { - console.log("compress-sol failed", txId); - this.error(`Failed to compress-sol!\n${error}`); + console.log("wrap-sol failed", txId); + this.error(`Failed to wrap-sol!\n${error}`); } } } -export default CompressSolCommand; +export default WrapSolCommand; diff --git a/cli/src/commands/compress-spl/index.ts b/cli/src/commands/wrap-spl/index.ts similarity index 59% rename from cli/src/commands/compress-spl/index.ts rename to cli/src/commands/wrap-spl/index.ts index 63582cfe11..ccc91d1e1c 100644 --- a/cli/src/commands/compress-spl/index.ts +++ b/cli/src/commands/wrap-spl/index.ts @@ -6,16 +6,20 @@ import { rpc, } from "../../utils/utils"; import { PublicKey } from "@solana/web3.js"; -import { compress } from "@lightprotocol/compressed-token"; +import { + wrap, + CompressedTokenProgram, + getAssociatedTokenAddressInterface, + createAtaInterfaceIdempotent, +} from "@lightprotocol/compressed-token"; import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { CompressedTokenProgram } from "@lightprotocol/compressed-token"; -/// TODO: add ability to compress from non-fee payer -class CompressSplCommand extends Command { - static summary = "Compress SPL tokens."; +/// TODO: add ability to wrap from non-fee payer +class WrapSplCommand extends Command { + static summary = "Wrap SPL tokens into Light Token account."; static examples = [ - "$ light compress-spl --mint PublicKey --to PublicKey --amount 10", + "$ light wrap-spl --mint PublicKey --to PublicKey --amount 10", ]; static flags = { @@ -25,11 +29,11 @@ class CompressSplCommand extends Command { }), to: Flags.string({ description: - "Specify the recipient address (owner of destination compressed token account).", + "Specify the recipient address (owner of destination Light Token account).", required: true, }), amount: Flags.integer({ - description: "Amount to compress, in tokens.", + description: "Amount to wrap, in tokens.", required: true, }), }; @@ -37,7 +41,7 @@ class CompressSplCommand extends Command { static args = {}; async run() { - const { flags } = await this.parse(CompressSplCommand); + const { flags } = await this.parse(WrapSplCommand); const to = flags["to"]; const mint = flags["mint"]; const amount = flags["amount"]; @@ -45,7 +49,7 @@ class CompressSplCommand extends Command { throw new Error("Invalid arguments"); } - const loader = new CustomLoader(`Performing compress-spl...\n`); + const loader = new CustomLoader(`Performing wrap-spl...\n`); loader.start(); let txId; try { @@ -57,22 +61,24 @@ class CompressSplCommand extends Command { rpc(), ); - /// TODO: add explicit check that the ata is valid const sourceAta = getAssociatedTokenAddressSync( mintPublicKey, payer.publicKey, - undefined, + false, tokenProgramId, ); - txId = await compress( + await createAtaInterfaceIdempotent(rpc(), payer, mintPublicKey, toPublicKey); + const destAta = getAssociatedTokenAddressInterface(mintPublicKey, toPublicKey); + + txId = await wrap( rpc(), payer, - mintPublicKey, - amount, - payer, sourceAta, - toPublicKey, + destAta, + payer, + mintPublicKey, + BigInt(amount), ); loader.stop(false); @@ -80,12 +86,12 @@ class CompressSplCommand extends Command { "\x1b[32mtxId:\x1b[0m ", generateSolanaTransactionURL("tx", txId, "custom"), ); - console.log("compress-spl successful"); + console.log("wrap-spl successful"); } catch (error) { - console.log("compress-spl failed", txId); - this.error(`Failed to compress-spl!\n${error}`); + console.log("wrap-spl failed", txId); + this.error(`Failed to wrap-spl!\n${error}`); } } } -export default CompressSplCommand; +export default WrapSplCommand; diff --git a/cli/test/commands/approve-and-mint-to/index.test.ts b/cli/test/commands/approve-and-mint-to/index.test.ts deleted file mode 100644 index 1f07c29279..0000000000 --- a/cli/test/commands/approve-and-mint-to/index.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { runCommand } from "@oclif/test"; -import { expect } from "chai"; -import { before } from "mocha"; -import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; -import { defaultSolanaWalletKeypair } from "../../../src"; -import { Keypair } from "@solana/web3.js"; -import { createTestSplMint, requestAirdrop } from "../../helpers/helpers"; -import { getTestRpc } from "@lightprotocol/stateless.js"; -import { WasmFactory } from "@lightprotocol/hasher.rs"; - -describe("mint-to", () => { - let mintAmount: number = 100; - /// authority is also the feepayer, and mint-to recipient - let mintAuthorityPath = process.env.HOME + "/.config/solana/id.json"; - let mintAuthority: Keypair = defaultSolanaWalletKeypair(); - - let mintKeypair = Keypair.generate(); - let mintAddress = mintKeypair.publicKey; - - before(async () => { - await initTestEnvIfNeeded({ indexer: true, prover: true }); - await requestAirdrop(mintAuthority.publicKey); - const lightWasm = await WasmFactory.getInstance(); - const rpc = await getTestRpc(lightWasm); - await createTestSplMint(rpc, mintAuthority, mintKeypair, mintAuthority); - }); - - it(`approve-and-mint-to ${mintAmount} tokens to ${mintAuthority.publicKey.toBase58()} from mint: ${mintAddress.toBase58()} with authority ${mintAuthority.publicKey.toBase58()}`, async () => { - // First create the token pool - const { stdout: poolStdout } = await runCommand([ - "create-token-pool", - `--mint=${mintKeypair.publicKey.toBase58()}`, - ]); - console.log(poolStdout); - - // Then approve and mint - const { stdout } = await runCommand([ - "approve-and-mint-to", - `--amount=${mintAmount}`, - `--mint=${mintAddress.toBase58()}`, - `--mint-authority=${mintAuthorityPath}`, - `--to=${mintAuthority.publicKey.toBase58()}`, - ]); - expect(stdout).to.contain("approve-and-mint-to successful"); - }); -}); diff --git a/cli/test/commands/balance/index.test.ts b/cli/test/commands/balance/index.test.ts index 90097fcffb..577579e93d 100644 --- a/cli/test/commands/balance/index.test.ts +++ b/cli/test/commands/balance/index.test.ts @@ -14,7 +14,7 @@ describe("balance", () => { await requestAirdrop(keypair.publicKey); }); - it(`get compressed SOL balance for ${owner}`, async () => { + it(`get wrapped SOL balance for ${owner}`, async () => { // Get initial balance first const { stdout: initialStdout } = await runCommand([ "balance", @@ -26,7 +26,7 @@ describe("balance", () => { initialBalance = 0; } else { const balanceMatch = initialStdout.match( - /Compressed SOL balance:\s+(\d+)/, + /Wrapped SOL balance:\s+(\d+)/, ); if (balanceMatch && balanceMatch[1]) { initialBalance = parseInt(balanceMatch[1], 10); @@ -34,13 +34,13 @@ describe("balance", () => { } console.log(`Initial balance captured: ${initialBalance}`); - // Compress SOL to create a balance to check - const { stdout: compressStdout } = await runCommand([ - "compress-sol", + // Wrap SOL to create a balance to check + const { stdout: wrapStdout } = await runCommand([ + "wrap-sol", `--amount=${amount}`, `--to=${owner}`, ]); - expect(compressStdout).to.contain("compress-sol successful"); + expect(wrapStdout).to.contain("wrap-sol successful"); // Test the balance command const { stdout: finalStdout } = await runCommand([ @@ -49,7 +49,7 @@ describe("balance", () => { ]); // Extract the balance - const balanceMatch = finalStdout.match(/Compressed SOL balance:\s+(\d+)/); + const balanceMatch = finalStdout.match(/Wrapped SOL balance:\s+(\d+)/); expect(balanceMatch).to.not.be.null; if (balanceMatch && balanceMatch[1]) { @@ -58,7 +58,7 @@ describe("balance", () => { `Current balance: ${currentBalance}, Initial balance: ${initialBalance}, Expected increase: ${amount}`, ); - // Verify the balance increased by the compressed amount + // Verify the balance increased by the wrapped amount expect(currentBalance).to.equal(initialBalance + amount); } else { throw new Error("Could not extract balance from output"); diff --git a/cli/test/commands/create-token-pool/index.test.ts b/cli/test/commands/create-interface-pda/index.test.ts similarity index 89% rename from cli/test/commands/create-token-pool/index.test.ts rename to cli/test/commands/create-interface-pda/index.test.ts index 04bec23aec..5ebc000786 100644 --- a/cli/test/commands/create-token-pool/index.test.ts +++ b/cli/test/commands/create-interface-pda/index.test.ts @@ -7,7 +7,7 @@ import { Keypair } from "@solana/web3.js"; import { getTestRpc } from "@lightprotocol/stateless.js"; import { WasmFactory } from "@lightprotocol/hasher.rs"; -describe("create-mint", () => { +describe("create-interface-pda", () => { let mintAuthority: Keypair = defaultSolanaWalletKeypair(); let mintKeypair = Keypair.generate(); before(async () => { @@ -26,9 +26,9 @@ describe("create-mint", () => { it(`register mint for mintAuthority: ${mintAuthority.publicKey.toBase58()}`, async () => { const { stdout } = await runCommand([ - "create-token-pool", + "create-interface-pda", `--mint=${mintKeypair.publicKey.toBase58()}`, ]); - expect(stdout).to.contain("create-token-pool successful"); + expect(stdout).to.contain("create-interface-pda successful"); }); }); diff --git a/cli/test/commands/create-interface-pda/index.ts b/cli/test/commands/create-interface-pda/index.ts new file mode 100644 index 0000000000..5873dea9e8 --- /dev/null +++ b/cli/test/commands/create-interface-pda/index.ts @@ -0,0 +1,48 @@ +import { Command, Flags } from "@oclif/core"; +import { + CustomLoader, + defaultSolanaWalletKeypair, + generateSolanaTransactionURL, + rpc, +} from "../../utils/utils"; + +import { PublicKey } from "@solana/web3.js"; +import { createSplInterface } from "@lightprotocol/compressed-token"; + +class CreateInterfacePdaCommand extends Command { + static summary = "Create an SPL interface PDA for an existing mint"; + + static examples = ["$ light create-interface-pda --mint "]; + + static flags = { + mint: Flags.string({ + description: "Provide a base58 encoded mint address to register", + required: true, + }), + }; + + static args = {}; + + async run() { + const { flags } = await this.parse(CreateInterfacePdaCommand); + + const loader = new CustomLoader(`Creating SPL interface PDA...\n`); + loader.start(); + try { + const payer = defaultSolanaWalletKeypair(); + const mintAddress = new PublicKey(flags.mint); + const txId = await createSplInterface(rpc(), payer, mintAddress); + loader.stop(false); + console.log("\x1b[1mMint public key:\x1b[0m ", mintAddress.toBase58()); + console.log( + "\x1b[1mMint tx:\x1b[0m ", + generateSolanaTransactionURL("tx", txId, "custom"), + ); + console.log("create-interface-pda successful"); + } catch (error) { + this.error(`Failed to create-interface-pda!\n${error}`); + } + } +} + +export default CreateInterfacePdaCommand; diff --git a/cli/test/commands/create-mint/index.test.ts b/cli/test/commands/create-mint/index.test.ts index 9c9a8f7aa7..75486efd12 100644 --- a/cli/test/commands/create-mint/index.test.ts +++ b/cli/test/commands/create-mint/index.test.ts @@ -12,10 +12,7 @@ describe("create-mint", () => { }); it(`create mint for mintAuthority: ${mintAuthority.publicKey.toBase58()}`, async () => { - const { stdout } = await runCommand([ - "create-mint", - `--mint-authority=${mintAuthority.publicKey.toBase58()}`, - ]); + const { stdout } = await runCommand(["create-mint"]); expect(stdout).to.contain("create-mint successful"); }); }); diff --git a/cli/test/commands/create-token-account/index.test.ts b/cli/test/commands/create-token-account/index.test.ts new file mode 100644 index 0000000000..ff8e12301f --- /dev/null +++ b/cli/test/commands/create-token-account/index.test.ts @@ -0,0 +1,36 @@ +import { runCommand } from "@oclif/test"; +import { expect } from "chai"; +import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; +import { defaultSolanaWalletKeypair } from "../../../src"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { createTestMint, requestAirdrop } from "../../helpers/helpers"; + +describe("create-token-account", () => { + const payerKeypair = defaultSolanaWalletKeypair(); + const mintKeypair = Keypair.generate(); + let mintAddress: PublicKey; + + before(async () => { + await initTestEnvIfNeeded({ indexer: true, prover: true }); + await requestAirdrop(payerKeypair.publicKey); + mintAddress = await createTestMint(mintKeypair); + }); + + it("creates a token account for the payer", async () => { + const { stdout } = await runCommand([ + "create-token-account", + mintAddress.toBase58(), + ]); + expect(stdout).to.contain("create-token-account successful"); + }); + + it("creates a token account with --owner", async () => { + const otherOwner = Keypair.generate().publicKey; + const { stdout } = await runCommand([ + "create-token-account", + mintAddress.toBase58(), + `--owner=${otherOwner.toBase58()}`, + ]); + expect(stdout).to.contain("create-token-account successful"); + }); +}); diff --git a/cli/test/commands/merge-token-accounts/index.test.ts b/cli/test/commands/merge-token-accounts/index.test.ts deleted file mode 100644 index a5179b0f5c..0000000000 --- a/cli/test/commands/merge-token-accounts/index.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { runCommand } from "@oclif/test"; -import { expect } from "chai"; -import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; -import { defaultSolanaWalletKeypair } from "../../../src"; -import { Keypair } from "@solana/web3.js"; -import { - createTestMint, - requestAirdrop, - testMintTo, -} from "../../helpers/helpers"; -import { rpc } from "../../../src/utils/utils"; - -describe("merge-token-accounts", () => { - const payerKeypair = defaultSolanaWalletKeypair(); - const payerKeypairPath = process.env.HOME + "/.config/solana/id.json"; - - const mintKeypair = Keypair.generate(); - const mintAuthority = payerKeypair; - - const mintAmount = 10; - - before(async () => { - await initTestEnvIfNeeded({ indexer: true, prover: true }); - await requestAirdrop(payerKeypair.publicKey); - await createTestMint(mintKeypair); - - // Create multiple token accounts - for (let i = 0; i < 3; i++) { - await testMintTo( - payerKeypair, - mintKeypair.publicKey, - payerKeypair.publicKey, - mintAuthority, - mintAmount, - ); - } - }); - - it(`merge token accounts for mint ${mintKeypair.publicKey.toBase58()}, fee-payer: ${payerKeypair.publicKey.toBase58()} `, async () => { - const { stdout } = await runCommand([ - "merge-token-accounts", - `--fee-payer=${payerKeypairPath}`, - `--mint=${mintKeypair.publicKey.toBase58()}`, - ]); - expect(stdout).to.contain("Token accounts merged successfully"); - - // Verify that accounts were merged - const accounts = await rpc().getCompressedTokenAccountsByOwner( - payerKeypair.publicKey, - { mint: mintKeypair.publicKey }, - ); - expect(accounts.items.length).to.equal(1); - expect(accounts.items[0].parsed.amount.toNumber()).to.equal(mintAmount * 3); - }); -}); diff --git a/cli/test/commands/mint-to/index.test.ts b/cli/test/commands/mint-to/index.test.ts index 10ddd2fb9d..ea2cb33474 100644 --- a/cli/test/commands/mint-to/index.test.ts +++ b/cli/test/commands/mint-to/index.test.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { before } from "mocha"; import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; import { defaultSolanaWalletKeypair } from "../../../src"; -import { Keypair } from "@solana/web3.js"; +import { Keypair, PublicKey } from "@solana/web3.js"; import { createTestMint, requestAirdrop } from "../../helpers/helpers"; describe("mint-to", () => { @@ -13,15 +13,15 @@ describe("mint-to", () => { let mintAuthority: Keypair = defaultSolanaWalletKeypair(); let mintKeypair = Keypair.generate(); - let mintAddress = mintKeypair.publicKey; + let mintAddress: PublicKey; before(async () => { await initTestEnvIfNeeded({ indexer: true, prover: true }); await requestAirdrop(mintAuthority.publicKey); - await createTestMint(mintKeypair); + mintAddress = await createTestMint(mintKeypair); }); - it(`mint-to ${mintAmount} tokens to ${mintAuthority.publicKey.toBase58()} from mint: ${mintAddress.toBase58()} with authority ${mintAuthority.publicKey.toBase58()}`, async () => { + it(`mint-to tokens`, async () => { const { stdout } = await runCommand([ "mint-to", `--amount=${mintAmount}`, diff --git a/cli/test/commands/token-balance/index.test.ts b/cli/test/commands/token-balance/index.test.ts index d95afc43ab..b7827d3985 100644 --- a/cli/test/commands/token-balance/index.test.ts +++ b/cli/test/commands/token-balance/index.test.ts @@ -2,7 +2,7 @@ import { runCommand } from "@oclif/test"; import { expect } from "chai"; import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; import { defaultSolanaWalletKeypair } from "../../../src"; -import { Keypair } from "@solana/web3.js"; +import { Keypair, PublicKey } from "@solana/web3.js"; import { createTestMint, requestAirdrop, @@ -13,6 +13,7 @@ describe("Get balance", () => { const payerKeypair = defaultSolanaWalletKeypair(); const mintKeypair = Keypair.generate(); const mintAuthority = payerKeypair; + let mintAddress: PublicKey; const mintAmount = 10; const mintDestination = Keypair.generate().publicKey; @@ -21,23 +22,25 @@ describe("Get balance", () => { await initTestEnvIfNeeded({ indexer: true, prover: true }); await requestAirdrop(payerKeypair.publicKey); - await createTestMint(mintKeypair); + mintAddress = await createTestMint(mintKeypair); await testMintTo( payerKeypair, - mintKeypair.publicKey, + mintAddress, mintDestination, mintAuthority, mintAmount, ); }); - it(`check balance of ${mintAmount} tokens for ${mintDestination.toBase58()} from mint ${mintKeypair.publicKey.toBase58()}`, async () => { + it(`check token balance`, async () => { const { stdout } = await runCommand([ "token-balance", - `--mint=${mintKeypair.publicKey.toBase58()}`, + `--mint=${mintAddress.toBase58()}`, `--owner=${mintDestination.toBase58()}`, ]); - expect(stdout).to.contain("Balance:"); + expect(stdout).to.contain("Light token account balance:"); + expect(stdout).to.contain("Compressed light token balance:"); + expect(stdout).to.contain("Total balance:"); }); }); diff --git a/cli/test/commands/transfer/index.test.ts b/cli/test/commands/transfer/index.test.ts index f9df159649..d6a44ceef1 100644 --- a/cli/test/commands/transfer/index.test.ts +++ b/cli/test/commands/transfer/index.test.ts @@ -2,7 +2,7 @@ import { runCommand } from "@oclif/test"; import { expect } from "chai"; import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; import { defaultSolanaWalletKeypair } from "../../../src"; -import { Keypair } from "@solana/web3.js"; +import { Keypair, PublicKey } from "@solana/web3.js"; import { createTestMint, requestAirdrop, @@ -15,6 +15,7 @@ describe("transfer", () => { const mintKeypair = Keypair.generate(); const mintAuthority = payerKeypair; + let mintAddress: PublicKey; const mintAmount = 10; const mintDestination = Keypair.generate().publicKey; @@ -22,25 +23,23 @@ describe("transfer", () => { before(async () => { await initTestEnvIfNeeded({ indexer: true, prover: true }); await requestAirdrop(payerKeypair.publicKey); - await createTestMint(mintKeypair); + mintAddress = await createTestMint(mintKeypair); await testMintTo( payerKeypair, - mintKeypair.publicKey, + mintAddress, payerKeypair.publicKey, mintAuthority, mintAmount, ); }); - it(`transfer ${ - mintAmount - 1 - } tokens to ${mintDestination.toBase58()} from ${mintKeypair.publicKey.toBase58()}, fee-payer: ${payerKeypair.publicKey.toBase58()} `, async () => { + it(`transfer tokens`, async () => { const { stdout } = await runCommand([ "transfer", `--amount=${mintAmount - 1}`, `--fee-payer=${payerKeypairPath}`, - `--mint=${mintKeypair.publicKey.toBase58()}`, + `--mint=${mintAddress.toBase58()}`, `--to=${mintDestination.toBase58()}`, ]); expect(stdout).to.contain("transfer successful"); diff --git a/cli/test/commands/decompress-sol/index.test.ts b/cli/test/commands/unwrap-sol/index.test.ts similarity index 57% rename from cli/test/commands/decompress-sol/index.test.ts rename to cli/test/commands/unwrap-sol/index.test.ts index ee973aa2a7..1af957ff32 100644 --- a/cli/test/commands/decompress-sol/index.test.ts +++ b/cli/test/commands/unwrap-sol/index.test.ts @@ -4,7 +4,7 @@ import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; import { defaultSolanaWalletKeypair } from "../../../src"; import { requestAirdrop } from "../../helpers/helpers"; -describe("decompress-sol", () => { +describe("unwrap-sol", () => { const keypair = defaultSolanaWalletKeypair(); const to = keypair.publicKey.toBase58(); const amount = 200; @@ -14,7 +14,7 @@ describe("decompress-sol", () => { await requestAirdrop(keypair.publicKey); }); - it(`full compress-check-decompress-check cycle for ${amount} SOL to ${to}`, async () => { + it(`full wrap-check-unwrap-check cycle for ${amount} SOL to ${to}`, async () => { // Get initial balance first const { stdout: initialBalanceStdout } = await runCommand([ "balance", @@ -26,7 +26,7 @@ describe("decompress-sol", () => { initialBalance = 0; } else { const balanceMatch = initialBalanceStdout.match( - /Compressed SOL balance:\s+(\d+)/, + /Wrapped SOL balance:\s+(\d+)/, ); if (balanceMatch && balanceMatch[1]) { initialBalance = parseInt(balanceMatch[1], 10); @@ -34,45 +34,45 @@ describe("decompress-sol", () => { } console.log(`Initial balance captured: ${initialBalance}`); - // Compress SOL - const { stdout: compressStdout } = await runCommand([ - "compress-sol", + // Wrap SOL + const { stdout: wrapStdout } = await runCommand([ + "wrap-sol", `--amount=${amount}`, `--to=${to}`, ]); - expect(compressStdout).to.contain("compress-sol successful"); + expect(wrapStdout).to.contain("wrap-sol successful"); - // Check balance after compression - const { stdout: afterCompressStdout } = await runCommand([ + // Check balance after wrapping + const { stdout: afterWrapStdout } = await runCommand([ "balance", `--owner=${to}`, ]); - const balanceMatchAfterCompress = afterCompressStdout.match( - /Compressed SOL balance:\s+(\d+)/, + const balanceMatchAfterWrap = afterWrapStdout.match( + /Wrapped SOL balance:\s+(\d+)/, ); - expect(balanceMatchAfterCompress).to.not.be.null; + expect(balanceMatchAfterWrap).to.not.be.null; - let balanceAfterCompression = 0; - if (balanceMatchAfterCompress && balanceMatchAfterCompress[1]) { - balanceAfterCompression = parseInt(balanceMatchAfterCompress[1], 10); - console.log(`Balance after compression: ${balanceAfterCompression}`); + let balanceAfterWrap = 0; + if (balanceMatchAfterWrap && balanceMatchAfterWrap[1]) { + balanceAfterWrap = parseInt(balanceMatchAfterWrap[1], 10); + console.log(`Balance after wrapping: ${balanceAfterWrap}`); - // Verify the balance increased by the compressed amount - expect(balanceAfterCompression).to.equal(initialBalance + amount); + // Verify the balance increased by the wrapped amount + expect(balanceAfterWrap).to.equal(initialBalance + amount); } else { throw new Error("Could not extract balance from output"); } - // Decompress SOL - const { stdout: decompressStdout } = await runCommand([ - "decompress-sol", + // Unwrap SOL + const { stdout: unwrapStdout } = await runCommand([ + "unwrap-sol", `--amount=${amount}`, `--to=${to}`, ]); - expect(decompressStdout).to.contain("decompress-sol successful"); + expect(unwrapStdout).to.contain("unwrap-sol successful"); - // Check balance after decompression + // Check balance after unwrapping const { stdout: finalBalanceStdout } = await runCommand([ "balance", `--owner=${to}`, @@ -80,24 +80,24 @@ describe("decompress-sol", () => { // Extract the final balance if (finalBalanceStdout.includes("No accounts found")) { - // If there were no accounts before compression, there should be none after decompression + // If there were no accounts before wrapping, there should be none after unwrapping expect(initialBalance).to.equal(0); } else { const balanceMatch = finalBalanceStdout.match( - /Compressed SOL balance:\s+(\d+)/, + /Wrapped SOL balance:\s+(\d+)/, ); if (balanceMatch && balanceMatch[1]) { const finalBalance = parseInt(balanceMatch[1], 10); console.log( - `Final balance: ${finalBalance}, Expected: ${balanceAfterCompression - amount}`, + `Final balance: ${finalBalance}, Expected: ${balanceAfterWrap - amount}`, ); - // Verify the balance decreased by the decompressed amount - expect(finalBalance).to.equal(balanceAfterCompression - amount); + // Verify the balance decreased by the unwrapped amount + expect(finalBalance).to.equal(balanceAfterWrap - amount); } else { // If we can't extract the balance but initial balance was equal to amount, // we should get "No accounts found" - if (balanceAfterCompression === amount) { + if (balanceAfterWrap === amount) { expect(finalBalanceStdout).to.contain("No accounts found"); } else { throw new Error("Could not extract balance from output"); diff --git a/cli/test/commands/unwrap-sol/index.ts b/cli/test/commands/unwrap-sol/index.ts new file mode 100644 index 0000000000..ee3407d037 --- /dev/null +++ b/cli/test/commands/unwrap-sol/index.ts @@ -0,0 +1,57 @@ +import { Command, Flags } from "@oclif/core"; +import { + CustomLoader, + defaultSolanaWalletKeypair, + generateSolanaTransactionURL, + rpc, +} from "../../utils/utils"; +import { PublicKey } from "@solana/web3.js"; +import { decompress } from "@lightprotocol/stateless.js"; + +class UnwrapSolCommand extends Command { + static summary = "Unwrap SOL from compressed account."; + + static examples = ["$ light unwrap-sol --to PublicKey --amount 10"]; + + static flags = { + to: Flags.string({ + description: "Specify the recipient address.", + required: true, + }), + amount: Flags.integer({ + description: "Amount to unwrap, in lamports.", + required: true, + }), + }; + + static args = {}; + + async run() { + const { flags } = await this.parse(UnwrapSolCommand); + const to = flags["to"]; + const amount = flags["amount"]; + if (!to || !amount) { + throw new Error("Invalid arguments"); + } + + const loader = new CustomLoader(`Performing unwrap-sol...\n`); + loader.start(); + + try { + const toPublicKey = new PublicKey(to); + const payer = defaultSolanaWalletKeypair(); + + const txId = await decompress(rpc(), payer, amount, toPublicKey); + loader.stop(false); + console.log( + "\x1b[32munwrap-sol:\x1b[0m ", + generateSolanaTransactionURL("tx", txId, "custom"), + ); + console.log("unwrap-sol successful"); + } catch (error) { + this.error(`Failed to unwrap-sol!\n${error}`); + } + } +} + +export default UnwrapSolCommand; diff --git a/cli/test/commands/decompress-spl/index.test.ts b/cli/test/commands/unwrap-spl/index.test.ts similarity index 62% rename from cli/test/commands/decompress-spl/index.test.ts rename to cli/test/commands/unwrap-spl/index.test.ts index 83dc112772..715edeb74b 100644 --- a/cli/test/commands/decompress-spl/index.test.ts +++ b/cli/test/commands/unwrap-spl/index.test.ts @@ -4,15 +4,12 @@ import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; import { defaultSolanaWalletKeypair } from "../../../src"; import { Keypair } from "@solana/web3.js"; import { - createTestMint, + createTestSplMintWithPool, requestAirdrop, - testMintTo, } from "../../helpers/helpers"; -describe("decompress-spl", () => { +describe("unwrap-spl", () => { const payerKeypair = defaultSolanaWalletKeypair(); - /// TODO: add test case for separate fee-payer - const payerKeypairPath = process.env.HOME + "/.config/solana/id.json"; const mintKeypair = Keypair.generate(); const mintAuthority = payerKeypair; @@ -22,26 +19,21 @@ describe("decompress-spl", () => { before(async () => { await initTestEnvIfNeeded({ indexer: true, prover: true }); await requestAirdrop(payerKeypair.publicKey); - await createTestMint(mintKeypair); - - await testMintTo( - payerKeypair, - mintKeypair.publicKey, - payerKeypair.publicKey, + await createTestSplMintWithPool( + mintKeypair, mintAuthority, mintAmount, + payerKeypair.publicKey, ); }); - it(`decompress ${ - mintAmount - 1 - } tokens to ${payerKeypair.publicKey.toBase58()} from ${payerKeypair.publicKey.toBase58()}`, async () => { + it(`unwrap tokens`, async () => { const { stdout } = await runCommand([ - "decompress-spl", + "unwrap-spl", `--mint=${mintKeypair.publicKey.toBase58()}`, `--amount=${mintAmount - 1}`, `--to=${payerKeypair.publicKey.toBase58()}`, ]); - expect(stdout).to.contain("decompress-spl successful"); + expect(stdout).to.contain("unwrap-spl successful"); }); }); diff --git a/cli/test/commands/unwrap-spl/index.ts b/cli/test/commands/unwrap-spl/index.ts new file mode 100644 index 0000000000..b998f06d1f --- /dev/null +++ b/cli/test/commands/unwrap-spl/index.ts @@ -0,0 +1,92 @@ +import { Command, Flags } from "@oclif/core"; +import { + CustomLoader, + defaultSolanaWalletKeypair, + generateSolanaTransactionURL, + rpc, +} from "../../utils/utils"; +import { PublicKey } from "@solana/web3.js"; +import { unwrap, CompressedTokenProgram } from "@lightprotocol/compressed-token"; +import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; + +/// TODO: add ability to unwrap from non-fee payer +class UnwrapSplCommand extends Command { + static summary = "Unwrap Light Tokens into SPL token account."; + + static examples = [ + "$ light unwrap-spl --mint PublicKey --to PublicKey --amount 10", + ]; + + static flags = { + mint: Flags.string({ + description: "Specify the mint address.", + required: true, + }), + to: Flags.string({ + description: + "Specify the recipient address. (owner of destination SPL token account)", + required: true, + }), + amount: Flags.integer({ + description: "Amount to unwrap, in tokens.", + required: true, + }), + }; + + static args = {}; + + async run() { + const { flags } = await this.parse(UnwrapSplCommand); + const to = flags["to"]; + const mint = flags["mint"]; + const amount = flags["amount"]; + if (!to || !mint || !amount) { + throw new Error("Invalid arguments"); + } + + const loader = new CustomLoader(`Performing unwrap-spl...\n`); + loader.start(); + let txId; + try { + const toPublicKey = new PublicKey(to); + const mintPublicKey = new PublicKey(mint); + const payer = defaultSolanaWalletKeypair(); + const tokenProgramId = await CompressedTokenProgram.getMintProgramId( + mintPublicKey, + rpc(), + ); + + const recipientAta = await getOrCreateAssociatedTokenAccount( + rpc(), + payer, + mintPublicKey, + toPublicKey, + undefined, + undefined, + undefined, + tokenProgramId, + ); + + txId = await unwrap( + rpc(), + payer, + recipientAta.address, + payer, + mintPublicKey, + BigInt(amount), + ); + + loader.stop(false); + console.log( + "\x1b[32mtxId:\x1b[0m ", + generateSolanaTransactionURL("tx", txId, "custom"), + ); + console.log("unwrap-spl successful"); + } catch (error) { + console.log("unwrap-spl failed", txId); + this.error(`Failed to unwrap-spl!\n${error}`); + } + } +} + +export default UnwrapSplCommand; diff --git a/cli/test/commands/compress-sol/index.test.ts b/cli/test/commands/wrap-sol/index.test.ts similarity index 76% rename from cli/test/commands/compress-sol/index.test.ts rename to cli/test/commands/wrap-sol/index.test.ts index 09085b7998..5f70a4c9f0 100644 --- a/cli/test/commands/compress-sol/index.test.ts +++ b/cli/test/commands/wrap-sol/index.test.ts @@ -4,7 +4,7 @@ import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; import { defaultSolanaWalletKeypair } from "../../../src"; import { requestAirdrop } from "../../helpers/helpers"; -describe("compress-sol", () => { +describe("wrap-sol", () => { const keypair = defaultSolanaWalletKeypair(); const to = keypair.publicKey.toBase58(); const amount = 1000_000; @@ -15,7 +15,7 @@ describe("compress-sol", () => { await requestAirdrop(keypair.publicKey); }); - it(`compress-sol ${amount} lamports to ${to} and verify balance increase`, async () => { + it(`wrap-sol ${amount} lamports to ${to} and verify balance increase`, async () => { // Get initial balance first const { stdout: initialStdout } = await runCommand([ "balance", @@ -28,7 +28,7 @@ describe("compress-sol", () => { } else { // Extract the balance number const balanceMatch = initialStdout.match( - /Compressed SOL balance:\s+(\d+)/, + /Wrapped SOL balance:\s+(\d+)/, ); if (balanceMatch && balanceMatch[1]) { initialBalance = parseInt(balanceMatch[1], 10); @@ -36,22 +36,22 @@ describe("compress-sol", () => { } console.log(`Initial balance captured: ${initialBalance}`); - // Compress SOL - const { stdout: compressStdout } = await runCommand([ - "compress-sol", + // Wrap SOL + const { stdout: wrapStdout } = await runCommand([ + "wrap-sol", `--amount=${amount}`, `--to=${to}`, ]); - expect(compressStdout).to.contain("compress-sol successful"); + expect(wrapStdout).to.contain("wrap-sol successful"); - // Check balance after compression + // Check balance after wrapping const { stdout: finalStdout } = await runCommand([ "balance", `--owner=${to}`, ]); // Extract the new balance - const balanceMatch = finalStdout.match(/Compressed SOL balance:\s+(\d+)/); + const balanceMatch = finalStdout.match(/Wrapped SOL balance:\s+(\d+)/); expect(balanceMatch).to.not.be.null; if (balanceMatch && balanceMatch[1]) { @@ -60,7 +60,7 @@ describe("compress-sol", () => { `New balance: ${newBalance}, Initial balance: ${initialBalance}, Expected increase: ${amount}`, ); - // Verify the balance increased by the compressed amount + // Verify the balance increased by the wrapped amount expect(newBalance).to.equal(initialBalance + amount); } else { throw new Error("Could not extract balance from output"); diff --git a/cli/test/commands/wrap-sol/index.ts b/cli/test/commands/wrap-sol/index.ts new file mode 100644 index 0000000000..ea6ab70119 --- /dev/null +++ b/cli/test/commands/wrap-sol/index.ts @@ -0,0 +1,59 @@ +import { Command, Flags } from "@oclif/core"; +import { + CustomLoader, + defaultSolanaWalletKeypair, + generateSolanaTransactionURL, + rpc, +} from "../../utils/utils"; +import { PublicKey } from "@solana/web3.js"; +import { compress } from "@lightprotocol/stateless.js"; + +class WrapSolCommand extends Command { + static summary = "Wrap SOL into compressed account."; + + static examples = ["$ light wrap-sol --to PublicKey --amount 10"]; + + static flags = { + to: Flags.string({ + description: "Specify the recipient address.", + required: true, + }), + amount: Flags.integer({ + description: "Amount to wrap, in lamports.", + required: true, + }), + }; + + static args = {}; + + async run() { + const { flags } = await this.parse(WrapSolCommand); + const to = flags["to"]; + const amount = flags["amount"]; + if (!to || !amount) { + throw new Error("Invalid arguments"); + } + + const loader = new CustomLoader(`Performing wrap-sol...\n`); + loader.start(); + let txId; + try { + const toPublicKey = new PublicKey(to); + const payer = defaultSolanaWalletKeypair(); + + txId = await compress(rpc(), payer, amount, toPublicKey); + + loader.stop(false); + console.log( + "\x1b[32mtxId:\x1b[0m ", + generateSolanaTransactionURL("tx", txId, "custom"), + ); + console.log("wrap-sol successful"); + } catch (error) { + console.log("wrap-sol failed", txId); + this.error(`Failed to wrap-sol!\n${error}`); + } + } +} + +export default WrapSolCommand; diff --git a/cli/test/commands/compress-spl/index.test.ts b/cli/test/commands/wrap-spl/index.test.ts similarity index 58% rename from cli/test/commands/compress-spl/index.test.ts rename to cli/test/commands/wrap-spl/index.test.ts index f80fa4a140..fda6353ee7 100644 --- a/cli/test/commands/compress-spl/index.test.ts +++ b/cli/test/commands/wrap-spl/index.test.ts @@ -4,15 +4,12 @@ import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; import { defaultSolanaWalletKeypair } from "../../../src"; import { Keypair } from "@solana/web3.js"; import { - createTestMint, + createTestSplMintWithPool, requestAirdrop, - testMintTo, } from "../../helpers/helpers"; -describe("compress-spl", () => { +describe("wrap-spl", () => { const payerKeypair = defaultSolanaWalletKeypair(); - /// TODO: add test case for separate fee-payer - const payerKeypairPath = process.env.HOME + "/.config/solana/id.json"; const mintKeypair = Keypair.generate(); const mintAuthority = payerKeypair; @@ -22,37 +19,31 @@ describe("compress-spl", () => { before(async () => { await initTestEnvIfNeeded({ indexer: true, prover: true }); await requestAirdrop(payerKeypair.publicKey); - - await createTestMint(mintKeypair); - - await testMintTo( - payerKeypair, - mintKeypair.publicKey, - payerKeypair.publicKey, + await createTestSplMintWithPool( + mintKeypair, mintAuthority, mintAmount, + payerKeypair.publicKey, ); }); - it(`compress ${ - mintAmount - 2 - } tokens to ${payerKeypair.publicKey.toBase58()} from ${payerKeypair.publicKey.toBase58()}`, async () => { - // First decompress some tokens to have them available - const { stdout: decompressStdout } = await runCommand([ - "decompress-spl", + it(`wrap tokens`, async () => { + // First unwrap some tokens to have SPL tokens available + const { stdout: unwrapStdout } = await runCommand([ + "unwrap-spl", `--mint=${mintKeypair.publicKey.toBase58()}`, `--amount=${mintAmount - 1}`, `--to=${payerKeypair.publicKey.toBase58()}`, ]); - console.log(decompressStdout); + console.log(unwrapStdout); - // Then compress tokens + // Then wrap SPL tokens back const { stdout } = await runCommand([ - "compress-spl", + "wrap-spl", `--mint=${mintKeypair.publicKey.toBase58()}`, `--amount=${mintAmount - 2}`, `--to=${payerKeypair.publicKey.toBase58()}`, ]); - expect(stdout).to.contain("compress-spl successful"); + expect(stdout).to.contain("wrap-spl successful"); }); }); diff --git a/cli/test/commands/wrap-spl/index.ts b/cli/test/commands/wrap-spl/index.ts new file mode 100644 index 0000000000..ccc91d1e1c --- /dev/null +++ b/cli/test/commands/wrap-spl/index.ts @@ -0,0 +1,97 @@ +import { Command, Flags } from "@oclif/core"; +import { + CustomLoader, + defaultSolanaWalletKeypair, + generateSolanaTransactionURL, + rpc, +} from "../../utils/utils"; +import { PublicKey } from "@solana/web3.js"; +import { + wrap, + CompressedTokenProgram, + getAssociatedTokenAddressInterface, + createAtaInterfaceIdempotent, +} from "@lightprotocol/compressed-token"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; + +/// TODO: add ability to wrap from non-fee payer +class WrapSplCommand extends Command { + static summary = "Wrap SPL tokens into Light Token account."; + + static examples = [ + "$ light wrap-spl --mint PublicKey --to PublicKey --amount 10", + ]; + + static flags = { + mint: Flags.string({ + description: "Specify the mint address.", + required: true, + }), + to: Flags.string({ + description: + "Specify the recipient address (owner of destination Light Token account).", + required: true, + }), + amount: Flags.integer({ + description: "Amount to wrap, in tokens.", + required: true, + }), + }; + + static args = {}; + + async run() { + const { flags } = await this.parse(WrapSplCommand); + const to = flags["to"]; + const mint = flags["mint"]; + const amount = flags["amount"]; + if (!to || !mint || !amount) { + throw new Error("Invalid arguments"); + } + + const loader = new CustomLoader(`Performing wrap-spl...\n`); + loader.start(); + let txId; + try { + const toPublicKey = new PublicKey(to); + const mintPublicKey = new PublicKey(mint); + const payer = defaultSolanaWalletKeypair(); + const tokenProgramId = await CompressedTokenProgram.getMintProgramId( + mintPublicKey, + rpc(), + ); + + const sourceAta = getAssociatedTokenAddressSync( + mintPublicKey, + payer.publicKey, + false, + tokenProgramId, + ); + + await createAtaInterfaceIdempotent(rpc(), payer, mintPublicKey, toPublicKey); + const destAta = getAssociatedTokenAddressInterface(mintPublicKey, toPublicKey); + + txId = await wrap( + rpc(), + payer, + sourceAta, + destAta, + payer, + mintPublicKey, + BigInt(amount), + ); + + loader.stop(false); + console.log( + "\x1b[32mtxId:\x1b[0m ", + generateSolanaTransactionURL("tx", txId, "custom"), + ); + console.log("wrap-spl successful"); + } catch (error) { + console.log("wrap-spl failed", txId); + this.error(`Failed to wrap-spl!\n${error}`); + } + } +} + +export default WrapSplCommand; diff --git a/cli/test/helpers/helpers.ts b/cli/test/helpers/helpers.ts index c2a2873c61..ddaeee68b0 100644 --- a/cli/test/helpers/helpers.ts +++ b/cli/test/helpers/helpers.ts @@ -5,43 +5,51 @@ import { Signer, SystemProgram, } from "@solana/web3.js"; -import { getPayer, getSolanaRpcUrl } from "../../src"; +import { getPayer, getSolanaRpcUrl, getIndexerUrl, getProverUrl } from "../../src/utils/utils"; import { Rpc, buildAndSignTx, confirmTx, + createRpc, dedupeSigner, - getTestRpc, sendAndConfirmTx, } from "@lightprotocol/stateless.js"; -import { createMint, mintTo } from "@lightprotocol/compressed-token"; +import { + createMintInterface, + decompressMint, + createSplInterface, + mintTo, + mintToInterface, + createAtaInterfaceIdempotent, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; import { MINT_SIZE, TOKEN_PROGRAM_ID, createInitializeMint2Instruction, } from "@solana/spl-token"; -import { WasmFactory } from "@lightprotocol/hasher.rs"; export async function requestAirdrop(address: PublicKey, amount = 3e9) { - const lightWasm = await WasmFactory.getInstance(); - const rpc = await getTestRpc(lightWasm); + const rpc = createRpc(getSolanaRpcUrl(), getIndexerUrl(), getProverUrl()); const connection = new Connection(getSolanaRpcUrl(), "finalized"); let sig = await connection.requestAirdrop(address, amount); await confirmTx(rpc, sig); } export async function createTestMint(mintKeypair: Keypair) { - const lightWasm = await WasmFactory.getInstance(); - const rpc = await getTestRpc(lightWasm); + const rpc = createRpc(getSolanaRpcUrl(), getIndexerUrl(), getProverUrl()); - const { mint, transactionSignature } = await createMint( + const payer = await getPayer(); + const { mint, transactionSignature } = await createMintInterface( rpc, - await getPayer(), - (await getPayer()).publicKey, + payer, + payer, + null, 9, mintKeypair, ); await confirmTx(rpc, transactionSignature); + await decompressMint(rpc, payer, mint); return mint; } @@ -52,20 +60,47 @@ export async function testMintTo( mintAuthority: Keypair, mintAmount: number, ) { - const lightWasm = await WasmFactory.getInstance(); - const rpc = await getTestRpc(lightWasm); + const rpc = createRpc(getSolanaRpcUrl(), getIndexerUrl(), getProverUrl()); - const txId = await mintTo( + await createAtaInterfaceIdempotent(rpc, payer, mintAddress, mintDestination); + const destination = getAssociatedTokenAddressInterface(mintAddress, mintDestination); + const txId = await mintToInterface( rpc, payer, mintAddress, - mintDestination, + destination, mintAuthority, mintAmount, ); return txId; } +/** + * Create a standard SPL mint, register it with the compressed token program, + * and mint compressed tokens. Used by wrap/unwrap tests. + */ +export async function createTestSplMintWithPool( + mintKeypair: Keypair, + mintAuthority: Keypair, + mintAmount: number, + mintDestination: PublicKey, +) { + const rpc = createRpc(getSolanaRpcUrl(), getIndexerUrl(), getProverUrl()); + const payer = await getPayer(); + + await createTestSplMint(rpc, payer, mintKeypair, mintAuthority); + await createSplInterface(rpc, payer, mintKeypair.publicKey); + await mintTo( + rpc, + payer, + mintKeypair.publicKey, + mintDestination, + mintAuthority, + mintAmount, + ); + return mintKeypair.publicKey; +} + export const TEST_TOKEN_DECIMALS = 2; export async function createTestSplMint( diff --git a/js/compressed-token/src/index.ts b/js/compressed-token/src/index.ts index ab4648087d..7692034798 100644 --- a/js/compressed-token/src/index.ts +++ b/js/compressed-token/src/index.ts @@ -95,6 +95,8 @@ export { sliceLast, decompressInterface, wrap, + unwrap, + decompressMint, mintTo as mintToCToken, mintToCompressed, mintToInterface, From e29b8062981d5a0a63434e8ef053048dadeb38cd Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Thu, 19 Feb 2026 01:00:17 +0000 Subject: [PATCH 2/6] refactor(cli): remove ZK Compression SOL commands Remove wrap-sol, unwrap-sol, and balance commands that use raw ZK Compression (stateless.js compress/decompress). CLI now exclusively uses Light Token APIs from @lightprotocol/compressed-token. --- cli/README.md | 30 ------ cli/package.json | 7 +- cli/src/commands/balance/index.ts | 50 ---------- cli/src/commands/unwrap-sol/index.ts | 57 ----------- cli/src/commands/wrap-sol/index.ts | 59 ----------- cli/test/commands/balance/index.test.ts | 67 ------------- cli/test/commands/unwrap-sol/index.test.ts | 108 --------------------- cli/test/commands/unwrap-sol/index.ts | 57 ----------- cli/test/commands/wrap-sol/index.test.ts | 69 ------------- cli/test/commands/wrap-sol/index.ts | 59 ----------- 10 files changed, 2 insertions(+), 561 deletions(-) delete mode 100644 cli/src/commands/balance/index.ts delete mode 100644 cli/src/commands/unwrap-sol/index.ts delete mode 100644 cli/src/commands/wrap-sol/index.ts delete mode 100644 cli/test/commands/balance/index.test.ts delete mode 100644 cli/test/commands/unwrap-sol/index.test.ts delete mode 100644 cli/test/commands/unwrap-sol/index.ts delete mode 100644 cli/test/commands/wrap-sol/index.test.ts delete mode 100644 cli/test/commands/wrap-sol/index.ts diff --git a/cli/README.md b/cli/README.md index 2f940212a0..a0a93670e7 100644 --- a/cli/README.md +++ b/cli/README.md @@ -197,36 +197,6 @@ FLAGS ``` -#### Wrap SOL into compressed account - -```bash -light wrap-sol --amount 1000 --to "YOUR_WALLET_ADDRESS_BASE58" -``` - -``` -USAGE - $ light wrap-sol --to --amount - -FLAGS - --amount= (required) Amount to wrap in lamports. - --to= (required) Specify the recipient address. -``` - -#### Unwrap SOL from compressed account - -```bash -light unwrap-sol --amount 42 --to "YOUR_WALLET_ADDRESS_BASE58" -``` - -``` -USAGE - $ light unwrap-sol --to --amount - -FLAGS - --amount= (required) Amount to unwrap in lamports. - --to= (required) Specify the recipient address. -``` - #### Get token balance ```bash diff --git a/cli/package.json b/cli/package.json index a9b8c2ab94..d25eafd6b1 100644 --- a/cli/package.json +++ b/cli/package.json @@ -110,15 +110,12 @@ "test-mint-to": "mocha ./test/commands/mint-to/index.test.ts -t 10000000 --exit", "test-transfer": "mocha ./test/commands/transfer/index.test.ts -t 10000000 --exit", "test-token-balance": "mocha test/commands/token-balance/index.test.ts -t 10000000 --exit", - "test-wrap-sol": "mocha ./test/commands/wrap-sol/index.test.ts -t 10000000 --exit", - "test-balance": "mocha ./test/commands/balance/index.test.ts -t 10000000 --exit", - "test-unwrap-sol": "mocha ./test/commands/unwrap-sol/index.test.ts -t 10000000 --exit", - "test-wrap-spl": "mocha ./test/commands/wrap-spl/index.test.ts -t 10000000 --exit", +"test-wrap-spl": "mocha ./test/commands/wrap-spl/index.test.ts -t 10000000 --exit", "test-unwrap-spl": "mocha ./test/commands/unwrap-spl/index.test.ts -t 10000000 --exit", "test-test-validator": "mocha ./test/commands/test-validator/index.test.ts -t 10000000 --exit", "kill": "killall solana-test-validator || true && killall solana-test-val || true && sleep 1", "test-cli": "pnpm test-config && pnpm kill", - "test": "pnpm kill && pnpm test-cli && pnpm test-utils && pnpm test-create-mint && pnpm test-create-token-account && pnpm test-mint-to && pnpm test-transfer && pnpm test-create-interface-pda && pnpm test-wrap-spl && pnpm test-unwrap-spl && pnpm test-token-balance && pnpm test-wrap-sol && pnpm test-balance && pnpm test-unwrap-sol && pnpm test-test-validator", + "test": "pnpm kill && pnpm test-cli && pnpm test-utils && pnpm test-create-mint && pnpm test-create-token-account && pnpm test-mint-to && pnpm test-transfer && pnpm test-create-interface-pda && pnpm test-wrap-spl && pnpm test-unwrap-spl && pnpm test-token-balance && pnpm test-test-validator", "install-local": "pnpm build && pnpm global remove @lightprotocol/zk-compression-cli || true && pnpm global add $PWD", "version": "oclif readme && git add README.md" }, diff --git a/cli/src/commands/balance/index.ts b/cli/src/commands/balance/index.ts deleted file mode 100644 index d029b8ca23..0000000000 --- a/cli/src/commands/balance/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Command, Flags } from "@oclif/core"; -import { CustomLoader, rpc } from "../../utils/utils"; -import { PublicKey } from "@solana/web3.js"; - -class BalanceCommand extends Command { - static summary = "Get wrapped SOL balance"; - static examples = ["$ light balance --owner=
"]; - - static flags = { - owner: Flags.string({ - description: "Address of the owner.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(BalanceCommand); - const loader = new CustomLoader(`Performing balance...\n`); - loader.start(); - try { - const { owner } = flags; - const refOwner = new PublicKey(owner); - - const accounts = await rpc().getCompressedAccountsByOwner(refOwner); - - loader.stop(false); - - if (accounts.items.length === 0) { - console.log("No accounts found"); - return; - } - - let totalAmount = 0; - for (const account of accounts.items) { - totalAmount += account.lamports.toNumber(); - } - - console.log( - "\u001B[1mWrapped SOL balance:\u001B[0m", - totalAmount.toString(), - ); - } catch (error) { - this.error(`Failed to get balance!\n${error}`); - } - } -} - -export default BalanceCommand; diff --git a/cli/src/commands/unwrap-sol/index.ts b/cli/src/commands/unwrap-sol/index.ts deleted file mode 100644 index ee3407d037..0000000000 --- a/cli/src/commands/unwrap-sol/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Command, Flags } from "@oclif/core"; -import { - CustomLoader, - defaultSolanaWalletKeypair, - generateSolanaTransactionURL, - rpc, -} from "../../utils/utils"; -import { PublicKey } from "@solana/web3.js"; -import { decompress } from "@lightprotocol/stateless.js"; - -class UnwrapSolCommand extends Command { - static summary = "Unwrap SOL from compressed account."; - - static examples = ["$ light unwrap-sol --to PublicKey --amount 10"]; - - static flags = { - to: Flags.string({ - description: "Specify the recipient address.", - required: true, - }), - amount: Flags.integer({ - description: "Amount to unwrap, in lamports.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(UnwrapSolCommand); - const to = flags["to"]; - const amount = flags["amount"]; - if (!to || !amount) { - throw new Error("Invalid arguments"); - } - - const loader = new CustomLoader(`Performing unwrap-sol...\n`); - loader.start(); - - try { - const toPublicKey = new PublicKey(to); - const payer = defaultSolanaWalletKeypair(); - - const txId = await decompress(rpc(), payer, amount, toPublicKey); - loader.stop(false); - console.log( - "\x1b[32munwrap-sol:\x1b[0m ", - generateSolanaTransactionURL("tx", txId, "custom"), - ); - console.log("unwrap-sol successful"); - } catch (error) { - this.error(`Failed to unwrap-sol!\n${error}`); - } - } -} - -export default UnwrapSolCommand; diff --git a/cli/src/commands/wrap-sol/index.ts b/cli/src/commands/wrap-sol/index.ts deleted file mode 100644 index ea6ab70119..0000000000 --- a/cli/src/commands/wrap-sol/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Command, Flags } from "@oclif/core"; -import { - CustomLoader, - defaultSolanaWalletKeypair, - generateSolanaTransactionURL, - rpc, -} from "../../utils/utils"; -import { PublicKey } from "@solana/web3.js"; -import { compress } from "@lightprotocol/stateless.js"; - -class WrapSolCommand extends Command { - static summary = "Wrap SOL into compressed account."; - - static examples = ["$ light wrap-sol --to PublicKey --amount 10"]; - - static flags = { - to: Flags.string({ - description: "Specify the recipient address.", - required: true, - }), - amount: Flags.integer({ - description: "Amount to wrap, in lamports.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(WrapSolCommand); - const to = flags["to"]; - const amount = flags["amount"]; - if (!to || !amount) { - throw new Error("Invalid arguments"); - } - - const loader = new CustomLoader(`Performing wrap-sol...\n`); - loader.start(); - let txId; - try { - const toPublicKey = new PublicKey(to); - const payer = defaultSolanaWalletKeypair(); - - txId = await compress(rpc(), payer, amount, toPublicKey); - - loader.stop(false); - console.log( - "\x1b[32mtxId:\x1b[0m ", - generateSolanaTransactionURL("tx", txId, "custom"), - ); - console.log("wrap-sol successful"); - } catch (error) { - console.log("wrap-sol failed", txId); - this.error(`Failed to wrap-sol!\n${error}`); - } - } -} - -export default WrapSolCommand; diff --git a/cli/test/commands/balance/index.test.ts b/cli/test/commands/balance/index.test.ts deleted file mode 100644 index 577579e93d..0000000000 --- a/cli/test/commands/balance/index.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { runCommand } from "@oclif/test"; -import { expect } from "chai"; -import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; -import { defaultSolanaWalletKeypair } from "../../../src"; -import { requestAirdrop } from "../../helpers/helpers"; - -describe("balance", () => { - const keypair = defaultSolanaWalletKeypair(); - const owner = keypair.publicKey.toBase58(); - const amount = 200; - - before(async () => { - await initTestEnvIfNeeded({ indexer: true, prover: true }); - await requestAirdrop(keypair.publicKey); - }); - - it(`get wrapped SOL balance for ${owner}`, async () => { - // Get initial balance first - const { stdout: initialStdout } = await runCommand([ - "balance", - `--owner=${owner}`, - ]); - - let initialBalance = 0; - if (initialStdout.includes("No accounts found")) { - initialBalance = 0; - } else { - const balanceMatch = initialStdout.match( - /Wrapped SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - initialBalance = parseInt(balanceMatch[1], 10); - } - } - console.log(`Initial balance captured: ${initialBalance}`); - - // Wrap SOL to create a balance to check - const { stdout: wrapStdout } = await runCommand([ - "wrap-sol", - `--amount=${amount}`, - `--to=${owner}`, - ]); - expect(wrapStdout).to.contain("wrap-sol successful"); - - // Test the balance command - const { stdout: finalStdout } = await runCommand([ - "balance", - `--owner=${owner}`, - ]); - - // Extract the balance - const balanceMatch = finalStdout.match(/Wrapped SOL balance:\s+(\d+)/); - expect(balanceMatch).to.not.be.null; - - if (balanceMatch && balanceMatch[1]) { - const currentBalance = parseInt(balanceMatch[1], 10); - console.log( - `Current balance: ${currentBalance}, Initial balance: ${initialBalance}, Expected increase: ${amount}`, - ); - - // Verify the balance increased by the wrapped amount - expect(currentBalance).to.equal(initialBalance + amount); - } else { - throw new Error("Could not extract balance from output"); - } - }); -}); diff --git a/cli/test/commands/unwrap-sol/index.test.ts b/cli/test/commands/unwrap-sol/index.test.ts deleted file mode 100644 index 1af957ff32..0000000000 --- a/cli/test/commands/unwrap-sol/index.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { runCommand } from "@oclif/test"; -import { expect } from "chai"; -import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; -import { defaultSolanaWalletKeypair } from "../../../src"; -import { requestAirdrop } from "../../helpers/helpers"; - -describe("unwrap-sol", () => { - const keypair = defaultSolanaWalletKeypair(); - const to = keypair.publicKey.toBase58(); - const amount = 200; - - before(async () => { - await initTestEnvIfNeeded({ indexer: true, prover: true }); - await requestAirdrop(keypair.publicKey); - }); - - it(`full wrap-check-unwrap-check cycle for ${amount} SOL to ${to}`, async () => { - // Get initial balance first - const { stdout: initialBalanceStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - let initialBalance = 0; - if (initialBalanceStdout.includes("No accounts found")) { - initialBalance = 0; - } else { - const balanceMatch = initialBalanceStdout.match( - /Wrapped SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - initialBalance = parseInt(balanceMatch[1], 10); - } - } - console.log(`Initial balance captured: ${initialBalance}`); - - // Wrap SOL - const { stdout: wrapStdout } = await runCommand([ - "wrap-sol", - `--amount=${amount}`, - `--to=${to}`, - ]); - expect(wrapStdout).to.contain("wrap-sol successful"); - - // Check balance after wrapping - const { stdout: afterWrapStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - const balanceMatchAfterWrap = afterWrapStdout.match( - /Wrapped SOL balance:\s+(\d+)/, - ); - expect(balanceMatchAfterWrap).to.not.be.null; - - let balanceAfterWrap = 0; - if (balanceMatchAfterWrap && balanceMatchAfterWrap[1]) { - balanceAfterWrap = parseInt(balanceMatchAfterWrap[1], 10); - console.log(`Balance after wrapping: ${balanceAfterWrap}`); - - // Verify the balance increased by the wrapped amount - expect(balanceAfterWrap).to.equal(initialBalance + amount); - } else { - throw new Error("Could not extract balance from output"); - } - - // Unwrap SOL - const { stdout: unwrapStdout } = await runCommand([ - "unwrap-sol", - `--amount=${amount}`, - `--to=${to}`, - ]); - expect(unwrapStdout).to.contain("unwrap-sol successful"); - - // Check balance after unwrapping - const { stdout: finalBalanceStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - // Extract the final balance - if (finalBalanceStdout.includes("No accounts found")) { - // If there were no accounts before wrapping, there should be none after unwrapping - expect(initialBalance).to.equal(0); - } else { - const balanceMatch = finalBalanceStdout.match( - /Wrapped SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - const finalBalance = parseInt(balanceMatch[1], 10); - console.log( - `Final balance: ${finalBalance}, Expected: ${balanceAfterWrap - amount}`, - ); - - // Verify the balance decreased by the unwrapped amount - expect(finalBalance).to.equal(balanceAfterWrap - amount); - } else { - // If we can't extract the balance but initial balance was equal to amount, - // we should get "No accounts found" - if (balanceAfterWrap === amount) { - expect(finalBalanceStdout).to.contain("No accounts found"); - } else { - throw new Error("Could not extract balance from output"); - } - } - } - }); -}); diff --git a/cli/test/commands/unwrap-sol/index.ts b/cli/test/commands/unwrap-sol/index.ts deleted file mode 100644 index ee3407d037..0000000000 --- a/cli/test/commands/unwrap-sol/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Command, Flags } from "@oclif/core"; -import { - CustomLoader, - defaultSolanaWalletKeypair, - generateSolanaTransactionURL, - rpc, -} from "../../utils/utils"; -import { PublicKey } from "@solana/web3.js"; -import { decompress } from "@lightprotocol/stateless.js"; - -class UnwrapSolCommand extends Command { - static summary = "Unwrap SOL from compressed account."; - - static examples = ["$ light unwrap-sol --to PublicKey --amount 10"]; - - static flags = { - to: Flags.string({ - description: "Specify the recipient address.", - required: true, - }), - amount: Flags.integer({ - description: "Amount to unwrap, in lamports.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(UnwrapSolCommand); - const to = flags["to"]; - const amount = flags["amount"]; - if (!to || !amount) { - throw new Error("Invalid arguments"); - } - - const loader = new CustomLoader(`Performing unwrap-sol...\n`); - loader.start(); - - try { - const toPublicKey = new PublicKey(to); - const payer = defaultSolanaWalletKeypair(); - - const txId = await decompress(rpc(), payer, amount, toPublicKey); - loader.stop(false); - console.log( - "\x1b[32munwrap-sol:\x1b[0m ", - generateSolanaTransactionURL("tx", txId, "custom"), - ); - console.log("unwrap-sol successful"); - } catch (error) { - this.error(`Failed to unwrap-sol!\n${error}`); - } - } -} - -export default UnwrapSolCommand; diff --git a/cli/test/commands/wrap-sol/index.test.ts b/cli/test/commands/wrap-sol/index.test.ts deleted file mode 100644 index 5f70a4c9f0..0000000000 --- a/cli/test/commands/wrap-sol/index.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { runCommand } from "@oclif/test"; -import { expect } from "chai"; -import { initTestEnvIfNeeded } from "../../../src/utils/initTestEnv"; -import { defaultSolanaWalletKeypair } from "../../../src"; -import { requestAirdrop } from "../../helpers/helpers"; - -describe("wrap-sol", () => { - const keypair = defaultSolanaWalletKeypair(); - const to = keypair.publicKey.toBase58(); - const amount = 1000_000; - let initialBalance = 0; - - before(async () => { - await initTestEnvIfNeeded({ indexer: true, prover: true }); - await requestAirdrop(keypair.publicKey); - }); - - it(`wrap-sol ${amount} lamports to ${to} and verify balance increase`, async () => { - // Get initial balance first - const { stdout: initialStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - let initialBalance = 0; - if (initialStdout.includes("No accounts found")) { - initialBalance = 0; - } else { - // Extract the balance number - const balanceMatch = initialStdout.match( - /Wrapped SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - initialBalance = parseInt(balanceMatch[1], 10); - } - } - console.log(`Initial balance captured: ${initialBalance}`); - - // Wrap SOL - const { stdout: wrapStdout } = await runCommand([ - "wrap-sol", - `--amount=${amount}`, - `--to=${to}`, - ]); - expect(wrapStdout).to.contain("wrap-sol successful"); - - // Check balance after wrapping - const { stdout: finalStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - // Extract the new balance - const balanceMatch = finalStdout.match(/Wrapped SOL balance:\s+(\d+)/); - expect(balanceMatch).to.not.be.null; - - if (balanceMatch && balanceMatch[1]) { - const newBalance = parseInt(balanceMatch[1], 10); - console.log( - `New balance: ${newBalance}, Initial balance: ${initialBalance}, Expected increase: ${amount}`, - ); - - // Verify the balance increased by the wrapped amount - expect(newBalance).to.equal(initialBalance + amount); - } else { - throw new Error("Could not extract balance from output"); - } - }); -}); diff --git a/cli/test/commands/wrap-sol/index.ts b/cli/test/commands/wrap-sol/index.ts deleted file mode 100644 index ea6ab70119..0000000000 --- a/cli/test/commands/wrap-sol/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Command, Flags } from "@oclif/core"; -import { - CustomLoader, - defaultSolanaWalletKeypair, - generateSolanaTransactionURL, - rpc, -} from "../../utils/utils"; -import { PublicKey } from "@solana/web3.js"; -import { compress } from "@lightprotocol/stateless.js"; - -class WrapSolCommand extends Command { - static summary = "Wrap SOL into compressed account."; - - static examples = ["$ light wrap-sol --to PublicKey --amount 10"]; - - static flags = { - to: Flags.string({ - description: "Specify the recipient address.", - required: true, - }), - amount: Flags.integer({ - description: "Amount to wrap, in lamports.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(WrapSolCommand); - const to = flags["to"]; - const amount = flags["amount"]; - if (!to || !amount) { - throw new Error("Invalid arguments"); - } - - const loader = new CustomLoader(`Performing wrap-sol...\n`); - loader.start(); - let txId; - try { - const toPublicKey = new PublicKey(to); - const payer = defaultSolanaWalletKeypair(); - - txId = await compress(rpc(), payer, amount, toPublicKey); - - loader.stop(false); - console.log( - "\x1b[32mtxId:\x1b[0m ", - generateSolanaTransactionURL("tx", txId, "custom"), - ); - console.log("wrap-sol successful"); - } catch (error) { - console.log("wrap-sol failed", txId); - this.error(`Failed to wrap-sol!\n${error}`); - } - } -} - -export default WrapSolCommand; From bac39788e629e89d279c3398183eb4517a77a6fc Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Thu, 19 Feb 2026 12:25:31 +0000 Subject: [PATCH 3/6] fix ci --- .github/workflows/cli-v1.yml | 76 ------------------------------------ 1 file changed, 76 deletions(-) delete mode 100644 .github/workflows/cli-v1.yml diff --git a/.github/workflows/cli-v1.yml b/.github/workflows/cli-v1.yml deleted file mode 100644 index b6dd4c82d5..0000000000 --- a/.github/workflows/cli-v1.yml +++ /dev/null @@ -1,76 +0,0 @@ -on: - push: - branches: - - main - pull_request: - branches: - - "*" - types: - - opened - - synchronize - - reopened - - ready_for_review - -name: cli-tests-v1 - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - cli-v1: - name: cli-v1 - if: github.event.pull_request.draft == false - runs-on: ubuntu-latest - - services: - redis: - image: redis:8.0.1 - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - env: - LIGHT_PROTOCOL_VERSION: V1 - REDIS_URL: redis://localhost:6379 - CI: true - - steps: - - name: Checkout sources - uses: actions/checkout@v6 - with: - submodules: true - - - name: Setup and build - uses: ./.github/actions/setup-and-build - with: - skip-components: "redis,disk-cleanup,go" - cache-key: "js" - - - name: Build stateless.js with V1 - run: | - cd js/stateless.js - pnpm build:v1 - - - name: Build compressed-token with V1 - run: | - cd js/compressed-token - pnpm build:v1 - - - name: Build CLI - run: | - just cli build - - - name: Run CLI tests with V1 - run: | - just cli test - - - name: Display prover logs on failure - if: failure() - run: | - echo "=== Displaying prover logs ===" - find cli/test-ledger -name "*prover*.log" -type f -exec echo "=== Contents of {} ===" \; -exec cat {} \; -exec echo "=== End of {} ===" \; || echo "No prover logs found" From 243683c7d240fe97e6eecd2bbec5c06d935b9ec1 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Thu, 19 Feb 2026 13:07:57 +0000 Subject: [PATCH 4/6] fix(cli): pass wallet key instead of ATA to transferInterface transferInterface expects a wallet public key (on-curve) as destination and derives the ATA internally. The CLI was passing a derived ATA which caused an off-curve validation error, silently failing the transfer. Also removes redundant createAtaInterfaceIdempotent call since transferInterface handles recipient ATA creation via ensureRecipientAta. --- cli/src/commands/transfer/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cli/src/commands/transfer/index.ts b/cli/src/commands/transfer/index.ts index 9aae910c5f..e1c20674e5 100644 --- a/cli/src/commands/transfer/index.ts +++ b/cli/src/commands/transfer/index.ts @@ -8,7 +8,6 @@ import { } from "../../utils/utils"; import { transferInterface, - createAtaInterfaceIdempotent, getAssociatedTokenAddressInterface, } from "@lightprotocol/compressed-token"; import { PublicKey } from "@solana/web3.js"; @@ -61,15 +60,13 @@ class TransferCommand extends Command { if (flags["fee-payer"] !== undefined) { payer = await getKeypairFromFile(flags["fee-payer"]); } - await createAtaInterfaceIdempotent(rpc(), payer, mintPublicKey, toPublicKey); const source = getAssociatedTokenAddressInterface(mintPublicKey, payer.publicKey); - const destination = getAssociatedTokenAddressInterface(mintPublicKey, toPublicKey); const txId = await transferInterface( rpc(), payer, source, mintPublicKey, - destination, + toPublicKey, payer, amount, ); From 4b73af8b38f8af9dfcc7c53987c80b87faffe6e0 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Thu, 19 Feb 2026 15:37:34 +0000 Subject: [PATCH 5/6] fix(cli): remove redundant decompressMint, update init description createMintInterface already embeds decompressMint as an action in the create-mint instruction data. The separate decompressMint call after mint creation was a wasted transaction. Also updates init command description to "Light Token". --- cli/src/commands/create-mint/index.ts | 6 +----- cli/src/commands/init/index.ts | 2 +- cli/test/helpers/helpers.ts | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/cli/src/commands/create-mint/index.ts b/cli/src/commands/create-mint/index.ts index b7288975ef..d697937048 100644 --- a/cli/src/commands/create-mint/index.ts +++ b/cli/src/commands/create-mint/index.ts @@ -6,10 +6,7 @@ import { getKeypairFromFile, rpc, } from "../../utils/utils"; -import { - createMintInterface, - decompressMint, -} from "@lightprotocol/compressed-token"; +import { createMintInterface } from "@lightprotocol/compressed-token"; import { Keypair } from "@solana/web3.js"; const DEFAULT_DECIMAL_COUNT = 9; @@ -57,7 +54,6 @@ class CreateMintCommand extends Command { mintDecimals, mintKeypair, ); - await decompressMint(rpc(), payer, mint); loader.stop(false); console.log("\x1b[1mMint public key:\x1b[0m ", mint.toBase58()); console.log( diff --git a/cli/src/commands/init/index.ts b/cli/src/commands/init/index.ts index b9740c7d0b..6876cc98e4 100644 --- a/cli/src/commands/init/index.ts +++ b/cli/src/commands/init/index.ts @@ -37,7 +37,7 @@ import { } from "case-anything"; export default class InitCommand extends Command { - static description = "Initialize a Light Protocol project."; + static description = "Initialize a Light Token project."; static args = { name: Args.string({ diff --git a/cli/test/helpers/helpers.ts b/cli/test/helpers/helpers.ts index ddaeee68b0..95aa2ac5ff 100644 --- a/cli/test/helpers/helpers.ts +++ b/cli/test/helpers/helpers.ts @@ -16,7 +16,6 @@ import { } from "@lightprotocol/stateless.js"; import { createMintInterface, - decompressMint, createSplInterface, mintTo, mintToInterface, @@ -49,7 +48,6 @@ export async function createTestMint(mintKeypair: Keypair) { mintKeypair, ); await confirmTx(rpc, transactionSignature); - await decompressMint(rpc, payer, mint); return mint; } From 5f0ad949f3cf6f448a2331cd1630403e1b704d6b Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Thu, 19 Feb 2026 15:51:22 +0000 Subject: [PATCH 6/6] fix(cli): replace V1 mintTo with SPL mintTo in test helpers Setup now mints SPL tokens to the payer's ATA instead of compressed tokens via the V1 path. Wrap test wraps directly, unwrap test wraps first then unwraps. --- .github/workflows/cli-v2.yml | 10 +++++----- cli/test/commands/unwrap-spl/index.test.ts | 8 ++++++++ cli/test/commands/wrap-spl/index.test.ts | 12 +----------- cli/test/helpers/helpers.ts | 16 ++++++++++++---- external/photon | 2 +- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cli-v2.yml b/.github/workflows/cli-v2.yml index edf752987e..dcd6f2aa3e 100644 --- a/.github/workflows/cli-v2.yml +++ b/.github/workflows/cli-v2.yml @@ -11,15 +11,15 @@ on: - reopened - ready_for_review -name: cli-tests-v2 +name: cli-tests concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - cli-v2: - name: cli-v2 + cli: + name: cli if: github.event.pull_request.draft == false runs-on: ubuntu-latest @@ -61,11 +61,11 @@ jobs: cd js/compressed-token pnpm build:v2 - - name: Build CLI with V2 + - name: Build CLI run: | just cli build - - name: Run CLI tests with V2 + - name: Run CLI tests run: | just cli test diff --git a/cli/test/commands/unwrap-spl/index.test.ts b/cli/test/commands/unwrap-spl/index.test.ts index 715edeb74b..1d695c9f1f 100644 --- a/cli/test/commands/unwrap-spl/index.test.ts +++ b/cli/test/commands/unwrap-spl/index.test.ts @@ -28,6 +28,14 @@ describe("unwrap-spl", () => { }); it(`unwrap tokens`, async () => { + // Wrap SPL tokens first to get Light Tokens + await runCommand([ + "wrap-spl", + `--mint=${mintKeypair.publicKey.toBase58()}`, + `--amount=${mintAmount}`, + `--to=${payerKeypair.publicKey.toBase58()}`, + ]); + const { stdout } = await runCommand([ "unwrap-spl", `--mint=${mintKeypair.publicKey.toBase58()}`, diff --git a/cli/test/commands/wrap-spl/index.test.ts b/cli/test/commands/wrap-spl/index.test.ts index fda6353ee7..ebe2a97380 100644 --- a/cli/test/commands/wrap-spl/index.test.ts +++ b/cli/test/commands/wrap-spl/index.test.ts @@ -28,20 +28,10 @@ describe("wrap-spl", () => { }); it(`wrap tokens`, async () => { - // First unwrap some tokens to have SPL tokens available - const { stdout: unwrapStdout } = await runCommand([ - "unwrap-spl", - `--mint=${mintKeypair.publicKey.toBase58()}`, - `--amount=${mintAmount - 1}`, - `--to=${payerKeypair.publicKey.toBase58()}`, - ]); - console.log(unwrapStdout); - - // Then wrap SPL tokens back const { stdout } = await runCommand([ "wrap-spl", `--mint=${mintKeypair.publicKey.toBase58()}`, - `--amount=${mintAmount - 2}`, + `--amount=${mintAmount - 1}`, `--to=${payerKeypair.publicKey.toBase58()}`, ]); expect(stdout).to.contain("wrap-spl successful"); diff --git a/cli/test/helpers/helpers.ts b/cli/test/helpers/helpers.ts index 95aa2ac5ff..aaaf4808c5 100644 --- a/cli/test/helpers/helpers.ts +++ b/cli/test/helpers/helpers.ts @@ -17,7 +17,6 @@ import { import { createMintInterface, createSplInterface, - mintTo, mintToInterface, createAtaInterfaceIdempotent, getAssociatedTokenAddressInterface, @@ -26,6 +25,8 @@ import { MINT_SIZE, TOKEN_PROGRAM_ID, createInitializeMint2Instruction, + mintTo as splMintTo, + createAssociatedTokenAccountIdempotent, } from "@solana/spl-token"; export async function requestAirdrop(address: PublicKey, amount = 3e9) { @@ -74,8 +75,8 @@ export async function testMintTo( } /** - * Create a standard SPL mint, register it with the compressed token program, - * and mint compressed tokens. Used by wrap/unwrap tests. + * Create a standard SPL mint, register it with the Light Token program, + * and mint SPL tokens to the destination's ATA. Used by wrap/unwrap tests. */ export async function createTestSplMintWithPool( mintKeypair: Keypair, @@ -88,11 +89,18 @@ export async function createTestSplMintWithPool( await createTestSplMint(rpc, payer, mintKeypair, mintAuthority); await createSplInterface(rpc, payer, mintKeypair.publicKey); - await mintTo( + + const ata = await createAssociatedTokenAccountIdempotent( rpc, payer, mintKeypair.publicKey, mintDestination, + ); + await splMintTo( + rpc, + payer, + mintKeypair.publicKey, + ata, mintAuthority, mintAmount, ); diff --git a/external/photon b/external/photon index 84ddfc0f58..0df2397c2c 160000 --- a/external/photon +++ b/external/photon @@ -1 +1 @@ -Subproject commit 84ddfc0f586806373567faf75f45158076a4f133 +Subproject commit 0df2397c2c7d8458f45df9279e999a730ba56482