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" 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/README.md b/cli/README.md index 4ed917026c..a0a93670e7 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,35 +197,22 @@ FLAGS ``` -#### Assign native SOL to a compressed account +#### Get token balance ```bash -light compress-sol --amount 1000 --to "YOUR_WALLET_ADDRESS_BASE58" +light token-balance --mint "YOUR_MINT_ADDRESS" --owner "OWNER_ADDRESS" ``` ``` USAGE - $ light compress-sol --to --amount + $ light token-balance --mint --owner FLAGS - --amount= (required) Amount to compress in lamports. - --to= (required) Specify the recipient address. -``` - -#### Decompress into native SOL - -```bash -light decompress-sol --amount 42 --to "YOUR_WALLET_ADDRESS_BASE58" + --mint= (required) Mint address of the token account. + --owner= (required) Address of the token owner. ``` -``` -USAGE - $ light decompress-sol --to --amount - -FLAGS - --amount= (required) Amount to decompress in lamports. - --to= (required) Specify the recipient address. -``` +Displays light token account (hot), compressed light token (cold), and total balances. ### Support diff --git a/cli/package.json b/cli/package.json index c29bcd265e..d25eafd6b1 100644 --- a/cli/package.json +++ b/cli/package.json @@ -105,21 +105,17 @@ "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-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-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-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 deleted file mode 100644 index ea5c33820c..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 compressed 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[1mCompressed 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/compress-sol/index.ts b/cli/src/commands/compress-sol/index.ts deleted file mode 100644 index 7b397a1489..0000000000 --- a/cli/src/commands/compress-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 CompressSolCommand extends Command { - static summary = "Compress SOL."; - - static examples = ["$ light compress-sol --to PublicKey --amount 10"]; - - static flags = { - to: Flags.string({ - description: "Specify the recipient address.", - required: true, - }), - amount: Flags.integer({ - description: "Amount to compress, in lamports.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(CompressSolCommand); - const to = flags["to"]; - const amount = flags["amount"]; - if (!to || !amount) { - throw new Error("Invalid arguments"); - } - - const loader = new CustomLoader(`Performing compress-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("compress-sol successful"); - } catch (error) { - console.log("compress-sol failed", txId); - this.error(`Failed to compress-sol!\n${error}`); - } - } -} - -export default CompressSolCommand; 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..d697937048 100644 --- a/cli/src/commands/create-mint/index.ts +++ b/cli/src/commands/create-mint/index.ts @@ -6,13 +6,13 @@ import { getKeypairFromFile, rpc, } from "../../utils/utils"; -import { createMint } from "@lightprotocol/compressed-token"; -import { Keypair, PublicKey } from "@solana/web3.js"; +import { createMintInterface } 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 +23,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,11 +45,12 @@ 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, ); @@ -76,9 +78,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/decompress-sol/index.ts b/cli/src/commands/decompress-sol/index.ts deleted file mode 100644 index c32cae20bf..0000000000 --- a/cli/src/commands/decompress-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 DecompressSolCommand extends Command { - static summary = "Decompress SOL."; - - static examples = ["$ light decompress-sol --to PublicKey --amount 10"]; - - static flags = { - to: Flags.string({ - description: "Specify the recipient address.", - required: true, - }), - amount: Flags.integer({ - description: "Amount to decompress, in lamports.", - required: true, - }), - }; - - static args = {}; - - async run() { - const { flags } = await this.parse(DecompressSolCommand); - const to = flags["to"]; - const amount = flags["amount"]; - if (!to || !amount) { - throw new Error("Invalid arguments"); - } - - const loader = new CustomLoader(`Performing decompress-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[32mdecompress-sol:\x1b[0m ", - generateSolanaTransactionURL("tx", txId, "custom"), - ); - console.log("decompress-sol successful"); - } catch (error) { - this.error(`Failed to decompress-sol!\n${error}`); - } - } -} - -export default DecompressSolCommand; diff --git a/cli/src/commands/init/index.ts b/cli/src/commands/init/index.ts index 726123d5d0..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 compressed account project."; + static description = "Initialize a Light Token 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..e1c20674e5 100644 --- a/cli/src/commands/transfer/index.ts +++ b/cli/src/commands/transfer/index.ts @@ -6,7 +6,10 @@ import { getKeypairFromFile, rpc, } from "../../utils/utils"; -import { transfer } from "@lightprotocol/compressed-token"; +import { + transferInterface, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; import { PublicKey } from "@solana/web3.js"; class TransferCommand extends Command { @@ -57,13 +60,15 @@ class TransferCommand extends Command { if (flags["fee-payer"] !== undefined) { payer = await getKeypairFromFile(flags["fee-payer"]); } - const txId = await transfer( + const source = getAssociatedTokenAddressInterface(mintPublicKey, payer.publicKey); + const txId = await transferInterface( rpc(), payer, + source, mintPublicKey, - amount, - payer, toPublicKey, + payer, + amount, ); loader.stop(false); console.log( 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-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 deleted file mode 100644 index 90097fcffb..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 compressed 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( - /Compressed SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - initialBalance = parseInt(balanceMatch[1], 10); - } - } - console.log(`Initial balance captured: ${initialBalance}`); - - // Compress SOL to create a balance to check - const { stdout: compressStdout } = await runCommand([ - "compress-sol", - `--amount=${amount}`, - `--to=${owner}`, - ]); - expect(compressStdout).to.contain("compress-sol successful"); - - // Test the balance command - const { stdout: finalStdout } = await runCommand([ - "balance", - `--owner=${owner}`, - ]); - - // Extract the balance - const balanceMatch = finalStdout.match(/Compressed 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 compressed amount - expect(currentBalance).to.equal(initialBalance + amount); - } else { - throw new Error("Could not extract balance from output"); - } - }); -}); diff --git a/cli/test/commands/compress-sol/index.test.ts b/cli/test/commands/compress-sol/index.test.ts deleted file mode 100644 index 09085b7998..0000000000 --- a/cli/test/commands/compress-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("compress-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(`compress-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( - /Compressed SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - initialBalance = parseInt(balanceMatch[1], 10); - } - } - console.log(`Initial balance captured: ${initialBalance}`); - - // Compress SOL - const { stdout: compressStdout } = await runCommand([ - "compress-sol", - `--amount=${amount}`, - `--to=${to}`, - ]); - expect(compressStdout).to.contain("compress-sol successful"); - - // Check balance after compression - const { stdout: finalStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - // Extract the new balance - const balanceMatch = finalStdout.match(/Compressed 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 compressed amount - expect(newBalance).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/decompress-sol/index.test.ts b/cli/test/commands/decompress-sol/index.test.ts deleted file mode 100644 index ee973aa2a7..0000000000 --- a/cli/test/commands/decompress-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("decompress-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 compress-check-decompress-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( - /Compressed SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - initialBalance = parseInt(balanceMatch[1], 10); - } - } - console.log(`Initial balance captured: ${initialBalance}`); - - // Compress SOL - const { stdout: compressStdout } = await runCommand([ - "compress-sol", - `--amount=${amount}`, - `--to=${to}`, - ]); - expect(compressStdout).to.contain("compress-sol successful"); - - // Check balance after compression - const { stdout: afterCompressStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - const balanceMatchAfterCompress = afterCompressStdout.match( - /Compressed SOL balance:\s+(\d+)/, - ); - expect(balanceMatchAfterCompress).to.not.be.null; - - let balanceAfterCompression = 0; - if (balanceMatchAfterCompress && balanceMatchAfterCompress[1]) { - balanceAfterCompression = parseInt(balanceMatchAfterCompress[1], 10); - console.log(`Balance after compression: ${balanceAfterCompression}`); - - // Verify the balance increased by the compressed amount - expect(balanceAfterCompression).to.equal(initialBalance + amount); - } else { - throw new Error("Could not extract balance from output"); - } - - // Decompress SOL - const { stdout: decompressStdout } = await runCommand([ - "decompress-sol", - `--amount=${amount}`, - `--to=${to}`, - ]); - expect(decompressStdout).to.contain("decompress-sol successful"); - - // Check balance after decompression - const { stdout: finalBalanceStdout } = await runCommand([ - "balance", - `--owner=${to}`, - ]); - - // Extract the final balance - if (finalBalanceStdout.includes("No accounts found")) { - // If there were no accounts before compression, there should be none after decompression - expect(initialBalance).to.equal(0); - } else { - const balanceMatch = finalBalanceStdout.match( - /Compressed SOL balance:\s+(\d+)/, - ); - if (balanceMatch && balanceMatch[1]) { - const finalBalance = parseInt(balanceMatch[1], 10); - console.log( - `Final balance: ${finalBalance}, Expected: ${balanceAfterCompression - amount}`, - ); - - // Verify the balance decreased by the decompressed amount - expect(finalBalance).to.equal(balanceAfterCompression - 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) { - expect(finalBalanceStdout).to.contain("No accounts found"); - } else { - throw new Error("Could not extract balance from output"); - } - } - } - }); -}); 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-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..1d695c9f1f 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,29 @@ 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 () => { + // 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([ - "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-spl/index.test.ts b/cli/test/commands/wrap-spl/index.test.ts similarity index 50% rename from cli/test/commands/compress-spl/index.test.ts rename to cli/test/commands/wrap-spl/index.test.ts index f80fa4a140..ebe2a97380 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,21 @@ 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", - `--mint=${mintKeypair.publicKey.toBase58()}`, - `--amount=${mintAmount - 1}`, - `--to=${payerKeypair.publicKey.toBase58()}`, - ]); - console.log(decompressStdout); - - // Then compress tokens + it(`wrap tokens`, async () => { const { stdout } = await runCommand([ - "compress-spl", + "wrap-spl", `--mint=${mintKeypair.publicKey.toBase58()}`, - `--amount=${mintAmount - 2}`, + `--amount=${mintAmount - 1}`, `--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..aaaf4808c5 100644 --- a/cli/test/helpers/helpers.ts +++ b/cli/test/helpers/helpers.ts @@ -5,39 +5,46 @@ 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, + createSplInterface, + mintToInterface, + createAtaInterfaceIdempotent, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; import { MINT_SIZE, TOKEN_PROGRAM_ID, createInitializeMint2Instruction, + mintTo as splMintTo, + createAssociatedTokenAccountIdempotent, } 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, ); @@ -52,20 +59,54 @@ 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 Light Token program, + * and mint SPL tokens to the destination's ATA. 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); + + const ata = await createAssociatedTokenAccountIdempotent( + rpc, + payer, + mintKeypair.publicKey, + mintDestination, + ); + await splMintTo( + rpc, + payer, + mintKeypair.publicKey, + ata, + mintAuthority, + mintAmount, + ); + return mintKeypair.publicKey; +} + export const TEST_TOKEN_DECIMALS = 2; export async function createTestSplMint( 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 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,