From da718b85af332c45179ea736205985593a2807c9 Mon Sep 17 00:00:00 2001 From: ananas Date: Wed, 21 Jan 2026 16:52:01 +0000 Subject: [PATCH 1/4] refactor: light token client to a clean minimal impl, move existing actions to test utils --- .github/workflows/sdk-tests.yml | 2 +- Cargo.lock | 24 + Cargo.toml | 1 + forester/Cargo.toml | 1 + forester/src/compressible/mint/compressor.rs | 2 +- forester/tests/e2e_test.rs | 10 +- forester/tests/test_compressible_ctoken.rs | 2 +- .../tests/compress_only/all.rs | 10 +- .../tests/compress_only/ata_decompress.rs | 6 +- .../compress_only/decompress_restrictions.rs | 14 +- .../compress_only/invalid_destination.rs | 14 +- .../compress_only/invalid_extension_state.rs | 12 +- .../tests/compress_only/mod.rs | 4 +- .../tests/compress_only/withheld_fee.rs | 6 +- .../tests/freeze/compress_only.rs | 6 +- .../tests/freeze/functional.rs | 8 +- .../tests/light_token/burn.rs | 26 +- .../tests/light_token/compress_and_close.rs | 40 +- .../tests/light_token/extensions.rs | 6 +- .../tests/light_token/shared.rs | 6 +- .../light_token/spl_instruction_compat.rs | 22 +- .../compressed-token-test/tests/mint/burn.rs | 26 +- .../tests/mint/cmint_resize.rs | 48 +- .../tests/mint/edge_cases.rs | 16 +- .../tests/mint/failing.rs | 86 ++-- .../tests/mint/functional.rs | 111 ++--- .../tests/mint/mint_to.rs | 26 +- .../tests/mint/random.rs | 16 +- .../tests/transfer2/compress_failing.rs | 40 +- .../tests/transfer2/decompress_failing.rs | 20 +- .../no_system_program_cpi_failing.rs | 40 +- .../tests/transfer2/shared.rs | 25 +- .../tests/transfer2/spl_ctoken.rs | 2 +- .../tests/transfer2/transfer_failing.rs | 6 +- .../registry-test/tests/compressible.rs | 17 +- program-tests/utils/Cargo.toml | 8 + .../create_compressible_token_account.rs | 0 .../utils/src/actions/legacy/create_mint.rs | 61 +++ .../legacy}/instructions/create_mint.rs | 2 +- .../legacy}/instructions/mint_action.rs | 0 .../instructions/mint_to_compressed.rs | 0 .../src/actions/legacy}/instructions/mod.rs | 0 .../actions/legacy}/instructions/transfer2.rs | 0 .../instructions/update_compressed_mint.rs | 0 .../utils/src/actions/legacy}/mint_action.rs | 4 +- .../src/actions/legacy}/mint_to_compressed.rs | 2 +- program-tests/utils/src/actions/legacy/mod.rs | 18 + .../src/actions/legacy}/spl_interface.rs | 0 .../utils/src/actions/legacy/transfer.rs | 78 ++++ .../src/actions/legacy}/transfer2/approve.rs | 2 +- .../src/actions/legacy}/transfer2/compress.rs | 2 +- .../legacy}/transfer2/compress_and_close.rs | 2 +- .../legacy}/transfer2/ctoken_to_spl.rs | 0 .../actions/legacy}/transfer2/decompress.rs | 2 +- .../src/actions/legacy}/transfer2/mod.rs | 0 .../legacy}/transfer2/spl_to_ctoken.rs | 0 .../src/actions/legacy}/transfer2/transfer.rs | 2 +- .../legacy}/transfer2/transfer_delegated.rs | 2 +- .../actions/legacy}/update_compressed_mint.rs | 2 +- program-tests/utils/src/actions/mod.rs | 6 + program-tests/utils/src/assert_mint_action.rs | 3 +- program-tests/utils/src/assert_transfer2.rs | 8 +- program-tests/utils/src/lib.rs | 1 + sdk-libs/token-client/src/actions/approve.rs | 122 +++++ .../token-client/src/actions/create_ata.rs | 99 ++++ .../token-client/src/actions/create_mint.rs | 215 +++++++-- sdk-libs/token-client/src/actions/mint_to.rs | 75 +++ sdk-libs/token-client/src/actions/mod.rs | 50 +- sdk-libs/token-client/src/actions/revoke.rs | 110 +++++ sdk-libs/token-client/src/actions/transfer.rs | 129 +++--- .../src/actions/transfer_checked.rs | 86 ++++ .../src/actions/transfer_interface.rs | 122 +++++ sdk-libs/token-client/src/actions/unwrap.rs | 84 ++++ sdk-libs/token-client/src/actions/wrap.rs | 85 ++++ sdk-libs/token-client/src/lib.rs | 6 +- .../src/instruction/approve_checked.rs | 144 ------ sdk-libs/token-sdk/src/instruction/mod.rs | 2 - .../sdk-light-token-test/tests/shared.rs | 4 +- .../tests/test_decompress_mint.rs | 4 +- .../tests/decompress_full_cpi.rs | 6 +- .../sdk-token-test/tests/test_4_transfer2.rs | 2 +- .../tests/test_compress_full_and_close.rs | 2 +- sdk-tests/token-client-test/Cargo.toml | 29 ++ .../tests/test_approve_revoke.rs | 427 ++++++++++++++++++ .../tests/test_create_mint.rs | 160 +++++++ .../token-client-test/tests/test_transfer.rs | 193 ++++++++ .../tests/test_transfer_checked.rs | 206 +++++++++ .../tests/test_transfer_interface.rs | 215 +++++++++ .../tests/test_wrap_unwrap.rs | 328 ++++++++++++++ 89 files changed, 3205 insertions(+), 606 deletions(-) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/create_compressible_token_account.rs (100%) create mode 100644 program-tests/utils/src/actions/legacy/create_mint.rs rename {sdk-libs/token-client/src => program-tests/utils/src/actions/legacy}/instructions/create_mint.rs (95%) rename {sdk-libs/token-client/src => program-tests/utils/src/actions/legacy}/instructions/mint_action.rs (100%) rename {sdk-libs/token-client/src => program-tests/utils/src/actions/legacy}/instructions/mint_to_compressed.rs (100%) rename {sdk-libs/token-client/src => program-tests/utils/src/actions/legacy}/instructions/mod.rs (100%) rename {sdk-libs/token-client/src => program-tests/utils/src/actions/legacy}/instructions/transfer2.rs (100%) rename {sdk-libs/token-client/src => program-tests/utils/src/actions/legacy}/instructions/update_compressed_mint.rs (100%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/mint_action.rs (98%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/mint_to_compressed.rs (96%) create mode 100644 program-tests/utils/src/actions/legacy/mod.rs rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/spl_interface.rs (100%) create mode 100644 program-tests/utils/src/actions/legacy/transfer.rs rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/approve.rs (97%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/compress.rs (99%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/compress_and_close.rs (97%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/ctoken_to_spl.rs (100%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/decompress.rs (97%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/mod.rs (100%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/spl_to_ctoken.rs (100%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/transfer.rs (97%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/transfer2/transfer_delegated.rs (98%) rename {sdk-libs/token-client/src/actions => program-tests/utils/src/actions/legacy}/update_compressed_mint.rs (98%) create mode 100644 program-tests/utils/src/actions/mod.rs create mode 100644 sdk-libs/token-client/src/actions/approve.rs create mode 100644 sdk-libs/token-client/src/actions/create_ata.rs create mode 100644 sdk-libs/token-client/src/actions/mint_to.rs create mode 100644 sdk-libs/token-client/src/actions/revoke.rs create mode 100644 sdk-libs/token-client/src/actions/transfer_checked.rs create mode 100644 sdk-libs/token-client/src/actions/transfer_interface.rs create mode 100644 sdk-libs/token-client/src/actions/unwrap.rs create mode 100644 sdk-libs/token-client/src/actions/wrap.rs delete mode 100644 sdk-libs/token-sdk/src/instruction/approve_checked.rs create mode 100644 sdk-tests/token-client-test/Cargo.toml create mode 100644 sdk-tests/token-client-test/tests/test_approve_revoke.rs create mode 100644 sdk-tests/token-client-test/tests/test_create_mint.rs create mode 100644 sdk-tests/token-client-test/tests/test_transfer.rs create mode 100644 sdk-tests/token-client-test/tests/test_transfer_checked.rs create mode 100644 sdk-tests/token-client-test/tests/test_transfer_interface.rs create mode 100644 sdk-tests/token-client-test/tests/test_wrap_unwrap.rs diff --git a/.github/workflows/sdk-tests.yml b/.github/workflows/sdk-tests.yml index 0b2669aec1..98d50f2e22 100644 --- a/.github/workflows/sdk-tests.yml +++ b/.github/workflows/sdk-tests.yml @@ -52,7 +52,7 @@ jobs: - program: anchor & pinocchio sub-tests: '["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk-compressible-test", "cargo-test-sbf -p csdk-anchor-derived-test", "cargo-test-sbf -p csdk-anchor-full-derived-test", "cargo-test-sbf -p sdk-pinocchio-v1-test", "cargo-test-sbf -p sdk-pinocchio-v2-test", "cargo-test-sbf -p pinocchio-nostd-test", "cargo-test-sbf -p single-mint-test", "cargo-test-sbf -p single-pda-test", "cargo-test-sbf -p single-ata-test", "cargo-test-sbf -p single-token-test"]' - program: token test - sub-tests: '["cargo-test-sbf -p sdk-token-test"]' + sub-tests: '["cargo-test-sbf -p sdk-token-test", "cargo-test-sbf -p token-client-test"]' - program: sdk-libs test_cmd: just sdk-libs test steps: diff --git a/Cargo.lock b/Cargo.lock index a107e70b97..b53dd361f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4166,6 +4166,7 @@ dependencies = [ "anchor-lang", "anchor-spl", "base64 0.13.1", + "borsh 0.10.4", "create-address-test-program", "forester-utils", "light-account-checks", @@ -4191,6 +4192,7 @@ dependencies = [ "light-token", "light-token-client", "light-token-interface", + "light-token-types", "light-zero-copy", "log", "num-bigint 0.4.6", @@ -4198,7 +4200,13 @@ dependencies = [ "rand 0.8.5", "reqwest 0.12.26", "solana-banks-client", + "solana-instruction", + "solana-keypair", + "solana-msg 2.2.1", + "solana-pubkey 2.4.0", "solana-sdk", + "solana-signature", + "solana-signer", "solana-system-interface 1.0.0", "spl-pod", "spl-token 7.0.0", @@ -10593,6 +10601,22 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token-client-test" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "light-client", + "light-program-test", + "light-test-utils", + "light-token", + "light-token-client", + "light-token-interface", + "solana-sdk", + "spl-token 7.0.0", + "tokio", +] + [[package]] name = "tokio" version = "1.48.0" diff --git a/Cargo.toml b/Cargo.toml index dc679fc404..c552f04eb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "sdk-tests/sdk-v1-native-test", "sdk-tests/sdk-token-test", "sdk-tests/sdk-light-token-test", + "sdk-tests/token-client-test", "sdk-tests/csdk-anchor-full-derived-test", "sdk-tests/csdk-anchor-full-derived-test-sdk", "sdk-tests/single-mint-test", diff --git a/forester/Cargo.toml b/forester/Cargo.toml index 5b23dc2855..c648a54156 100644 --- a/forester/Cargo.toml +++ b/forester/Cargo.toml @@ -31,6 +31,7 @@ light-compressible = { workspace = true, default-features = false, features = [" light-token-interface = { workspace = true } light-token-client = { workspace = true } light-token = { workspace = true } +light-test-utils = { workspace = true } light-compressed-token-sdk = { workspace = true } solana-rpc-client-api = { workspace = true } solana-transaction-status = { workspace = true } diff --git a/forester/src/compressible/mint/compressor.rs b/forester/src/compressible/mint/compressor.rs index 259416f05d..db3d4bd6d2 100644 --- a/forester/src/compressible/mint/compressor.rs +++ b/forester/src/compressible/mint/compressor.rs @@ -6,7 +6,7 @@ use std::sync::{ use forester_utils::rpc_pool::SolanaRpcPool; use futures::StreamExt; use light_client::{indexer::Indexer, rpc::Rpc}; -use light_token_client::instructions::mint_action::{ +use light_test_utils::actions::legacy::instructions::mint_action::{ create_mint_action_instruction, MintActionParams, MintActionType, }; use solana_sdk::{ diff --git a/forester/tests/e2e_test.rs b/forester/tests/e2e_test.rs index 70bb748ea9..0f0387bb0c 100644 --- a/forester/tests/e2e_test.rs +++ b/forester/tests/e2e_test.rs @@ -39,14 +39,14 @@ use light_hasher::Poseidon; use light_program_test::accounts::test_accounts::TestAccounts; use light_prover_client::prover::spawn_prover; use light_test_utils::{ - conversions::sdk_to_program_token_data, get_concurrent_merkle_tree, get_indexed_merkle_tree, - pack::pack_new_address_params_assigned, spl::create_mint_helper_with_keypair, + actions::{create_compressible_token_account, CreateCompressibleTokenAccountInputs}, + conversions::sdk_to_program_token_data, + get_concurrent_merkle_tree, get_indexed_merkle_tree, + pack::pack_new_address_params_assigned, + spl::create_mint_helper_with_keypair, system_program::create_invoke_instruction, }; use light_token::compat::TokenDataWithMerkleContext; -use light_token_client::actions::{ - create_compressible_token_account, CreateCompressibleTokenAccountInputs, -}; use light_token_interface::state::TokenDataVersion; use rand::{prelude::SliceRandom, rngs::StdRng, Rng, SeedableRng}; use serial_test::serial; diff --git a/forester/tests/test_compressible_ctoken.rs b/forester/tests/test_compressible_ctoken.rs index c4ba53697e..4bd135b9b5 100644 --- a/forester/tests/test_compressible_ctoken.rs +++ b/forester/tests/test_compressible_ctoken.rs @@ -21,7 +21,7 @@ use light_registry::{ utils::{get_forester_pda, get_protocol_config_pda_address}, ForesterConfig, }; -use light_token_client::actions::{ +use light_test_utils::actions::legacy::{ create_compressible_token_account, CreateCompressibleTokenAccountInputs, }; use light_token_interface::state::TokenDataVersion; diff --git a/program-tests/compressed-token-test/tests/compress_only/all.rs b/program-tests/compressed-token-test/tests/compress_only/all.rs index 89be839c88..1dca92d9ea 100644 --- a/program-tests/compressed-token-test/tests/compress_only/all.rs +++ b/program-tests/compressed-token-test/tests/compress_only/all.rs @@ -21,11 +21,13 @@ async fn test_compress_and_close_ctoken_with_extensions() { use light_client::indexer::CompressedTokenAccount; use light_client::indexer::Indexer; use light_compressed_token_sdk::spl_interface::find_spl_interface_pda_with_index; - use light_test_utils::mint_2022::{create_token_22_account, mint_spl_tokens_22}; - use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}; - use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + }, + mint_2022::{create_token_22_account, mint_spl_tokens_22}, }; + use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}; use light_token_interface::{ instructions::extensions::{ CompressedOnlyExtensionInstructionData, ExtensionInstructionData, diff --git a/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs b/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs index 3fff58d3ac..1f5f9453be 100644 --- a/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs +++ b/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs @@ -9,6 +9,9 @@ use light_program_test::{ program_test::TestRpc, utils::assert::assert_rpc_error, LightProgramTest, ProgramTestConfig, }; use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + }, mint_2022::{ create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22, RESTRICTED_EXTENSIONS, @@ -19,9 +22,6 @@ use light_token::instruction::{ derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, CreateTokenAccount, TransferFromSpl, }; -use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, -}; use light_token_interface::{ instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, state::{ExtensionStruct, TokenDataVersion}, diff --git a/program-tests/compressed-token-test/tests/compress_only/decompress_restrictions.rs b/program-tests/compressed-token-test/tests/compress_only/decompress_restrictions.rs index 430b3ed59a..4f23f42292 100644 --- a/program-tests/compressed-token-test/tests/compress_only/decompress_restrictions.rs +++ b/program-tests/compressed-token-test/tests/compress_only/decompress_restrictions.rs @@ -11,14 +11,16 @@ use light_program_test::{ utils::assert::assert_rpc_error, ProgramTestConfig, Rpc, }; -use light_test_utils::mint_2022::{ - create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22, - RESTRICTED_EXTENSIONS, +use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + }, + mint_2022::{ + create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22, + RESTRICTED_EXTENSIONS, + }, }; use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}; -use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, -}; use light_token_interface::{ instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, state::TokenDataVersion, diff --git a/program-tests/compressed-token-test/tests/compress_only/invalid_destination.rs b/program-tests/compressed-token-test/tests/compress_only/invalid_destination.rs index b9c35082eb..e085af3c5b 100644 --- a/program-tests/compressed-token-test/tests/compress_only/invalid_destination.rs +++ b/program-tests/compressed-token-test/tests/compress_only/invalid_destination.rs @@ -14,14 +14,16 @@ use light_program_test::{ utils::assert::assert_rpc_error, ProgramTestConfig, Rpc, }; -use light_test_utils::mint_2022::{ - create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22, - RESTRICTED_EXTENSIONS, +use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + }, + mint_2022::{ + create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22, + RESTRICTED_EXTENSIONS, + }, }; use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}; -use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, -}; use light_token_interface::{ instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, state::TokenDataVersion, diff --git a/program-tests/compressed-token-test/tests/compress_only/invalid_extension_state.rs b/program-tests/compressed-token-test/tests/compress_only/invalid_extension_state.rs index e54222acde..3122555dd4 100644 --- a/program-tests/compressed-token-test/tests/compress_only/invalid_extension_state.rs +++ b/program-tests/compressed-token-test/tests/compress_only/invalid_extension_state.rs @@ -18,15 +18,17 @@ use light_program_test::{ utils::assert::assert_rpc_error, ProgramTestConfig, Rpc, }; -use light_test_utils::mint_2022::{ - create_token_22_account, mint_spl_tokens_22, set_mint_transfer_fee, set_mint_transfer_hook, +use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + }, + mint_2022::{ + create_token_22_account, mint_spl_tokens_22, set_mint_transfer_fee, set_mint_transfer_hook, + }, }; use light_token::instruction::{ CompressibleParams, CreateTokenAccount, TransferFromSpl, TransferToSpl, }; -use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, -}; use light_token_interface::{ find_spl_interface_pda_with_index, instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, diff --git a/program-tests/compressed-token-test/tests/compress_only/mod.rs b/program-tests/compressed-token-test/tests/compress_only/mod.rs index f0aeff10c7..f0fc95c6cf 100644 --- a/program-tests/compressed-token-test/tests/compress_only/mod.rs +++ b/program-tests/compressed-token-test/tests/compress_only/mod.rs @@ -155,10 +155,10 @@ pub async fn run_compress_and_close_extension_test( ) -> Result<(), RpcError> { use light_client::indexer::Indexer; use light_compressed_token_sdk::spl_interface::find_spl_interface_pda_with_index; - use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}; - use light_token_client::instructions::transfer2::{ + use light_test_utils::actions::legacy::instructions::transfer2::{ create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, }; + use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}; use light_token_interface::{ instructions::extensions::{ CompressedOnlyExtensionInstructionData, ExtensionInstructionData, diff --git a/program-tests/compressed-token-test/tests/compress_only/withheld_fee.rs b/program-tests/compressed-token-test/tests/compress_only/withheld_fee.rs index 0caa35e5ca..65d720d7f0 100644 --- a/program-tests/compressed-token-test/tests/compress_only/withheld_fee.rs +++ b/program-tests/compressed-token-test/tests/compress_only/withheld_fee.rs @@ -8,13 +8,13 @@ use light_client::indexer::Indexer; use light_compressed_token_sdk::spl_interface::find_spl_interface_pda_with_index; use light_program_test::{program_test::TestRpc, LightProgramTest, ProgramTestConfig}; use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + }, mint_2022::{create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22}, Rpc, RpcError, }; use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}; -use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, -}; use light_token_interface::{ instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, state::{ diff --git a/program-tests/compressed-token-test/tests/freeze/compress_only.rs b/program-tests/compressed-token-test/tests/freeze/compress_only.rs index f54429f5de..d9d7eebd75 100644 --- a/program-tests/compressed-token-test/tests/freeze/compress_only.rs +++ b/program-tests/compressed-token-test/tests/freeze/compress_only.rs @@ -10,6 +10,9 @@ use light_compressed_token::freeze::sdk::{ use light_compressed_token_sdk::spl_interface::find_spl_interface_pda_with_index; use light_program_test::{program_test::TestRpc, LightProgramTest, ProgramTestConfig}; use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, + }, conversions::sdk_to_program_token_data, mint_2022::{ create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22, @@ -21,9 +24,6 @@ use light_token::{ compat::{AccountState, TokenDataWithMerkleContext}, instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl}, }; -use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, -}; use light_token_interface::{ instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData}, state::TokenDataVersion, diff --git a/program-tests/compressed-token-test/tests/freeze/functional.rs b/program-tests/compressed-token-test/tests/freeze/functional.rs index 4796a59a71..5d43d66015 100644 --- a/program-tests/compressed-token-test/tests/freeze/functional.rs +++ b/program-tests/compressed-token-test/tests/freeze/functional.rs @@ -10,11 +10,13 @@ use light_compressed_token::freeze::sdk::{ use light_program_test::{LightProgramTest, ProgramTestConfig}; use light_prover_client::prover::spawn_prover; use light_test_utils::{ - conversions::sdk_to_program_token_data, mint_2022::create_token_22_account, - spl::create_mint_22_helper, Rpc, RpcError, + actions::transfer2::{compress_with_version, decompress}, + conversions::sdk_to_program_token_data, + mint_2022::create_token_22_account, + spl::create_mint_22_helper, + Rpc, RpcError, }; use light_token::compat::{AccountState, TokenDataWithMerkleContext}; -use light_token_client::actions::transfer2::{compress_with_version, decompress}; use light_token_interface::state::TokenDataVersion; use serial_test::serial; use solana_sdk::{program_pack::Pack, pubkey::Pubkey, signature::Keypair, signer::Signer}; diff --git a/program-tests/compressed-token-test/tests/light_token/burn.rs b/program-tests/compressed-token-test/tests/light_token/burn.rs index 8490655227..01ba115ef1 100644 --- a/program-tests/compressed-token-test/tests/light_token/burn.rs +++ b/program-tests/compressed-token-test/tests/light_token/burn.rs @@ -21,9 +21,11 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::find_m use light_program_test::{ program_test::TestRpc, utils::assert::assert_rpc_error, LightProgramTest, ProgramTestConfig, }; -use light_test_utils::assert_ctoken_burn::assert_ctoken_burn; +use light_test_utils::{ + actions::legacy::instructions::mint_action::DecompressMintParams, + assert_ctoken_burn::assert_ctoken_burn, +}; use light_token::instruction::{derive_token_ata, Burn, CreateAssociatedTokenAccount, MintTo}; -use light_token_client::instructions::mint_action::DecompressMintParams; use super::shared::*; @@ -346,7 +348,7 @@ async fn setup_burn_test() -> BurnTestContext { .unwrap(); // Step 2: Create compressed mint + Mint (no recipients) - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -357,14 +359,16 @@ async fn setup_burn_test() -> BurnTestContext { vec![], // No ctoken recipients None, // No mint authority update None, // No freeze authority update - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 8, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 8, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, + }, + ), ) .await .unwrap(); diff --git a/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs b/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs index d858945406..ec78b45615 100644 --- a/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs +++ b/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs @@ -101,7 +101,7 @@ async fn test_compress_and_close_owner_scenarios() { .unwrap(); // Create compress_and_close instruction with is_compressible=false for non-compressible account - use light_token_client::instructions::transfer2::{ + use light_test_utils::actions::legacy::instructions::transfer2::{ create_generic_transfer2_instruction, CompressAndCloseInput, Transfer2InstructionType, }; @@ -180,7 +180,7 @@ async fn test_compress_and_close_owner_scenarios() { context.rpc.set_account(ata_pubkey, ata_account); // Create compress_and_close instruction manually for ATA - use light_token_client::instructions::transfer2::{ + use light_test_utils::actions::legacy::instructions::transfer2::{ create_generic_transfer2_instruction, CompressAndCloseInput, Transfer2InstructionType, }; @@ -289,8 +289,10 @@ async fn test_compress_and_close_rent_authority_scenarios() { .unwrap(); // Assert compress and close succeeded - use light_test_utils::assert_transfer2::assert_transfer2_compress_and_close; - use light_token_client::instructions::transfer2::CompressAndCloseInput; + use light_test_utils::{ + actions::legacy::instructions::transfer2::CompressAndCloseInput, + assert_transfer2::assert_transfer2_compress_and_close, + }; let output_queue = context.rpc.get_random_state_tree_info().unwrap().queue; assert_transfer2_compress_and_close( @@ -342,8 +344,10 @@ async fn test_compress_and_close_rent_authority_scenarios() { .unwrap(); // Assert compress and close succeeded - use light_test_utils::assert_transfer2::assert_transfer2_compress_and_close; - use light_token_client::instructions::transfer2::CompressAndCloseInput; + use light_test_utils::{ + actions::legacy::instructions::transfer2::CompressAndCloseInput, + assert_transfer2::assert_transfer2_compress_and_close, + }; let output_queue = context.rpc.get_random_state_tree_info().unwrap().queue; assert_transfer2_compress_and_close( @@ -400,8 +404,10 @@ async fn test_compress_and_close_rent_authority_scenarios() { .unwrap(); // Assert compress and close succeeded - use light_test_utils::assert_transfer2::assert_transfer2_compress_and_close; - use light_token_client::instructions::transfer2::CompressAndCloseInput; + use light_test_utils::{ + actions::legacy::instructions::transfer2::CompressAndCloseInput, + assert_transfer2::assert_transfer2_compress_and_close, + }; let output_queue = context.rpc.get_random_state_tree_info().unwrap().queue; assert_transfer2_compress_and_close( @@ -481,8 +487,10 @@ async fn test_compress_and_close_compress_to_pubkey() { .unwrap(); // Assert compress and close succeeded - the owner in compressed output should be the token_account_pubkey - use light_test_utils::assert_transfer2::assert_transfer2_compress_and_close; - use light_token_client::instructions::transfer2::CompressAndCloseInput; + use light_test_utils::{ + actions::legacy::instructions::transfer2::CompressAndCloseInput, + assert_transfer2::assert_transfer2_compress_and_close, + }; let output_queue = context.rpc.get_random_state_tree_info().unwrap().queue; assert_transfer2_compress_and_close( @@ -880,8 +888,10 @@ async fn test_compress_and_close_output_validation_errors() { .unwrap(); // Assert compress and close succeeded - use light_test_utils::assert_transfer2::assert_transfer2_compress_and_close; - use light_token_client::instructions::transfer2::CompressAndCloseInput; + use light_test_utils::{ + actions::legacy::instructions::transfer2::CompressAndCloseInput, + assert_transfer2::assert_transfer2_compress_and_close, + }; let output_queue = context.rpc.get_random_state_tree_info().unwrap().queue; assert_transfer2_compress_and_close( @@ -957,8 +967,10 @@ async fn test_compress_and_close_output_validation_errors() { .unwrap(); // Assert compress and close succeeded - use light_test_utils::assert_transfer2::assert_transfer2_compress_and_close; - use light_token_client::instructions::transfer2::CompressAndCloseInput; + use light_test_utils::{ + actions::legacy::instructions::transfer2::CompressAndCloseInput, + assert_transfer2::assert_transfer2_compress_and_close, + }; let output_queue = context.rpc.get_random_state_tree_info().unwrap().queue; assert_transfer2_compress_and_close( diff --git a/program-tests/compressed-token-test/tests/light_token/extensions.rs b/program-tests/compressed-token-test/tests/light_token/extensions.rs index 5b1c737e87..1061b5bd5e 100644 --- a/program-tests/compressed-token-test/tests/light_token/extensions.rs +++ b/program-tests/compressed-token-test/tests/light_token/extensions.rs @@ -5,15 +5,15 @@ use light_program_test::{utils::assert::assert_rpc_error, LightProgramTest, ProgramTestConfig}; use light_test_utils::{ + actions::legacy::instructions::transfer2::{ + create_generic_transfer2_instruction, CompressInput, Transfer2InstructionType, + }, mint_2022::{ create_mint_22_with_extensions, create_token_22_account, mint_spl_tokens_22, Token22ExtensionConfig, }, Rpc, RpcError, }; -use light_token_client::instructions::transfer2::{ - create_generic_transfer2_instruction, CompressInput, Transfer2InstructionType, -}; use light_token_interface::state::{ ExtensionStruct, PausableAccountExtension, PermanentDelegateAccountExtension, TransferFeeAccountExtension, TransferHookAccountExtension, ACCOUNT_TYPE_TOKEN_ACCOUNT, diff --git a/program-tests/compressed-token-test/tests/light_token/shared.rs b/program-tests/compressed-token-test/tests/light_token/shared.rs index d177bf1665..78811c89bc 100644 --- a/program-tests/compressed-token-test/tests/light_token/shared.rs +++ b/program-tests/compressed-token-test/tests/light_token/shared.rs @@ -6,6 +6,7 @@ pub use light_program_test::{ }; use light_registry::compressible::compressed_token::CompressAndCloseIndices; pub use light_test_utils::{ + actions::legacy::{instructions::transfer2::CompressInput, transfer2::compress}, assert_close_token_account::assert_close_token_account, assert_create_token_account::{ assert_create_associated_token_account, assert_create_token_account, CompressibleData, @@ -18,9 +19,6 @@ pub use light_token::instruction::{ derive_token_ata, Approve, CloseAccount, CompressibleParams, CreateAssociatedTokenAccount, CreateTokenAccount, Revoke, }; -pub use light_token_client::{ - actions::transfer2::compress, instructions::transfer2::CompressInput, -}; pub use serial_test::serial; pub use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; /// Shared test context for account operations @@ -575,7 +573,7 @@ pub async fn compress_and_close_and_assert_fails( name: &str, expected_error_code: u32, ) { - use light_token_client::instructions::transfer2::{ + use light_test_utils::actions::legacy::instructions::transfer2::{ create_generic_transfer2_instruction, CompressAndCloseInput, Transfer2InstructionType, }; diff --git a/program-tests/compressed-token-test/tests/light_token/spl_instruction_compat.rs b/program-tests/compressed-token-test/tests/light_token/spl_instruction_compat.rs index 71ec389827..5084f479f9 100644 --- a/program-tests/compressed-token-test/tests/light_token/spl_instruction_compat.rs +++ b/program-tests/compressed-token-test/tests/light_token/spl_instruction_compat.rs @@ -426,7 +426,7 @@ async fn test_spl_instruction_compatibility() { async fn test_spl_instruction_compatibility_with_mint() { use light_compressed_token_sdk::compressed_token::create_compressed_mint::find_mint_address; use light_program_test::ProgramTestConfig; - use light_token_client::instructions::mint_action::DecompressMintParams; + use light_test_utils::actions::legacy::instructions::mint_action::DecompressMintParams; // Set up test environment let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(false, None)) @@ -446,7 +446,7 @@ async fn test_spl_instruction_compatibility_with_mint() { println!("Creating decompressed mint with freeze authority..."); // Create compressed mint + Mint (decompressed mint) - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -457,14 +457,16 @@ async fn test_spl_instruction_compatibility_with_mint() { vec![], // No ctoken recipients None, // No mint authority update None, // No freeze authority update - Some(light_token_client::instructions::mint_action::NewMint { - decimals, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: Some(freeze_authority.pubkey()), - metadata: None, - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: Some(freeze_authority.pubkey()), + metadata: None, + version: 3, + }, + ), ) .await .unwrap(); diff --git a/program-tests/compressed-token-test/tests/mint/burn.rs b/program-tests/compressed-token-test/tests/mint/burn.rs index a17d1a45ef..259ca534e2 100644 --- a/program-tests/compressed-token-test/tests/mint/burn.rs +++ b/program-tests/compressed-token-test/tests/mint/burn.rs @@ -1,8 +1,10 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::find_mint_address; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_test_utils::{assert_ctoken_burn::assert_ctoken_burn, Rpc}; +use light_test_utils::{ + actions::legacy::instructions::mint_action::DecompressMintParams, + assert_ctoken_burn::assert_ctoken_burn, Rpc, +}; use light_token::instruction::{derive_token_ata, Burn, CreateAssociatedTokenAccount}; -use light_token_client::instructions::mint_action::DecompressMintParams; use light_token_interface::instructions::mint_action::Recipient; use serial_test::serial; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; @@ -50,7 +52,7 @@ async fn setup_burn_test(mint_amount: u64) -> BurnTestContext { .unwrap(); // Step 2: Create compressed mint + Mint + mint tokens in one call - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -64,14 +66,16 @@ async fn setup_burn_test(mint_amount: u64) -> BurnTestContext { }], // Mint to Light Token in same tx None, // No mint authority update None, // No freeze authority update - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 8, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 8, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, + }, + ), ) .await .unwrap(); diff --git a/program-tests/compressed-token-test/tests/mint/cmint_resize.rs b/program-tests/compressed-token-test/tests/mint/cmint_resize.rs index 1e654664c7..be80b5fc81 100644 --- a/program-tests/compressed-token-test/tests/mint/cmint_resize.rs +++ b/program-tests/compressed-token-test/tests/mint/cmint_resize.rs @@ -15,16 +15,20 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ derive_mint_compressed_address, find_mint_address, }; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_test_utils::{assert_mint_action::assert_mint_action, Rpc}; +use light_test_utils::{ + actions::{ + create_mint, + legacy::instructions::mint_action::{ + DecompressMintParams, MintActionParams, MintActionType, MintToRecipient, + }, + mint_action, mint_action_comprehensive, + }, + assert_mint_action::assert_mint_action, + Rpc, +}; use light_token::instruction::{ derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, }; -use light_token_client::{ - actions::create_mint, - instructions::mint_action::{ - DecompressMintParams, MintActionParams, MintActionType, MintToRecipient, - }, -}; use light_token_interface::{ instructions::extensions::token_metadata::TokenMetadataInstructionData, state::{extensions::AdditionalMetadata, Mint, TokenDataVersion}, @@ -75,7 +79,7 @@ async fn test_cmint_update_metadata_grow() { .unwrap(); // 2. Decompress to CMint (creates on-chain account) - light_token_client::actions::mint_action_comprehensive( + mint_action_comprehensive( &mut rpc, &mint_seed, &authority, @@ -110,7 +114,7 @@ async fn test_cmint_update_metadata_grow() { .to_vec(), }]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -174,7 +178,7 @@ async fn test_cmint_update_metadata_shrink() { .unwrap(); // 2. Decompress to CMint - light_token_client::actions::mint_action_comprehensive( + mint_action_comprehensive( &mut rpc, &mint_seed, &authority, @@ -207,7 +211,7 @@ async fn test_cmint_update_metadata_shrink() { value: "Short".as_bytes().to_vec(), }]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -276,7 +280,7 @@ async fn test_cmint_remove_metadata_key() { .unwrap(); // 2. Decompress to CMint - light_token_client::actions::mint_action_comprehensive( + mint_action_comprehensive( &mut rpc, &mint_seed, &authority, @@ -308,7 +312,7 @@ async fn test_cmint_remove_metadata_key() { idempotent: 0, }]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -377,7 +381,7 @@ async fn test_cmint_multiple_metadata_changes() { .unwrap(); // 2. Decompress to CMint - light_token_client::actions::mint_action_comprehensive( + mint_action_comprehensive( &mut rpc, &mint_seed, &authority, @@ -423,7 +427,7 @@ async fn test_cmint_multiple_metadata_changes() { }, ]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -495,7 +499,7 @@ async fn test_cmint_all_operations() { .unwrap(); // 2. Decompress to CMint - light_token_client::actions::mint_action_comprehensive( + mint_action_comprehensive( &mut rpc, &mint_seed, &authority, @@ -614,7 +618,7 @@ async fn test_cmint_all_operations() { }, ]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -697,7 +701,7 @@ async fn test_decompress_with_mint_to() { }, ]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -778,7 +782,7 @@ async fn test_decompress_with_authority_updates() { }, ]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -862,7 +866,7 @@ async fn test_decompress_with_metadata_update() { }, ]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -968,7 +972,7 @@ async fn test_decompress_with_mint_to_ctoken() { }, ]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, @@ -1144,7 +1148,7 @@ async fn test_decompress_with_all_operations() { }, ]; - light_token_client::actions::mint_action( + mint_action( &mut rpc, MintActionParams { compressed_mint_address, diff --git a/program-tests/compressed-token-test/tests/mint/edge_cases.rs b/program-tests/compressed-token-test/tests/mint/edge_cases.rs index 2196795fc9..0572b51aa1 100644 --- a/program-tests/compressed-token-test/tests/mint/edge_cases.rs +++ b/program-tests/compressed-token-test/tests/mint/edge_cases.rs @@ -5,13 +5,15 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ }; use light_program_test::{LightProgramTest, ProgramTestConfig}; use light_test_utils::{ - assert_mint_action::assert_mint_action, mint_assert::assert_compressed_mint_account, Rpc, + actions::{ + create_mint, + legacy::instructions::mint_action::{MintActionType, MintToRecipient}, + }, + assert_mint_action::assert_mint_action, + mint_assert::assert_compressed_mint_account, + Rpc, }; use light_token::instruction::{CompressibleParams, CreateAssociatedTokenAccount}; -use light_token_client::{ - actions::create_mint, - instructions::mint_action::{MintActionType, MintToRecipient}, -}; use light_token_interface::state::{extensions::AdditionalMetadata, Mint, TokenDataVersion}; use serial_test::serial; use solana_sdk::{signature::Keypair, signer::Signer}; @@ -249,9 +251,9 @@ async fn functional_all_in_one_instruction() { .unwrap(); // Execute all actions in a single instruction - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: authority.pubkey(), diff --git a/program-tests/compressed-token-test/tests/mint/failing.rs b/program-tests/compressed-token-test/tests/mint/failing.rs index 721053e462..48b5246ec8 100644 --- a/program-tests/compressed-token-test/tests/mint/failing.rs +++ b/program-tests/compressed-token-test/tests/mint/failing.rs @@ -7,13 +7,15 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ }; use light_program_test::{utils::assert::assert_rpc_error, LightProgramTest, ProgramTestConfig}; use light_test_utils::{ - assert_mint_action::assert_mint_action, mint_assert::assert_compressed_mint_account, Rpc, + actions::{ + create_mint, + legacy::instructions::mint_action::{MintActionType, MintToRecipient}, + }, + assert_mint_action::assert_mint_action, + mint_assert::assert_compressed_mint_account, + Rpc, }; use light_token::instruction::{CompressibleParams, CreateAssociatedTokenAccount}; -use light_token_client::{ - actions::create_mint, - instructions::mint_action::{MintActionType, MintToRecipient}, -}; use light_token_interface::state::{extensions::AdditionalMetadata, Mint}; use serial_test::serial; use solana_sdk::{ @@ -116,7 +118,7 @@ async fn functional_and_failing_tests() { // 2. FAIL - Create mint with duplicate metadata keys { let duplicate_mint_seed = Keypair::new(); - let result = light_token_client::actions::create_mint( + let result = light_test_utils::actions::create_mint( &mut rpc, &duplicate_mint_seed, // Use new mint seed 8, // decimals @@ -186,7 +188,7 @@ async fn functional_and_failing_tests() { // 3. MintToCompressed with invalid mint authority { - let result = light_token_client::actions::mint_to_compressed( + let result = light_test_utils::actions::mint_to_compressed( &mut rpc, spl_mint_pda, vec![ @@ -224,7 +226,7 @@ async fn functional_and_failing_tests() { .unwrap(); let recipient = Keypair::new().pubkey(); - let result = light_token_client::actions::mint_to_compressed( + let result = light_test_utils::actions::mint_to_compressed( &mut rpc, spl_mint_pda, vec![ @@ -268,7 +270,7 @@ async fn functional_and_failing_tests() { // 5. UpdateMintAuthority with invalid mint authority { - let result = light_token_client::actions::update_mint_authority( + let result = light_test_utils::actions::update_mint_authority( &mut rpc, &invalid_mint_authority, // Invalid authority Some(Keypair::new().pubkey()), @@ -301,7 +303,7 @@ async fn functional_and_failing_tests() { ) .unwrap(); - let result = light_token_client::actions::update_mint_authority( + let result = light_test_utils::actions::update_mint_authority( &mut rpc, &mint_authority, // Valid current authority Some(new_mint_authority.pubkey()), @@ -338,7 +340,7 @@ async fn functional_and_failing_tests() { .value .unwrap(); - let result = light_token_client::actions::update_freeze_authority( + let result = light_test_utils::actions::update_freeze_authority( &mut rpc, &invalid_freeze_authority, // Invalid authority Some(Keypair::new().pubkey()), @@ -372,7 +374,7 @@ async fn functional_and_failing_tests() { ) .unwrap(); - let result = light_token_client::actions::update_freeze_authority( + let result = light_test_utils::actions::update_freeze_authority( &mut rpc, &freeze_authority, // Valid current freeze authority Some(new_freeze_authority.pubkey()), @@ -413,7 +415,7 @@ async fn functional_and_failing_tests() { .unwrap(); // Try to mint with invalid authority - let result = light_token_client::actions::mint_action_comprehensive( + let result = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &invalid_mint_authority, // Invalid authority @@ -471,7 +473,7 @@ async fn functional_and_failing_tests() { light_token::instruction::derive_token_ata(&recipient2.pubkey(), &spl_mint_pda).0; // Try to mint with valid NEW authority (since we updated it) - let result = light_token_client::actions::mint_action_comprehensive( + let result = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &new_mint_authority, // Valid NEW authority after update @@ -508,9 +510,9 @@ async fn functional_and_failing_tests() { // 11. UpdateMetadataField with invalid metadata authority { - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: invalid_metadata_authority.pubkey(), // Invalid authority @@ -558,9 +560,9 @@ async fn functional_and_failing_tests() { value: "Updated Token Name".as_bytes().to_vec(), }]; - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: metadata_authority.pubkey(), // Valid metadata authority @@ -591,9 +593,9 @@ async fn functional_and_failing_tests() { // 13. UpdateMetadataAuthority with invalid metadata authority { - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: invalid_metadata_authority.pubkey(), // Invalid authority @@ -637,9 +639,9 @@ async fn functional_and_failing_tests() { new_authority: new_metadata_authority.pubkey(), }]; - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: metadata_authority.pubkey(), // Valid current metadata authority @@ -670,9 +672,9 @@ async fn functional_and_failing_tests() { // 15. RemoveMetadataKey with invalid metadata authority { - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: invalid_metadata_authority.pubkey(), // Invalid authority @@ -718,9 +720,9 @@ async fn functional_and_failing_tests() { idempotent: 0, // 0 = false }]; - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: new_metadata_authority.pubkey(), // Valid NEW metadata authority after update @@ -771,9 +773,9 @@ async fn functional_and_failing_tests() { idempotent: 1, // 1 = true (won't error if key doesn't exist) }]; - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: new_metadata_authority.pubkey(), // Valid NEW metadata authority @@ -977,7 +979,7 @@ async fn test_create_mint_non_signer_mint_signer() { // Create the instruction using the helper function let mut instruction = - light_token_client::instructions::create_mint::create_compressed_mint_instruction( + light_test_utils::actions::legacy::instructions::create_mint::create_compressed_mint_instruction( &mut rpc, &mint_seed, 8, // decimals @@ -1026,7 +1028,7 @@ async fn test_compress_and_close_mint_must_be_only_action() { use light_compressed_token_sdk::compressed_token::create_compressed_mint::derive_mint_compressed_address; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::program_test::TestRpc; - use light_token_client::instructions::mint_action::DecompressMintParams; + use light_test_utils::actions::legacy::instructions::mint_action::DecompressMintParams; let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(false, None)) .await @@ -1048,7 +1050,7 @@ async fn test_compress_and_close_mint_must_be_only_action() { derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree_pubkey); // 1. Create compressed mint with Mint (decompressed) - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -1059,14 +1061,16 @@ async fn test_compress_and_close_mint_must_be_only_action() { vec![], None, None, - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 9, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 9, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, + }, + ), ) .await .unwrap(); @@ -1076,9 +1080,9 @@ async fn test_compress_and_close_mint_must_be_only_action() { // 2. Try to combine CompressAndCloseMint with UpdateMintAuthority let new_authority = Keypair::new(); - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: mint_authority.pubkey(), diff --git a/program-tests/compressed-token-test/tests/mint/functional.rs b/program-tests/compressed-token-test/tests/mint/functional.rs index 36a15e9c6c..1440b8f2be 100644 --- a/program-tests/compressed-token-test/tests/mint/functional.rs +++ b/program-tests/compressed-token-test/tests/mint/functional.rs @@ -6,6 +6,17 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ use light_compressible::{compression_info::CompressionInfo, rent::SLOTS_PER_EPOCH}; use light_program_test::{program_test::TestRpc, LightProgramTest, ProgramTestConfig}; use light_test_utils::{ + actions::{ + create_mint, + legacy::instructions::{ + mint_action::{DecompressMintParams, MintActionType}, + transfer2::{ + create_decompress_instruction, create_generic_transfer2_instruction, CompressInput, + DecompressInput, Transfer2InstructionType, TransferInput, + }, + }, + mint_to_compressed, transfer, transfer2, + }, assert_ctoken_transfer::assert_ctoken_transfer, assert_mint_action::assert_mint_action, assert_mint_to_compressed::{assert_mint_to_compressed, assert_mint_to_compressed_one}, @@ -19,16 +30,6 @@ use light_test_utils::{ use light_token::instruction::{ derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, }; -use light_token_client::{ - actions::{create_mint, mint_to_compressed, transfer, transfer2}, - instructions::{ - mint_action::{DecompressMintParams, MintActionType}, - transfer2::{ - create_decompress_instruction, create_generic_transfer2_instruction, CompressInput, - DecompressInput, Transfer2InstructionType, TransferInput, - }, - }, -}; use light_token_interface::{ instructions::{ extensions::token_metadata::TokenMetadataInstructionData, mint_action::Recipient, @@ -185,7 +186,7 @@ async fn test_create_compressed_mint() { // Verify the transfer was successful using new transfer wrapper assert_transfer2_transfer( &mut rpc, - light_token_client::instructions::transfer2::TransferInput { + light_test_utils::actions::legacy::instructions::transfer2::TransferInput { compressed_token_account: compressed_token_accounts, to: new_recipient, amount: transfer_amount, @@ -258,7 +259,7 @@ async fn test_create_compressed_mint() { // Use comprehensive decompress assertion assert_transfer2_decompress( &mut rpc, - light_token_client::instructions::transfer2::DecompressInput { + light_test_utils::actions::legacy::instructions::transfer2::DecompressInput { pool_index: None, compressed_token_account: vec![compressed_token_account.clone()], decompress_amount, @@ -317,7 +318,7 @@ async fn test_create_compressed_mint() { // Use comprehensive compress assertion assert_transfer2_compress( &mut rpc, - light_token_client::instructions::transfer2::CompressInput { + light_test_utils::actions::legacy::instructions::transfer2::CompressInput { compressed_token_account: None, solana_token_account: ctoken_ata_pubkey, to: compress_recipient.pubkey(), @@ -567,7 +568,7 @@ async fn test_update_compressed_mint_authority() { .unwrap(); // 2. Update mint authority - let _signature = light_token_client::actions::update_mint_authority( + let _signature = light_test_utils::actions::update_mint_authority( &mut rpc, &initial_mint_authority, Some(new_mint_authority.pubkey()), @@ -594,7 +595,7 @@ async fn test_update_compressed_mint_authority() { new_mint_authority.pubkey() ); // 3. Update freeze authority (need to preserve mint authority) - let _signature = light_token_client::actions::update_freeze_authority( + let _signature = light_test_utils::actions::update_freeze_authority( &mut rpc, &initial_freeze_authority, Some(new_freeze_authority.pubkey()), @@ -639,7 +640,7 @@ async fn test_update_compressed_mint_authority() { .find(|account| account.address == Some(compressed_mint_address)) .expect("Updated compressed mint account not found"); - let _signature = light_token_client::actions::update_mint_authority( + let _signature = light_test_utils::actions::update_mint_authority( &mut rpc, &new_mint_authority, None, // Revoke authority @@ -719,7 +720,7 @@ async fn test_ctoken_transfer() { amount: 100000000u64, }]; - let signature = light_token_client::actions::mint_action_comprehensive( + let signature = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -730,14 +731,16 @@ async fn test_ctoken_transfer() { decompressed_recipients, // mint to decompressed recipients None, // no mint authority update None, // no freeze authority update - Some(light_token_client::instructions::mint_action::NewMint { - decimals, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: Some(freeze_authority.pubkey()), - metadata: None, // No metadata for simplicity - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: Some(freeze_authority.pubkey()), + metadata: None, // No metadata for simplicity + version: 3, + }, + ), ) .await .unwrap(); @@ -929,7 +932,7 @@ async fn test_ctoken_transfer() { // Use comprehensive compress assertion assert_transfer2_compress( &mut rpc, - light_token_client::instructions::transfer2::CompressInput { + light_test_utils::actions::legacy::instructions::transfer2::CompressInput { pool_index: None, compressed_token_account: None, solana_token_account: second_recipient_ata, @@ -1171,7 +1174,7 @@ async fn test_mint_actions() { rpc.context.warp_to_slot(1); // === SINGLE MINT ACTION INSTRUCTION === // Execute ONE instruction with ALL actions - let signature = light_token_client::actions::mint_action_comprehensive( + let signature = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -1182,7 +1185,7 @@ async fn test_mint_actions() { vec![], // mint_to_decompressed_recipients Some(new_mint_authority.pubkey()), // update_mint_authority None, // update_freeze_authority - Some(light_token_client::instructions::mint_action::NewMint { + Some(light_test_utils::actions::legacy::instructions::mint_action::NewMint { decimals, supply: 0, mint_authority: mint_authority.pubkey(), @@ -1326,7 +1329,7 @@ async fn test_mint_actions() { let additional_mint_amount = 7500u64; rpc.context.warp_to_slot(3); // Execute mint_action on existing mint (no creation) - let signature2 = light_token_client::actions::mint_action_comprehensive( + let signature2 = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &new_mint_authority, // Current authority from first test (now the authority for this mint) @@ -1418,7 +1421,7 @@ async fn test_create_compressed_mint_with_mint() { let (mint_pda, _mint_bump) = find_mint_address(&mint_seed.pubkey()); // Create mint + decompress in single instruction - let signature = light_token_client::actions::mint_action_comprehensive( + let signature = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -1429,14 +1432,16 @@ async fn test_create_compressed_mint_with_mint() { vec![], // no decompressed recipients None, // no mint authority update None, // no freeze authority update - Some(light_token_client::instructions::mint_action::NewMint { - decimals, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: Some(freeze_authority.pubkey()), - metadata: None, - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: Some(freeze_authority.pubkey()), + metadata: None, + version: 3, + }, + ), ) .await .unwrap(); @@ -1495,7 +1500,7 @@ async fn test_create_compressed_mint_with_mint() { .expect("Failed to deserialize Mint data"); // Compress and close Mint (permissionless when rent expired) - let close_signature = light_token_client::actions::mint_action_comprehensive( + let close_signature = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -1552,7 +1557,7 @@ async fn test_compress_and_close_mint_idempotent() { let (mint_pda, _) = find_mint_address(&mint_seed.pubkey()); // 1. Create compressed mint WITH Mint (decompress_mint = true) - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -1563,14 +1568,16 @@ async fn test_compress_and_close_mint_idempotent() { vec![], None, None, - Some(light_token_client::instructions::mint_action::NewMint { - decimals, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: Some(freeze_authority.pubkey()), - metadata: None, - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: Some(freeze_authority.pubkey()), + metadata: None, + version: 3, + }, + ), ) .await .unwrap(); @@ -1579,7 +1586,7 @@ async fn test_compress_and_close_mint_idempotent() { rpc.warp_to_slot(SLOTS_PER_EPOCH * 2).unwrap(); // 2. Compress and close Mint (first time - should succeed) - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -1609,9 +1616,9 @@ async fn test_compress_and_close_mint_idempotent() { use solana_sdk::compute_budget::ComputeBudgetInstruction; let mint_action_ix = - light_token_client::instructions::mint_action::create_mint_action_instruction( + light_test_utils::actions::legacy::instructions::mint_action::create_mint_action_instruction( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: mint_authority.pubkey(), @@ -1738,7 +1745,7 @@ async fn test_decompress_existing_mint_to_mint() { ); // === STEP 3: Decompress existing mint to Mint === - let signature = light_token_client::actions::mint_action_comprehensive( + let signature = light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, diff --git a/program-tests/compressed-token-test/tests/mint/mint_to.rs b/program-tests/compressed-token-test/tests/mint/mint_to.rs index cbf6561574..fcc2803d0d 100644 --- a/program-tests/compressed-token-test/tests/mint/mint_to.rs +++ b/program-tests/compressed-token-test/tests/mint/mint_to.rs @@ -1,8 +1,10 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::find_mint_address; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_test_utils::{assert_ctoken_mint_to::assert_ctoken_mint_to, Rpc}; +use light_test_utils::{ + actions::legacy::instructions::mint_action::DecompressMintParams, + assert_ctoken_mint_to::assert_ctoken_mint_to, Rpc, +}; use light_token::instruction::{derive_token_ata, CreateAssociatedTokenAccount, MintTo}; -use light_token_client::instructions::mint_action::DecompressMintParams; use serial_test::serial; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; @@ -48,7 +50,7 @@ async fn setup_mint_to_test() -> MintToTestContext { .unwrap(); // Step 2: Create compressed mint + Mint (no recipients - we'll mint via MintTo) - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -59,14 +61,16 @@ async fn setup_mint_to_test() -> MintToTestContext { vec![], // No ctoken recipients - we'll mint separately None, // No mint authority update None, // No freeze authority update - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 8, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 8, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, + }, + ), ) .await .unwrap(); diff --git a/program-tests/compressed-token-test/tests/mint/random.rs b/program-tests/compressed-token-test/tests/mint/random.rs index 081c607d0f..0b20f280db 100644 --- a/program-tests/compressed-token-test/tests/mint/random.rs +++ b/program-tests/compressed-token-test/tests/mint/random.rs @@ -6,13 +6,15 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ }; use light_program_test::{LightProgramTest, ProgramTestConfig}; use light_test_utils::{ - assert_mint_action::assert_mint_action, mint_assert::assert_compressed_mint_account, Rpc, + actions::{ + create_mint, + legacy::instructions::mint_action::{MintActionType, MintToRecipient}, + }, + assert_mint_action::assert_mint_action, + mint_assert::assert_compressed_mint_account, + Rpc, }; use light_token::instruction::CreateAssociatedTokenAccount; -use light_token_client::{ - actions::create_mint, - instructions::mint_action::{MintActionType, MintToRecipient}, -}; use light_token_interface::state::{extensions::AdditionalMetadata, Mint}; use serial_test::serial; use solana_sdk::{signature::Keypair, signer::Signer}; @@ -346,9 +348,9 @@ async fn test_random_mint_action() { .unwrap(); println!("actions {:?}", actions); // Execute all actions in a single instruction - let result = light_token_client::actions::mint_action( + let result = light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: authority.pubkey(), diff --git a/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs b/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs index 9e2d63fef5..e6229ffb35 100644 --- a/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs @@ -117,7 +117,7 @@ async fn setup_compression_test(token_amount: u64) -> Result Result Result<(), RpcError> { let token_amount = 1000u64; let decompressed_recipients = vec![Recipient::new(owner.pubkey(), token_amount)]; - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -639,14 +641,16 @@ async fn test_compression_max_top_up_exceeded() -> Result<(), RpcError> { decompressed_recipients, // mint to decompressed Light Token ATA None, None, - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 6, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, // ShaFlat for compressible accounts - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 6, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, // ShaFlat for compressible accounts + }, + ), ) .await?; diff --git a/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs b/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs index d8d7ca4806..3000b84066 100644 --- a/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs @@ -118,7 +118,7 @@ async fn setup_decompression_test( let compressed_recipients = vec![Recipient::new(owner.pubkey(), compressed_amount)]; let decompressed_recipients = vec![Recipient::new(owner.pubkey(), 0)]; - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -129,14 +129,16 @@ async fn setup_decompression_test( decompressed_recipients, // mint 1 token to decompressed Light Token ATA None, // no mint authority update None, // no freeze authority update - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 6, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, // ShaFlat for mint hashing - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 6, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, // ShaFlat for mint hashing + }, + ), ) .await?; diff --git a/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs b/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs index 2cd32161b9..b5f940f58d 100644 --- a/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs @@ -130,7 +130,7 @@ async fn setup_no_system_program_cpi_test( vec![] }; - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut rpc, &mint_seed, &mint_authority, @@ -141,14 +141,16 @@ async fn setup_no_system_program_cpi_test( decompressed_recipients, // mint to source Light Token ATA (empty if token_amount is 0) None, None, - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 6, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, // ShaFlat - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 6, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, // ShaFlat + }, + ), ) .await .unwrap(); @@ -738,7 +740,7 @@ async fn test_too_many_mints() { // Create mint and mint tokens to source Light Token ATA let decompressed_recipients = vec![Recipient::new(context.owner.pubkey(), 1000)]; - light_token_client::actions::mint_action_comprehensive( + light_test_utils::actions::mint_action_comprehensive( &mut context.rpc, &mint_seed, &mint_authority, @@ -749,14 +751,16 @@ async fn test_too_many_mints() { decompressed_recipients, // mint to source Light Token ATA None, None, - Some(light_token_client::instructions::mint_action::NewMint { - decimals: 6, - supply: 0, - mint_authority: mint_authority.pubkey(), - freeze_authority: None, - metadata: None, - version: 3, // ShaFlat - }), + Some( + light_test_utils::actions::legacy::instructions::mint_action::NewMint { + decimals: 6, + supply: 0, + mint_authority: mint_authority.pubkey(), + freeze_authority: None, + metadata: None, + version: 3, // ShaFlat + }, + ), ) .await .unwrap(); diff --git a/program-tests/compressed-token-test/tests/transfer2/shared.rs b/program-tests/compressed-token-test/tests/transfer2/shared.rs index ced4e55898..69237ef81d 100644 --- a/program-tests/compressed-token-test/tests/transfer2/shared.rs +++ b/program-tests/compressed-token-test/tests/transfer2/shared.rs @@ -7,6 +7,17 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ }; use light_program_test::{indexer::TestIndexerExtensions, LightProgramTest, ProgramTestConfig}; use light_test_utils::{ + actions::{ + create_mint, + legacy::instructions::{ + mint_action::MintActionType, + transfer2::{ + create_generic_transfer2_instruction, ApproveInput, CompressAndCloseInput, + CompressInput, DecompressInput, Transfer2InstructionType, TransferInput, + }, + }, + mint_to_compressed, + }, airdrop_lamports, assert_transfer2::assert_transfer2, spl::{ @@ -15,16 +26,6 @@ use light_test_utils::{ }, }; use light_token::instruction::{CompressibleParams, CreateAssociatedTokenAccount}; -use light_token_client::{ - actions::{create_mint, mint_to_compressed}, - instructions::{ - mint_action::MintActionType, - transfer2::{ - create_generic_transfer2_instruction, ApproveInput, CompressAndCloseInput, - CompressInput, DecompressInput, Transfer2InstructionType, TransferInput, - }, - }, -}; use light_token_interface::{ instructions::{mint_action::Recipient, transfer2::CompressedTokenInstructionDataTransfer2}, state::TokenDataVersion, @@ -496,9 +497,9 @@ impl TestContext { let compressed_mint_address = derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree_pubkey); - light_token_client::actions::mint_action( + light_test_utils::actions::mint_action( &mut rpc, - light_token_client::instructions::mint_action::MintActionParams { + light_test_utils::actions::legacy::instructions::mint_action::MintActionParams { compressed_mint_address, mint_seed: mint_seed.pubkey(), authority: mint_authority.pubkey(), diff --git a/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs b/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs index 3cb1cf1e03..d55087e5e8 100644 --- a/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs +++ b/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs @@ -14,6 +14,7 @@ use light_compressed_token_sdk::{ use light_program_test::utils::assert::assert_rpc_error; pub use light_program_test::{LightProgramTest, ProgramTestConfig}; pub use light_test_utils::{ + actions::transfer2::{self}, airdrop_lamports, spl::{ create_mint_helper, create_token_2022_account, mint_spl_tokens, CREATE_MINT_HELPER_DECIMALS, @@ -24,7 +25,6 @@ pub use light_token::instruction::{ derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, }; use light_token::ValidityProof; -pub use light_token_client::actions::transfer2::{self}; use light_token_interface::instructions::transfer2::{Compression, MultiTokenTransferOutputData}; use solana_sdk::pubkey::Pubkey; pub use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer}; diff --git a/program-tests/compressed-token-test/tests/transfer2/transfer_failing.rs b/program-tests/compressed-token-test/tests/transfer2/transfer_failing.rs index 7abbd48302..a503a6c0f0 100644 --- a/program-tests/compressed-token-test/tests/transfer2/transfer_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/transfer_failing.rs @@ -16,9 +16,11 @@ use light_program_test::{ utils::assert::assert_rpc_error, LightProgramTest, ProgramTestConfig, Rpc, }; use light_sdk::instruction::PackedAccounts; -use light_test_utils::{airdrop_lamports, RpcError}; +use light_test_utils::{ + actions::{create_mint, mint_to_compressed, transfer2::approve}, + airdrop_lamports, RpcError, +}; use light_token::ValidityProof; -use light_token_client::actions::{create_mint, mint_to_compressed, transfer2::approve}; use light_token_interface::{ instructions::{mint_action::Recipient, transfer2::MultiInputTokenDataWithContext}, state::TokenDataVersion, diff --git a/program-tests/registry-test/tests/compressible.rs b/program-tests/registry-test/tests/compressible.rs index 6326b4f084..633dc1831e 100644 --- a/program-tests/registry-test/tests/compressible.rs +++ b/program-tests/registry-test/tests/compressible.rs @@ -26,18 +26,19 @@ use light_registry::accounts::{ WithdrawFundingPool as WithdrawFundingPoolAccounts, }; use light_test_utils::{ - airdrop_lamports, assert_claim::assert_claim, spl::create_mint_helper, Rpc, RpcError, + actions::{ + create_compressible_token_account, + legacy::instructions::mint_action::{DecompressMintParams, NewMint}, + mint_action_comprehensive, transfer, CreateCompressibleTokenAccountInputs, + }, + airdrop_lamports, + assert_claim::assert_claim, + spl::create_mint_helper, + Rpc, RpcError, }; use light_token::instruction::{ derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, MintTo, }; -use light_token_client::{ - actions::{ - create_compressible_token_account, mint_action_comprehensive, transfer, - CreateCompressibleTokenAccountInputs, - }, - instructions::mint_action::{DecompressMintParams, NewMint}, -}; use solana_sdk::{ instruction::Instruction, pubkey::Pubkey, diff --git a/program-tests/utils/Cargo.toml b/program-tests/utils/Cargo.toml index 21b57c5f15..7b32fa5a0d 100644 --- a/program-tests/utils/Cargo.toml +++ b/program-tests/utils/Cargo.toml @@ -55,3 +55,11 @@ light-token = { workspace = true } light-compressed-token-sdk = { workspace = true } light-token-client = { workspace = true } light-compressible = { workspace = true } +light-token-types = { workspace = true } +solana-pubkey = { workspace = true } +solana-keypair = { workspace = true } +solana-signer = { workspace = true } +solana-signature = { workspace = true } +solana-instruction = { workspace = true } +solana-msg = { workspace = true } +borsh = { workspace = true } diff --git a/sdk-libs/token-client/src/actions/create_compressible_token_account.rs b/program-tests/utils/src/actions/legacy/create_compressible_token_account.rs similarity index 100% rename from sdk-libs/token-client/src/actions/create_compressible_token_account.rs rename to program-tests/utils/src/actions/legacy/create_compressible_token_account.rs diff --git a/program-tests/utils/src/actions/legacy/create_mint.rs b/program-tests/utils/src/actions/legacy/create_mint.rs new file mode 100644 index 0000000000..88e493755f --- /dev/null +++ b/program-tests/utils/src/actions/legacy/create_mint.rs @@ -0,0 +1,61 @@ +use light_client::{ + indexer::Indexer, + rpc::{Rpc, RpcError}, +}; +use light_token_interface::instructions::extensions::TokenMetadataInstructionData; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +use super::instructions::create_mint::create_compressed_mint_instruction; + +/// Create a compressed mint and send the transaction. +/// +/// # Arguments +/// * `rpc` - RPC client with indexer capabilities +/// * `mint_seed` - Keypair used to derive the mint PDA (must sign the transaction) +/// * `decimals` - Number of decimal places for the token +/// * `mint_authority_keypair` - Authority keypair that can mint tokens (must sign the transaction) +/// * `freeze_authority` - Optional authority that can freeze tokens +/// * `payer` - Transaction fee payer keypair +/// * `metadata` - Optional metadata for the token +/// +/// # Returns +/// `Result` - The transaction signature +pub async fn create_mint( + rpc: &mut R, + mint_seed: &Keypair, + decimals: u8, + mint_authority_keypair: &Keypair, + freeze_authority: Option, + metadata: Option, + payer: &Keypair, +) -> Result { + // Create the instruction + let ix = create_compressed_mint_instruction( + rpc, + mint_seed, + decimals, + mint_authority_keypair.pubkey(), + freeze_authority, + payer.pubkey(), + metadata, + ) + .await?; + + // Determine signers (deduplicate if any keypairs are the same) + let mut signers = vec![payer]; + if mint_seed.pubkey() != payer.pubkey() { + signers.push(mint_seed); + } + if mint_authority_keypair.pubkey() != payer.pubkey() + && mint_authority_keypair.pubkey() != mint_seed.pubkey() + { + signers.push(mint_authority_keypair); + } + + // Send the transaction + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await +} diff --git a/sdk-libs/token-client/src/instructions/create_mint.rs b/program-tests/utils/src/actions/legacy/instructions/create_mint.rs similarity index 95% rename from sdk-libs/token-client/src/instructions/create_mint.rs rename to program-tests/utils/src/actions/legacy/instructions/create_mint.rs index ebdc8e0bfc..c166c4aa2d 100644 --- a/sdk-libs/token-client/src/instructions/create_mint.rs +++ b/program-tests/utils/src/actions/legacy/instructions/create_mint.rs @@ -8,7 +8,7 @@ use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signer::Signer; -use crate::instructions::mint_action::{create_mint_action_instruction, MintActionParams, NewMint}; +use super::mint_action::{create_mint_action_instruction, MintActionParams, NewMint}; /// Create a compressed-only mint instruction (no decompression). /// diff --git a/sdk-libs/token-client/src/instructions/mint_action.rs b/program-tests/utils/src/actions/legacy/instructions/mint_action.rs similarity index 100% rename from sdk-libs/token-client/src/instructions/mint_action.rs rename to program-tests/utils/src/actions/legacy/instructions/mint_action.rs diff --git a/sdk-libs/token-client/src/instructions/mint_to_compressed.rs b/program-tests/utils/src/actions/legacy/instructions/mint_to_compressed.rs similarity index 100% rename from sdk-libs/token-client/src/instructions/mint_to_compressed.rs rename to program-tests/utils/src/actions/legacy/instructions/mint_to_compressed.rs diff --git a/sdk-libs/token-client/src/instructions/mod.rs b/program-tests/utils/src/actions/legacy/instructions/mod.rs similarity index 100% rename from sdk-libs/token-client/src/instructions/mod.rs rename to program-tests/utils/src/actions/legacy/instructions/mod.rs diff --git a/sdk-libs/token-client/src/instructions/transfer2.rs b/program-tests/utils/src/actions/legacy/instructions/transfer2.rs similarity index 100% rename from sdk-libs/token-client/src/instructions/transfer2.rs rename to program-tests/utils/src/actions/legacy/instructions/transfer2.rs diff --git a/sdk-libs/token-client/src/instructions/update_compressed_mint.rs b/program-tests/utils/src/actions/legacy/instructions/update_compressed_mint.rs similarity index 100% rename from sdk-libs/token-client/src/instructions/update_compressed_mint.rs rename to program-tests/utils/src/actions/legacy/instructions/update_compressed_mint.rs diff --git a/sdk-libs/token-client/src/actions/mint_action.rs b/program-tests/utils/src/actions/legacy/mint_action.rs similarity index 98% rename from sdk-libs/token-client/src/actions/mint_action.rs rename to program-tests/utils/src/actions/legacy/mint_action.rs index aa1420e58f..d16ba10a3b 100644 --- a/sdk-libs/token-client/src/actions/mint_action.rs +++ b/program-tests/utils/src/actions/legacy/mint_action.rs @@ -11,7 +11,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::mint_action::{ +use super::instructions::mint_action::{ create_mint_action_instruction, DecompressMintParams, MintActionParams, MintActionType, MintToRecipient, }; @@ -79,7 +79,7 @@ pub async fn mint_action_comprehensive( update_mint_authority: Option, update_freeze_authority: Option, // Parameters for mint creation (required when creating a new mint) - new_mint: Option, + new_mint: Option, ) -> Result { // Derive addresses let address_tree_pubkey = rpc.get_address_tree_v2().tree; diff --git a/sdk-libs/token-client/src/actions/mint_to_compressed.rs b/program-tests/utils/src/actions/legacy/mint_to_compressed.rs similarity index 96% rename from sdk-libs/token-client/src/actions/mint_to_compressed.rs rename to program-tests/utils/src/actions/legacy/mint_to_compressed.rs index b4533ae4bf..0a6d7f030b 100644 --- a/sdk-libs/token-client/src/actions/mint_to_compressed.rs +++ b/program-tests/utils/src/actions/legacy/mint_to_compressed.rs @@ -8,7 +8,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::mint_to_compressed::mint_to_compressed_instruction; +use super::instructions::mint_to_compressed::mint_to_compressed_instruction; /// Mints compressed tokens to recipients using a higher-level action /// diff --git a/program-tests/utils/src/actions/legacy/mod.rs b/program-tests/utils/src/actions/legacy/mod.rs new file mode 100644 index 0000000000..5396521fdf --- /dev/null +++ b/program-tests/utils/src/actions/legacy/mod.rs @@ -0,0 +1,18 @@ +pub mod instructions; + +mod create_compressible_token_account; +mod create_mint; +mod mint_action; +mod mint_to_compressed; +mod spl_interface; +mod transfer; +pub mod transfer2; +mod update_compressed_mint; + +pub use create_compressible_token_account::*; +pub use create_mint::*; +pub use mint_action::*; +pub use mint_to_compressed::*; +pub use spl_interface::*; +pub use transfer::*; +pub use update_compressed_mint::*; diff --git a/sdk-libs/token-client/src/actions/spl_interface.rs b/program-tests/utils/src/actions/legacy/spl_interface.rs similarity index 100% rename from sdk-libs/token-client/src/actions/spl_interface.rs rename to program-tests/utils/src/actions/legacy/spl_interface.rs diff --git a/program-tests/utils/src/actions/legacy/transfer.rs b/program-tests/utils/src/actions/legacy/transfer.rs new file mode 100644 index 0000000000..2d32024fb0 --- /dev/null +++ b/program-tests/utils/src/actions/legacy/transfer.rs @@ -0,0 +1,78 @@ +use light_client::rpc::{Rpc, RpcError}; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32]; + +/// Transfer from one token account to another. +/// +/// # Arguments +/// * `rpc` - RPC client +/// * `source` - Source token account (decompressed compressed token account) +/// * `destination` - Destination token account +/// * `amount` - Amount of tokens to transfer +/// * `authority` - Authority that can spend from the source token account +/// * `payer` - Transaction fee payer keypair +/// +/// # Returns +/// `Result` - The transaction signature +pub async fn transfer( + rpc: &mut R, + source: Pubkey, + destination: Pubkey, + amount: u64, + authority: &Keypair, + payer: &Keypair, +) -> Result { + let transfer_instruction = + create_transfer_token_instruction(source, destination, amount, authority.pubkey())?; + + let mut signers = vec![payer]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } + + rpc.create_and_send_transaction(&[transfer_instruction], &payer.pubkey(), &signers) + .await +} + +// TODO: consume the variant from compressed-token-sdk instead +/// Create a token transfer instruction. +/// +/// # Arguments +/// * `source` - Source token account +/// * `destination` - Destination token account +/// * `amount` - Amount to transfer +/// * `authority` - Authority pubkey +/// +/// # Returns +/// `Result` +#[allow(clippy::result_large_err)] +pub fn create_transfer_token_instruction( + source: Pubkey, + destination: Pubkey, + amount: u64, + authority: Pubkey, +) -> Result { + let transfer_instruction = Instruction { + program_id: Pubkey::from(light_token_interface::LIGHT_TOKEN_PROGRAM_ID), + accounts: vec![ + AccountMeta::new(source, false), // Source token account + AccountMeta::new(destination, false), // Destination token account + AccountMeta::new(authority, true), // Authority must be writable for potential top-ups + AccountMeta::new_readonly(Pubkey::from(SYSTEM_PROGRAM_ID), false), // System program for rent top-ups + ], + data: { + // CTokenTransfer discriminator + let mut data = vec![3u8]; + // Add SPL Token Transfer instruction data exactly like SPL does + data.extend_from_slice(&amount.to_le_bytes()); // Amount as u64 little-endian + data + }, + }; + + Ok(transfer_instruction) +} diff --git a/sdk-libs/token-client/src/actions/transfer2/approve.rs b/program-tests/utils/src/actions/legacy/transfer2/approve.rs similarity index 97% rename from sdk-libs/token-client/src/actions/transfer2/approve.rs rename to program-tests/utils/src/actions/legacy/transfer2/approve.rs index 32d231df55..f4de5e3645 100644 --- a/sdk-libs/token-client/src/actions/transfer2/approve.rs +++ b/program-tests/utils/src/actions/legacy/transfer2/approve.rs @@ -7,7 +7,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::transfer2::{ +use super::super::instructions::transfer2::{ create_generic_transfer2_instruction, ApproveInput, Transfer2InstructionType, }; diff --git a/sdk-libs/token-client/src/actions/transfer2/compress.rs b/program-tests/utils/src/actions/legacy/transfer2/compress.rs similarity index 99% rename from sdk-libs/token-client/src/actions/transfer2/compress.rs rename to program-tests/utils/src/actions/legacy/transfer2/compress.rs index 745a9873c0..aed0d7de51 100644 --- a/sdk-libs/token-client/src/actions/transfer2/compress.rs +++ b/program-tests/utils/src/actions/legacy/transfer2/compress.rs @@ -10,7 +10,7 @@ use solana_signer::Signer; use spl_pod::bytemuck::pod_from_bytes; use spl_token_2022::pod::PodAccount; -use crate::instructions::transfer2::{ +use super::super::instructions::transfer2::{ create_generic_transfer2_instruction, CompressInput, Transfer2InstructionType, }; diff --git a/sdk-libs/token-client/src/actions/transfer2/compress_and_close.rs b/program-tests/utils/src/actions/legacy/transfer2/compress_and_close.rs similarity index 97% rename from sdk-libs/token-client/src/actions/transfer2/compress_and_close.rs rename to program-tests/utils/src/actions/legacy/transfer2/compress_and_close.rs index 096919411b..6dfc016485 100644 --- a/sdk-libs/token-client/src/actions/transfer2/compress_and_close.rs +++ b/program-tests/utils/src/actions/legacy/transfer2/compress_and_close.rs @@ -7,7 +7,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::transfer2::{ +use super::super::instructions::transfer2::{ create_generic_transfer2_instruction, CompressAndCloseInput, Transfer2InstructionType, }; diff --git a/sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs b/program-tests/utils/src/actions/legacy/transfer2/ctoken_to_spl.rs similarity index 100% rename from sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs rename to program-tests/utils/src/actions/legacy/transfer2/ctoken_to_spl.rs diff --git a/sdk-libs/token-client/src/actions/transfer2/decompress.rs b/program-tests/utils/src/actions/legacy/transfer2/decompress.rs similarity index 97% rename from sdk-libs/token-client/src/actions/transfer2/decompress.rs rename to program-tests/utils/src/actions/legacy/transfer2/decompress.rs index daf2503f8a..55bba47b5e 100644 --- a/sdk-libs/token-client/src/actions/transfer2/decompress.rs +++ b/program-tests/utils/src/actions/legacy/transfer2/decompress.rs @@ -7,7 +7,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::transfer2::{ +use super::super::instructions::transfer2::{ create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType, }; diff --git a/sdk-libs/token-client/src/actions/transfer2/mod.rs b/program-tests/utils/src/actions/legacy/transfer2/mod.rs similarity index 100% rename from sdk-libs/token-client/src/actions/transfer2/mod.rs rename to program-tests/utils/src/actions/legacy/transfer2/mod.rs diff --git a/sdk-libs/token-client/src/actions/transfer2/spl_to_ctoken.rs b/program-tests/utils/src/actions/legacy/transfer2/spl_to_ctoken.rs similarity index 100% rename from sdk-libs/token-client/src/actions/transfer2/spl_to_ctoken.rs rename to program-tests/utils/src/actions/legacy/transfer2/spl_to_ctoken.rs diff --git a/sdk-libs/token-client/src/actions/transfer2/transfer.rs b/program-tests/utils/src/actions/legacy/transfer2/transfer.rs similarity index 97% rename from sdk-libs/token-client/src/actions/transfer2/transfer.rs rename to program-tests/utils/src/actions/legacy/transfer2/transfer.rs index a39ab8dd2a..03d73d4144 100644 --- a/sdk-libs/token-client/src/actions/transfer2/transfer.rs +++ b/program-tests/utils/src/actions/legacy/transfer2/transfer.rs @@ -7,7 +7,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::transfer2::{ +use super::super::instructions::transfer2::{ create_generic_transfer2_instruction, Transfer2InstructionType, TransferInput, }; diff --git a/sdk-libs/token-client/src/actions/transfer2/transfer_delegated.rs b/program-tests/utils/src/actions/legacy/transfer2/transfer_delegated.rs similarity index 98% rename from sdk-libs/token-client/src/actions/transfer2/transfer_delegated.rs rename to program-tests/utils/src/actions/legacy/transfer2/transfer_delegated.rs index 5d3e264606..eb4a639f8a 100644 --- a/sdk-libs/token-client/src/actions/transfer2/transfer_delegated.rs +++ b/program-tests/utils/src/actions/legacy/transfer2/transfer_delegated.rs @@ -7,7 +7,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::transfer2::{ +use super::super::instructions::transfer2::{ create_generic_transfer2_instruction, Transfer2InstructionType, TransferInput, }; diff --git a/sdk-libs/token-client/src/actions/update_compressed_mint.rs b/program-tests/utils/src/actions/legacy/update_compressed_mint.rs similarity index 98% rename from sdk-libs/token-client/src/actions/update_compressed_mint.rs rename to program-tests/utils/src/actions/legacy/update_compressed_mint.rs index 48afb42748..2e394653cc 100644 --- a/sdk-libs/token-client/src/actions/update_compressed_mint.rs +++ b/program-tests/utils/src/actions/legacy/update_compressed_mint.rs @@ -8,7 +8,7 @@ use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::update_compressed_mint::update_compressed_mint_instruction; +use super::instructions::update_compressed_mint::update_compressed_mint_instruction; /// Update compressed mint authority action /// diff --git a/program-tests/utils/src/actions/mod.rs b/program-tests/utils/src/actions/mod.rs new file mode 100644 index 0000000000..579abafadd --- /dev/null +++ b/program-tests/utils/src/actions/mod.rs @@ -0,0 +1,6 @@ +/// Legacy actions module - contains actions moved from light-token-client +/// for backwards compatibility during the refactoring process. +pub mod legacy; + +// Re-export legacy module contents for backward compatibility +pub use legacy::*; diff --git a/program-tests/utils/src/assert_mint_action.rs b/program-tests/utils/src/assert_mint_action.rs index 680063e28e..bc1b66b715 100644 --- a/program-tests/utils/src/assert_mint_action.rs +++ b/program-tests/utils/src/assert_mint_action.rs @@ -5,10 +5,11 @@ use light_client::indexer::Indexer; use light_compressed_account::compressed_account::CompressedAccountData; use light_compressible::compression_info::CompressionInfo; use light_program_test::{LightProgramTest, Rpc}; -use light_token_client::instructions::mint_action::MintActionType; use light_token_interface::state::{extensions::AdditionalMetadata, ExtensionStruct, Mint, Token}; use solana_sdk::pubkey::Pubkey; +use crate::actions::legacy::instructions::mint_action::MintActionType; + /// Extract CompressionInfo from Light Token's Compressible extension fn get_ctoken_compression_info(ctoken: &Token) -> Option { ctoken diff --git a/program-tests/utils/src/assert_transfer2.rs b/program-tests/utils/src/assert_transfer2.rs index 907e35673f..76c8a8073a 100644 --- a/program-tests/utils/src/assert_transfer2.rs +++ b/program-tests/utils/src/assert_transfer2.rs @@ -3,13 +3,13 @@ use std::collections::HashMap; use anchor_spl::token_2022::spl_token_2022; use light_client::{indexer::Indexer, rpc::Rpc}; use light_program_test::LightProgramTest; -use light_token_client::instructions::transfer2::{ - CompressInput, DecompressInput, Transfer2InstructionType, TransferInput, -}; use light_token_interface::LIGHT_TOKEN_PROGRAM_ID; use solana_sdk::{program_pack::Pack, pubkey::Pubkey}; use crate::{ + actions::legacy::instructions::transfer2::{ + CompressInput, DecompressInput, Transfer2InstructionType, TransferInput, + }, assert_close_token_account::assert_close_token_account, assert_ctoken_transfer::assert_compressible_for_account, }; @@ -606,7 +606,7 @@ pub async fn assert_transfer2_compress(rpc: &mut LightProgramTest, compress_inpu /// Automatically retrieves pre-state from the cached context pub async fn assert_transfer2_compress_and_close( rpc: &mut LightProgramTest, - compress_and_close_input: light_token_client::instructions::transfer2::CompressAndCloseInput, + compress_and_close_input: crate::actions::legacy::instructions::transfer2::CompressAndCloseInput, ) { // Get the destination account let destination_pubkey = compress_and_close_input diff --git a/program-tests/utils/src/lib.rs b/program-tests/utils/src/lib.rs index 1eeaf67a37..9af83e2d03 100644 --- a/program-tests/utils/src/lib.rs +++ b/program-tests/utils/src/lib.rs @@ -16,6 +16,7 @@ use solana_sdk::{ signature::{Keypair, Signature, Signer}, transaction, }; +pub mod actions; pub mod address; pub mod address_tree_rollover; pub mod assert_claim; diff --git a/sdk-libs/token-client/src/actions/approve.rs b/sdk-libs/token-client/src/actions/approve.rs new file mode 100644 index 0000000000..875b19632d --- /dev/null +++ b/sdk-libs/token-client/src/actions/approve.rs @@ -0,0 +1,122 @@ +//! Approve delegation action for Light Token. +//! +//! Simple interface for approving a delegate on a Light Token account. + +use light_client::rpc::{Rpc, RpcError}; +use light_token::instruction::Approve as ApproveInstruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for approving a delegate for a Light Token account. +/// +/// If `owner` is `Some`, the owner keypair will be used as the signer. +/// If `owner` is `None`, the payer will be used as the owner. +/// +/// # Example +/// ```ignore +/// // Payer is the owner +/// Approve { +/// token_account, +/// delegate, +/// amount: 1000, +/// owner: None, +/// }.execute(&mut rpc, &payer).await?; +/// +/// // Separate owner +/// Approve { +/// token_account, +/// delegate, +/// amount: 1000, +/// owner: Some(owner_pubkey), +/// }.execute_with_owner(&mut rpc, &payer, &owner_keypair).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct Approve { + /// The token account to approve delegation for. + pub token_account: Pubkey, + /// The delegate public key. + pub delegate: Pubkey, + /// Amount of tokens to delegate. + pub amount: u64, + /// Optional owner public key (for separate owner scenario). + /// If None, the payer is used as the owner. + pub owner: Option, +} + +impl Approve { + /// Execute the approve action via RPC where payer is the owner. + /// + /// This method only supports cases where `owner == payer`. If you need a + /// separate owner and payer, use [`execute_with_owner`](Self::execute_with_owner). + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair (must also be the owner) + /// + /// # Returns + /// `Result` - The transaction signature + /// + /// # Errors + /// Returns an error if `self.owner` is `Some` and does not equal `payer.pubkey()`. + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + ) -> Result { + let owner_pubkey = self.owner.unwrap_or_else(|| payer.pubkey()); + + if owner_pubkey != payer.pubkey() { + return Err(RpcError::CustomError( + "owner does not match payer; use execute_with_owner for separate owner/payer" + .to_string(), + )); + } + + let ix = ApproveInstruction { + token_account: self.token_account, + delegate: self.delegate, + owner: owner_pubkey, + amount: self.amount, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer]) + .await + } + + /// Execute the approve action via RPC with a separate owner. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair + /// * `owner` - The owner of the token account (signer) + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute_with_owner( + self, + rpc: &mut R, + payer: &Keypair, + owner: &Keypair, + ) -> Result { + let ix = ApproveInstruction { + token_account: self.token_account, + delegate: self.delegate, + owner: owner.pubkey(), + amount: self.amount, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let mut signers: Vec<&Keypair> = vec![payer]; + if owner.pubkey() != payer.pubkey() { + signers.push(owner); + } + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } +} diff --git a/sdk-libs/token-client/src/actions/create_ata.rs b/sdk-libs/token-client/src/actions/create_ata.rs new file mode 100644 index 0000000000..8d5838cf10 --- /dev/null +++ b/sdk-libs/token-client/src/actions/create_ata.rs @@ -0,0 +1,99 @@ +//! Create Associated Token Account actions for Light Token. +//! +//! These actions provide clean interfaces for creating Light Token ATAs. + +use light_client::rpc::{Rpc, RpcError}; +use light_token::instruction::{ + derive_associated_token_account, get_associated_token_address, CreateAssociatedTokenAccount, +}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for creating an associated token account for a Light Token mint. +/// +/// # Example +/// ```ignore +/// // Non-idempotent (fails if ATA exists) +/// CreateAta { +/// mint, +/// owner, +/// idempotent: false, +/// }.execute(&mut rpc, &payer).await?; +/// +/// // Idempotent (no-op if ATA exists) +/// CreateAta { +/// mint, +/// owner, +/// idempotent: true, +/// }.execute(&mut rpc, &payer).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct CreateAta { + /// The mint public key. + pub mint: Pubkey, + /// The owner of the ATA. + pub owner: Pubkey, + /// Whether to use idempotent mode (no-op if ATA exists). + pub idempotent: bool, +} + +impl CreateAta { + /// Execute the create_ata action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair + /// + /// # Returns + /// `Result<(Signature, Pubkey), RpcError>` - The transaction signature and ATA public key + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + ) -> Result<(Signature, Pubkey), RpcError> { + let mut instruction_builder = + CreateAssociatedTokenAccount::new(payer.pubkey(), self.owner, self.mint); + + if self.idempotent { + instruction_builder = instruction_builder.idempotent(); + } + + let ix = instruction_builder + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let signature = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[payer]) + .await?; + + Ok((signature, get_ata_address(&self.mint, &self.owner))) + } +} + +/// Get the associated token address for a given owner and mint. +/// +/// This is a pure function that computes the ATA address without any RPC calls. +/// +/// # Arguments +/// * `mint` - The mint public key +/// * `owner` - The owner public key +/// +/// # Returns +/// `Pubkey` - The ATA address +pub fn get_ata_address(mint: &Pubkey, owner: &Pubkey) -> Pubkey { + get_associated_token_address(owner, mint) +} + +/// Derive the associated token address with bump seed. +/// +/// # Arguments +/// * `mint` - The mint public key +/// * `owner` - The owner public key +/// +/// # Returns +/// `(Pubkey, u8)` - The ATA address and bump seed +pub fn derive_ata_address(mint: &Pubkey, owner: &Pubkey) -> (Pubkey, u8) { + derive_associated_token_account(owner, mint) +} diff --git a/sdk-libs/token-client/src/actions/create_mint.rs b/sdk-libs/token-client/src/actions/create_mint.rs index 25940c6013..e71a85d1a4 100644 --- a/sdk-libs/token-client/src/actions/create_mint.rs +++ b/sdk-libs/token-client/src/actions/create_mint.rs @@ -1,61 +1,180 @@ +//! Create mint action for Light Token. +//! +//! This action provides a clean interface for creating a new Light Token mint. + use light_client::{ - indexer::Indexer, + indexer::{AddressWithTree, Indexer}, rpc::{Rpc, RpcError}, }; -use light_token_interface::instructions::extensions::TokenMetadataInstructionData; +use light_token::instruction::{ + derive_mint_compressed_address, find_mint_address, CreateMint as CreateMintInstruction, + CreateMintParams as CreateMintInstructionParams, +}; +use light_token_interface::{ + instructions::extensions::{ExtensionInstructionData, TokenMetadataInstructionData}, + state::AdditionalMetadata, +}; use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -use crate::instructions::create_mint::create_compressed_mint_instruction; +/// Token metadata for the mint. +#[derive(Clone, Debug, Default)] +pub struct TokenMetadata { + /// The longer name of the token. + pub name: String, + /// The shortened symbol for the token. + pub symbol: String, + /// The URI pointing to richer metadata. + pub uri: String, + /// Authority that can update the metadata. + pub update_authority: Option, + /// Additional metadata as key-value pairs. + pub additional_metadata: Option>, +} -/// Create a compressed mint and send the transaction. +/// Parameters for creating a new Light Token mint. /// -/// # Arguments -/// * `rpc` - RPC client with indexer capabilities -/// * `mint_seed` - Keypair used to derive the mint PDA (must sign the transaction) -/// * `decimals` - Number of decimal places for the token -/// * `mint_authority_keypair` - Authority keypair that can mint tokens (must sign the transaction) -/// * `freeze_authority` - Optional authority that can freeze tokens -/// * `payer` - Transaction fee payer keypair -/// * `metadata` - Optional metadata for the token +/// This creates both a compressed mint AND a decompressed Mint Solana account +/// in a single instruction. /// -/// # Returns -/// `Result` - The transaction signature -pub async fn create_mint( - rpc: &mut R, - mint_seed: &Keypair, - decimals: u8, - mint_authority_keypair: &Keypair, - freeze_authority: Option, - metadata: Option, - payer: &Keypair, -) -> Result { - // Create the instruction - let ix = create_compressed_mint_instruction( - rpc, - mint_seed, - decimals, - mint_authority_keypair.pubkey(), - freeze_authority, - payer.pubkey(), - metadata, - ) - .await?; - - // Determine signers (deduplicate if any keypairs are the same) - let mut signers = vec![payer]; - if mint_seed.pubkey() != payer.pubkey() { - signers.push(mint_seed); - } - if mint_authority_keypair.pubkey() != payer.pubkey() - && mint_authority_keypair.pubkey() != mint_seed.pubkey() - { - signers.push(mint_authority_keypair); - } +/// # Example +/// ```ignore +/// let (signature, mint) = CreateMint { +/// decimals: 9, +/// freeze_authority: Some(freeze_authority_pubkey), +/// token_metadata: Some(TokenMetadata { +/// name: "My Token".to_string(), +/// symbol: "MTK".to_string(), +/// uri: "https://example.com/metadata.json".to_string(), +/// ..Default::default() +/// }), +/// seed: None, // auto-generate, or Some(keypair) for deterministic address +/// }.execute(&mut rpc, &payer, &mint_authority).await?; +/// ``` +#[derive(Default, Debug)] +pub struct CreateMint { + /// Number of decimals for the token. + pub decimals: u8, + /// Optional authority that can freeze token accounts. + pub freeze_authority: Option, + /// Optional token metadata (name, symbol, uri). + pub token_metadata: Option, + /// Optional seed keypair for deterministic mint address. + /// If None, a new keypair is generated. + pub seed: Option, +} + +impl CreateMint { + /// Execute the create_mint action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client that implements both `Rpc` and `Indexer` traits + /// * `payer` - Transaction fee payer keypair + /// * `mint_authority` - Authority that can mint new tokens + /// + /// # Returns + /// `Result<(Signature, Pubkey), RpcError>` - The transaction signature and mint public key + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + mint_authority: &Keypair, + ) -> Result<(Signature, Pubkey), RpcError> { + let mint_seed = self.seed.unwrap_or_else(Keypair::new); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info()?.queue; + + // Derive compression address + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - // Send the transaction - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) - .await + // Find mint PDA + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); + + // Get validity proof for the address + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .map_err(|e| RpcError::CustomError(format!("Failed to get validity proof: {}", e)))? + .value; + + // Build extensions if token metadata is provided + let extensions = self.token_metadata.map(|metadata| { + let additional_metadata = metadata.additional_metadata.map(|items| { + items + .into_iter() + .map(|(key, value)| AdditionalMetadata { + key: key.into_bytes(), + value: value.into_bytes(), + }) + .collect() + }); + + vec![ExtensionInstructionData::TokenMetadata( + TokenMetadataInstructionData { + update_authority: Some( + metadata + .update_authority + .unwrap_or_else(|| mint_authority.pubkey()) + .to_bytes() + .into(), + ), + name: metadata.name.into_bytes(), + symbol: metadata.symbol.into_bytes(), + uri: metadata.uri.into_bytes(), + additional_metadata, + }, + )] + }); + + // Build params + let params = CreateMintInstructionParams { + decimals: self.decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority: mint_authority.pubkey(), + proof: rpc_result.proof.0.ok_or_else(|| { + RpcError::CustomError("Validity proof is required for create_mint".to_string()) + })?, + compression_address, + mint, + bump, + freeze_authority: self.freeze_authority, + extensions, + rent_payment: 16, // ~24 hours rent + write_top_up: 766, // ~3 hours per write + }; + + // Create instruction + let instruction = CreateMintInstruction::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ) + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + // Build signers list + let mut signers: Vec<&Keypair> = vec![payer, &mint_seed]; + if mint_authority.pubkey() != payer.pubkey() { + signers.push(mint_authority); + } + + // Send transaction + let signature = rpc + .create_and_send_transaction(&[instruction], &payer.pubkey(), &signers) + .await?; + + Ok((signature, mint)) + } } diff --git a/sdk-libs/token-client/src/actions/mint_to.rs b/sdk-libs/token-client/src/actions/mint_to.rs new file mode 100644 index 0000000000..af7d76403f --- /dev/null +++ b/sdk-libs/token-client/src/actions/mint_to.rs @@ -0,0 +1,75 @@ +//! Mint tokens action for Light Token. +//! +//! Simple interface for minting tokens to a Light Token account. + +use light_client::rpc::{Rpc, RpcError}; +use light_token::instruction::MintTo as MintToInstruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for minting tokens to a Light Token account. +/// +/// # Example +/// ```ignore +/// MintTo { +/// mint, +/// destination, +/// amount: 1000, +/// ..Default::default() +/// }.execute(&mut rpc, &payer, &mint_authority).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct MintTo { + /// The mint public key. + pub mint: Pubkey, + /// The destination token account. + pub destination: Pubkey, + /// Amount of tokens to mint. + pub amount: u64, +} + +impl MintTo { + /// Execute the mint_to action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair (also pays for rent top-ups) + /// * `authority` - The mint authority keypair + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + authority: &Keypair, + ) -> Result { + // Only set fee_payer if payer differs from authority + let fee_payer = if payer.pubkey() != authority.pubkey() { + Some(payer.pubkey()) + } else { + None + }; + + let ix = MintToInstruction { + mint: self.mint, + destination: self.destination, + amount: self.amount, + authority: authority.pubkey(), + max_top_up: None, + fee_payer, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let mut signers: Vec<&Keypair> = vec![payer]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } +} diff --git a/sdk-libs/token-client/src/actions/mod.rs b/sdk-libs/token-client/src/actions/mod.rs index 712b0172af..7b902fb8f8 100644 --- a/sdk-libs/token-client/src/actions/mod.rs +++ b/sdk-libs/token-client/src/actions/mod.rs @@ -1,16 +1,36 @@ -mod create_compressible_token_account; -mod create_mint; -mod mint_action; -mod mint_to_compressed; -mod spl_interface; -mod transfer; -pub mod transfer2; -mod update_compressed_mint; +//! Clean action interfaces for Light Token operations. +//! +//! These actions provide simple, ergonomic interfaces for common Light Token operations. +//! +//! All actions use a params struct pattern with an `execute` method: +//! ```ignore +//! Transfer { +//! source, +//! destination, +//! amount: 1000, +//! ..Default::default() +//! }.execute(&mut rpc, &payer, &authority).await?; +//! ``` -pub use create_compressible_token_account::*; -pub use create_mint::*; -pub use mint_action::*; -pub use mint_to_compressed::*; -pub use spl_interface::*; -pub use transfer::*; -pub use update_compressed_mint::*; +pub mod approve; +pub mod create_ata; +pub mod create_mint; +pub mod mint_to; +pub mod revoke; +pub mod transfer; +pub mod transfer_checked; +pub mod transfer_interface; +pub mod unwrap; +pub mod wrap; + +// Re-export all action structs +pub use approve::Approve; +pub use create_ata::{derive_ata_address, get_ata_address, CreateAta}; +pub use create_mint::{CreateMint, TokenMetadata}; +pub use mint_to::MintTo; +pub use revoke::Revoke; +pub use transfer::Transfer; +pub use transfer_checked::TransferChecked; +pub use transfer_interface::TransferInterface; +pub use unwrap::Unwrap; +pub use wrap::Wrap; diff --git a/sdk-libs/token-client/src/actions/revoke.rs b/sdk-libs/token-client/src/actions/revoke.rs new file mode 100644 index 0000000000..803b4516ad --- /dev/null +++ b/sdk-libs/token-client/src/actions/revoke.rs @@ -0,0 +1,110 @@ +//! Revoke delegation action for Light Token. +//! +//! Simple interface for revoking a delegate on a Light Token account. + +use light_client::rpc::{Rpc, RpcError}; +use light_token::instruction::Revoke as RevokeInstruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for revoking delegation for a Light Token account. +/// +/// If `owner` is `Some`, the owner keypair will be used as the signer. +/// If `owner` is `None`, the payer will be used as the owner. +/// +/// # Example +/// ```ignore +/// // Payer is the owner +/// Revoke { +/// token_account, +/// owner: None, +/// }.execute(&mut rpc, &payer).await?; +/// +/// // Separate owner +/// Revoke { +/// token_account, +/// owner: Some(owner_pubkey), +/// }.execute_with_owner(&mut rpc, &payer, &owner_keypair).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct Revoke { + /// The token account to revoke delegation for. + pub token_account: Pubkey, + /// Optional owner public key (for separate owner scenario). + /// If None, the payer is used as the owner. + pub owner: Option, +} + +impl Revoke { + /// Execute the revoke action via RPC where payer is the owner. + /// + /// This method only supports cases where `owner == payer`. If you need a + /// separate owner and payer, use [`execute_with_owner`](Self::execute_with_owner). + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair (must also be the owner) + /// + /// # Returns + /// `Result` - The transaction signature + /// + /// # Errors + /// Returns an error if `self.owner` is `Some` and does not equal `payer.pubkey()`. + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + ) -> Result { + let owner_pubkey = self.owner.unwrap_or_else(|| payer.pubkey()); + + if owner_pubkey != payer.pubkey() { + return Err(RpcError::CustomError( + "owner does not match payer; use execute_with_owner for separate owner/payer" + .to_string(), + )); + } + + let ix = RevokeInstruction { + token_account: self.token_account, + owner: owner_pubkey, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer]) + .await + } + + /// Execute the revoke action via RPC with a separate owner. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair + /// * `owner` - The owner of the token account (signer) + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute_with_owner( + self, + rpc: &mut R, + payer: &Keypair, + owner: &Keypair, + ) -> Result { + let ix = RevokeInstruction { + token_account: self.token_account, + owner: owner.pubkey(), + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let mut signers: Vec<&Keypair> = vec![payer]; + if owner.pubkey() != payer.pubkey() { + signers.push(owner); + } + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } +} diff --git a/sdk-libs/token-client/src/actions/transfer.rs b/sdk-libs/token-client/src/actions/transfer.rs index 2d32024fb0..de6089df55 100644 --- a/sdk-libs/token-client/src/actions/transfer.rs +++ b/sdk-libs/token-client/src/actions/transfer.rs @@ -1,78 +1,75 @@ +//! Transfer actions for Light Token. +//! +//! These actions provide clean interfaces for transferring Light Tokens. + use light_client::rpc::{Rpc, RpcError}; -use solana_instruction::{AccountMeta, Instruction}; +use light_token::instruction::Transfer as TransferInstruction; use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_signer::Signer; -const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32]; - -/// Transfer from one token account to another. -/// -/// # Arguments -/// * `rpc` - RPC client -/// * `source` - Source token account (decompressed compressed token account) -/// * `destination` - Destination token account -/// * `amount` - Amount of tokens to transfer -/// * `authority` - Authority that can spend from the source token account -/// * `payer` - Transaction fee payer keypair +/// Parameters for transferring Light Tokens between accounts. /// -/// # Returns -/// `Result` - The transaction signature -pub async fn transfer( - rpc: &mut R, - source: Pubkey, - destination: Pubkey, - amount: u64, - authority: &Keypair, - payer: &Keypair, -) -> Result { - let transfer_instruction = - create_transfer_token_instruction(source, destination, amount, authority.pubkey())?; +/// # Example +/// ```ignore +/// Transfer { +/// source, +/// destination, +/// amount: 1000, +/// ..Default::default() +/// }.execute(&mut rpc, &payer, &authority).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct Transfer { + /// Source token account. + pub source: Pubkey, + /// Destination token account. + pub destination: Pubkey, + /// Amount of tokens to transfer. + pub amount: u64, +} - let mut signers = vec![payer]; - if authority.pubkey() != payer.pubkey() { - signers.push(authority); - } +impl Transfer { + /// Execute the transfer action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair (also pays for rent top-ups) + /// * `authority` - Authority that can spend from the source account + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + authority: &Keypair, + ) -> Result { + // Only set fee_payer if payer differs from authority + let fee_payer = if payer.pubkey() != authority.pubkey() { + Some(payer.pubkey()) + } else { + None + }; - rpc.create_and_send_transaction(&[transfer_instruction], &payer.pubkey(), &signers) - .await -} + let ix = TransferInstruction { + source: self.source, + destination: self.destination, + amount: self.amount, + authority: authority.pubkey(), + max_top_up: None, + fee_payer, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; -// TODO: consume the variant from compressed-token-sdk instead -/// Create a token transfer instruction. -/// -/// # Arguments -/// * `source` - Source token account -/// * `destination` - Destination token account -/// * `amount` - Amount to transfer -/// * `authority` - Authority pubkey -/// -/// # Returns -/// `Result` -#[allow(clippy::result_large_err)] -pub fn create_transfer_token_instruction( - source: Pubkey, - destination: Pubkey, - amount: u64, - authority: Pubkey, -) -> Result { - let transfer_instruction = Instruction { - program_id: Pubkey::from(light_token_interface::LIGHT_TOKEN_PROGRAM_ID), - accounts: vec![ - AccountMeta::new(source, false), // Source token account - AccountMeta::new(destination, false), // Destination token account - AccountMeta::new(authority, true), // Authority must be writable for potential top-ups - AccountMeta::new_readonly(Pubkey::from(SYSTEM_PROGRAM_ID), false), // System program for rent top-ups - ], - data: { - // CTokenTransfer discriminator - let mut data = vec![3u8]; - // Add SPL Token Transfer instruction data exactly like SPL does - data.extend_from_slice(&amount.to_le_bytes()); // Amount as u64 little-endian - data - }, - }; + let mut signers = vec![payer]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } - Ok(transfer_instruction) + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } } diff --git a/sdk-libs/token-client/src/actions/transfer_checked.rs b/sdk-libs/token-client/src/actions/transfer_checked.rs new file mode 100644 index 0000000000..0a5e0f3d7b --- /dev/null +++ b/sdk-libs/token-client/src/actions/transfer_checked.rs @@ -0,0 +1,86 @@ +//! Transfer checked action for Light Token. +//! +//! This action provides a clean interface for transferring Light Tokens with decimal validation. + +use light_client::rpc::{Rpc, RpcError}; +use light_token::instruction::TransferChecked as TransferCheckedInstruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for transferring Light Tokens with decimal validation. +/// +/// Unlike the basic transfer, this validates the amount against +/// the token's decimals to ensure the transfer is using the correct precision. +/// +/// # Example +/// ```ignore +/// TransferChecked { +/// source, +/// mint, +/// destination, +/// amount: 1000, +/// decimals: 9, +/// ..Default::default() +/// }.execute(&mut rpc, &payer, &authority).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct TransferChecked { + /// Source token account. + pub source: Pubkey, + /// The mint public key. + pub mint: Pubkey, + /// Destination token account. + pub destination: Pubkey, + /// Amount of tokens to transfer. + pub amount: u64, + /// Expected decimals for the token. + pub decimals: u8, +} + +impl TransferChecked { + /// Execute the transfer_checked action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair (also pays for rent top-ups) + /// * `authority` - Authority that can spend from the source account + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + authority: &Keypair, + ) -> Result { + // Only set fee_payer if payer differs from authority + let fee_payer = if payer.pubkey() != authority.pubkey() { + Some(payer.pubkey()) + } else { + None + }; + + let ix = TransferCheckedInstruction { + source: self.source, + mint: self.mint, + destination: self.destination, + amount: self.amount, + decimals: self.decimals, + authority: authority.pubkey(), + max_top_up: None, + fee_payer, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let mut signers = vec![payer]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } +} diff --git a/sdk-libs/token-client/src/actions/transfer_interface.rs b/sdk-libs/token-client/src/actions/transfer_interface.rs new file mode 100644 index 0000000000..9ecaf9ca1b --- /dev/null +++ b/sdk-libs/token-client/src/actions/transfer_interface.rs @@ -0,0 +1,122 @@ +//! Transfer interface action for Light Token. +//! +//! This action provides a clean interface for transferring tokens that auto-routes +//! based on the account types (Light or SPL). + +use light_client::rpc::{Rpc, RpcError}; +use light_token::{ + instruction::{SplInterface, TransferInterface as TransferInterfaceInstruction}, + spl_interface::find_spl_interface_pda_with_index, +}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for transferring tokens using the interface that auto-routes based on account types. +/// +/// This automatically detects whether the source and destination are +/// Light token accounts or SPL token accounts and routes the transfer accordingly: +/// - Light -> Light: Direct transfer between Light accounts +/// - Light -> SPL: Decompress from Light to SPL account +/// - SPL -> Light: Compress from SPL to Light account +/// - SPL -> SPL: Pass-through to SPL token program +/// +/// # Example +/// ```ignore +/// TransferInterface { +/// source, +/// mint, +/// destination, +/// amount: 1000, +/// decimals: 9, +/// ..Default::default() +/// }.execute(&mut rpc, &payer, &authority).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct TransferInterface { + /// Source token account. + pub source: Pubkey, + /// The mint public key. + pub mint: Pubkey, + /// Destination token account. + pub destination: Pubkey, + /// Amount of tokens to transfer. + pub amount: u64, + /// Token decimals. + pub decimals: u8, + /// SPL token program (spl_token::ID or spl_token_2022::ID), required for cross-interface transfers. + pub spl_token_program: Option, + /// Whether the mint has restricted extensions (Token-2022 specific). + pub restricted: bool, +} + +impl TransferInterface { + /// Execute the transfer_interface action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair + /// * `authority` - Authority that can spend from the source account + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + authority: &Keypair, + ) -> Result { + // Fetch account info to determine owners + let source_account = rpc.get_account(self.source).await?.ok_or_else(|| { + RpcError::CustomError(format!("Source account {} not found", self.source)) + })?; + + let destination_account = rpc.get_account(self.destination).await?.ok_or_else(|| { + RpcError::CustomError(format!( + "Destination account {} not found", + self.destination + )) + })?; + + let source_owner = source_account.owner; + let destination_owner = destination_account.owner; + + // Build SplInterface if needed for cross-interface transfers + let spl_interface = if let Some(spl_program) = self.spl_token_program { + let (spl_interface_pda, spl_interface_pda_bump) = + find_spl_interface_pda_with_index(&self.mint, 0, self.restricted); + Some(SplInterface { + mint: self.mint, + spl_token_program: spl_program, + spl_interface_pda, + spl_interface_pda_bump, + }) + } else { + None + }; + + let ix = TransferInterfaceInstruction { + source: self.source, + destination: self.destination, + amount: self.amount, + decimals: self.decimals, + authority: authority.pubkey(), + payer: payer.pubkey(), + spl_interface, + max_top_up: None, + source_owner, + destination_owner, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let mut signers = vec![payer]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } +} diff --git a/sdk-libs/token-client/src/actions/unwrap.rs b/sdk-libs/token-client/src/actions/unwrap.rs new file mode 100644 index 0000000000..72af63de42 --- /dev/null +++ b/sdk-libs/token-client/src/actions/unwrap.rs @@ -0,0 +1,84 @@ +//! Unwrap Light Token to SPL tokens action. +//! +//! Unwraps Light Token back to an SPL token account. + +use light_client::rpc::{Rpc, RpcError}; +use light_token::{ + constants::SPL_TOKEN_PROGRAM_ID, + instruction::{get_spl_interface_pda_and_bump, TransferToSpl}, +}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for unwrapping Light Token back to SPL tokens. +/// +/// This transfers tokens from a Light Token account to an SPL token account. +/// +/// # Example +/// ```ignore +/// Unwrap { +/// source, +/// destination_spl_ata, +/// mint, +/// amount: 1000, +/// decimals: 9, +/// }.execute(&mut rpc, &payer, &authority).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct Unwrap { + /// Source Light Token account. + pub source: Pubkey, + /// Destination SPL token account. + pub destination_spl_ata: Pubkey, + /// The mint public key. + pub mint: Pubkey, + /// Amount of tokens to unwrap. + pub amount: u64, + /// Token decimals. + pub decimals: u8, +} + +impl Unwrap { + /// Execute the unwrap action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair + /// * `authority` - Authority for the source Light Token account + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + authority: &Keypair, + ) -> Result { + let (spl_interface_pda, bump) = get_spl_interface_pda_and_bump(&self.mint); + + let ix = TransferToSpl { + source: self.source, + destination_spl_token_account: self.destination_spl_ata, + amount: self.amount, + authority: authority.pubkey(), + mint: self.mint, + payer: payer.pubkey(), + spl_interface_pda, + spl_interface_pda_bump: bump, + decimals: self.decimals, + spl_token_program: SPL_TOKEN_PROGRAM_ID, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let mut signers: Vec<&Keypair> = vec![payer]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } +} diff --git a/sdk-libs/token-client/src/actions/wrap.rs b/sdk-libs/token-client/src/actions/wrap.rs new file mode 100644 index 0000000000..89ca53ca82 --- /dev/null +++ b/sdk-libs/token-client/src/actions/wrap.rs @@ -0,0 +1,85 @@ +//! Wrap SPL tokens to Light Token action. +//! +//! Wraps SPL tokens into a Light Token account (rent-free storage). + +use light_client::rpc::{Rpc, RpcError}; +use light_token::{ + constants::SPL_TOKEN_PROGRAM_ID, + instruction::{get_spl_interface_pda_and_bump, TransferFromSpl}, +}; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signature::Signature; +use solana_signer::Signer; + +/// Parameters for wrapping SPL tokens into a Light Token account. +/// +/// This transfers tokens from an SPL token account to a Light Token account, +/// enabling rent-free storage. +/// +/// # Example +/// ```ignore +/// Wrap { +/// source_spl_ata, +/// destination, +/// mint, +/// amount: 1000, +/// decimals: 9, +/// }.execute(&mut rpc, &payer, &authority).await?; +/// ``` +#[derive(Default, Clone, Debug)] +pub struct Wrap { + /// Source SPL token account. + pub source_spl_ata: Pubkey, + /// Destination Light Token account. + pub destination: Pubkey, + /// The mint public key. + pub mint: Pubkey, + /// Amount of tokens to wrap. + pub amount: u64, + /// Token decimals. + pub decimals: u8, +} + +impl Wrap { + /// Execute the wrap action via RPC. + /// + /// # Arguments + /// * `rpc` - RPC client + /// * `payer` - Transaction fee payer keypair + /// * `authority` - Authority for the source SPL token account + /// + /// # Returns + /// `Result` - The transaction signature + pub async fn execute( + self, + rpc: &mut R, + payer: &Keypair, + authority: &Keypair, + ) -> Result { + let (spl_interface_pda, bump) = get_spl_interface_pda_and_bump(&self.mint); + + let ix = TransferFromSpl { + amount: self.amount, + spl_interface_pda_bump: bump, + decimals: self.decimals, + source_spl_token_account: self.source_spl_ata, + destination: self.destination, + authority: authority.pubkey(), + mint: self.mint, + payer: payer.pubkey(), + spl_interface_pda, + spl_token_program: SPL_TOKEN_PROGRAM_ID, + } + .instruction() + .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; + + let mut signers: Vec<&Keypair> = vec![payer]; + if authority.pubkey() != payer.pubkey() { + signers.push(authority); + } + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &signers) + .await + } +} diff --git a/sdk-libs/token-client/src/lib.rs b/sdk-libs/token-client/src/lib.rs index e5e9399ef0..4368dfed4a 100644 --- a/sdk-libs/token-client/src/lib.rs +++ b/sdk-libs/token-client/src/lib.rs @@ -1,4 +1,4 @@ pub mod actions; -pub mod instructions; -// re-export -pub use light_token::instruction; + +// Re-export actions at crate root for convenience +pub use actions::*; diff --git a/sdk-libs/token-sdk/src/instruction/approve_checked.rs b/sdk-libs/token-sdk/src/instruction/approve_checked.rs deleted file mode 100644 index 4b1939fe3a..0000000000 --- a/sdk-libs/token-sdk/src/instruction/approve_checked.rs +++ /dev/null @@ -1,144 +0,0 @@ -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use solana_account_info::AccountInfo; -use solana_cpi::{invoke, invoke_signed}; -use solana_instruction::{AccountMeta, Instruction}; -use solana_program_error::ProgramError; -use solana_pubkey::Pubkey; - -/// # Approve a delegate for a Light Token account with decimals validation: -/// ```rust -/// # use solana_pubkey::Pubkey; -/// # use light_token::instruction::ApproveChecked; -/// # let token_account = Pubkey::new_unique(); -/// # let mint = Pubkey::new_unique(); -/// # let delegate = Pubkey::new_unique(); -/// # let owner = Pubkey::new_unique(); -/// let instruction = ApproveChecked { -/// token_account, -/// mint, -/// delegate, -/// owner, -/// amount: 100, -/// decimals: 8, -/// max_top_up: None, -/// }.instruction()?; -/// # Ok::<(), solana_program_error::ProgramError>(()) -/// ``` -pub struct ApproveChecked { - /// Light Token account to approve delegation for - pub token_account: Pubkey, - /// Mint account (for decimals validation - may be skipped if Light Token has cached decimals) - pub mint: Pubkey, - /// Delegate to approve - pub delegate: Pubkey, - /// Owner of the Light Token account (signer, payer for top-up) - pub owner: Pubkey, - /// Amount of tokens to delegate - pub amount: u64, - /// Expected token decimals - pub decimals: u8, - /// Maximum lamports for rent top-up. Transaction fails if exceeded. (0 = no limit) - pub max_top_up: Option, -} - -/// # Approve Light Token via CPI with decimals validation: -/// ```rust,no_run -/// # use light_token::instruction::ApproveCheckedCpi; -/// # use solana_account_info::AccountInfo; -/// # let token_account: AccountInfo = todo!(); -/// # let mint: AccountInfo = todo!(); -/// # let delegate: AccountInfo = todo!(); -/// # let owner: AccountInfo = todo!(); -/// # let system_program: AccountInfo = todo!(); -/// ApproveCheckedCpi { -/// token_account, -/// mint, -/// delegate, -/// owner, -/// system_program, -/// amount: 100, -/// decimals: 8, -/// max_top_up: None, -/// } -/// .invoke()?; -/// # Ok::<(), solana_program_error::ProgramError>(()) -/// ``` -pub struct ApproveCheckedCpi<'info> { - pub token_account: AccountInfo<'info>, - pub mint: AccountInfo<'info>, - pub delegate: AccountInfo<'info>, - pub owner: AccountInfo<'info>, - pub system_program: AccountInfo<'info>, - pub amount: u64, - pub decimals: u8, - /// Maximum lamports for rent top-up. Transaction fails if exceeded. (0 = no limit) - pub max_top_up: Option, -} - -impl<'info> ApproveCheckedCpi<'info> { - pub fn instruction(&self) -> Result { - ApproveChecked::from(self).instruction() - } - - pub fn invoke(self) -> Result<(), ProgramError> { - let instruction = ApproveChecked::from(&self).instruction()?; - let account_infos = [ - self.token_account, - self.mint, - self.delegate, - self.owner, - self.system_program, - ]; - invoke(&instruction, &account_infos) - } - - pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> { - let instruction = ApproveChecked::from(&self).instruction()?; - let account_infos = [ - self.token_account, - self.mint, - self.delegate, - self.owner, - self.system_program, - ]; - invoke_signed(&instruction, &account_infos, signer_seeds) - } -} - -impl<'info> From<&ApproveCheckedCpi<'info>> for ApproveChecked { - fn from(cpi: &ApproveCheckedCpi<'info>) -> Self { - Self { - token_account: *cpi.token_account.key, - mint: *cpi.mint.key, - delegate: *cpi.delegate.key, - owner: *cpi.owner.key, - amount: cpi.amount, - decimals: cpi.decimals, - max_top_up: cpi.max_top_up, - } - } -} - -impl ApproveChecked { - pub fn instruction(self) -> Result { - let mut data = vec![13u8]; // CTokenApproveChecked discriminator (SPL compatible) - data.extend_from_slice(&self.amount.to_le_bytes()); - data.push(self.decimals); - // Include max_top_up if set (11-byte format) - if let Some(max_top_up) = self.max_top_up { - data.extend_from_slice(&max_top_up.to_le_bytes()); - } - - Ok(Instruction { - program_id: Pubkey::from(LIGHT_TOKEN_PROGRAM_ID), - accounts: vec![ - AccountMeta::new(self.token_account, false), - AccountMeta::new_readonly(self.mint, false), - AccountMeta::new_readonly(self.delegate, false), - AccountMeta::new(self.owner, true), - AccountMeta::new_readonly(Pubkey::default(), false), - ], - data, - }) - } -} diff --git a/sdk-libs/token-sdk/src/instruction/mod.rs b/sdk-libs/token-sdk/src/instruction/mod.rs index d5be2ca2f1..0babeb4475 100644 --- a/sdk-libs/token-sdk/src/instruction/mod.rs +++ b/sdk-libs/token-sdk/src/instruction/mod.rs @@ -93,7 +93,6 @@ //! mod approve; -mod approve_checked; mod burn; mod burn_checked; mod close; @@ -116,7 +115,6 @@ mod transfer_interface; mod transfer_to_spl; pub use approve::*; -pub use approve_checked::*; pub use burn::*; pub use burn_checked::*; pub use close::{CloseAccount, CloseAccountCpi}; diff --git a/sdk-tests/sdk-light-token-test/tests/shared.rs b/sdk-tests/sdk-light-token-test/tests/shared.rs index ab95972c50..aecc786261 100644 --- a/sdk-tests/sdk-light-token-test/tests/shared.rs +++ b/sdk-tests/sdk-light-token-test/tests/shared.rs @@ -409,10 +409,10 @@ pub async fn setup_create_compressed_only_mint( mint_authority: Pubkey, decimals: u8, ) -> (Pubkey, [u8; 32], Keypair) { - use light_token::instruction::{derive_mint_compressed_address, find_mint_address}; - use light_token_client::instructions::mint_action::{ + use light_test_utils::actions::legacy::instructions::mint_action::{ create_mint_action_instruction, MintActionParams, NewMint, }; + use light_token::instruction::{derive_mint_compressed_address, find_mint_address}; let mint_seed = Keypair::new(); let address_tree = rpc.get_address_tree_v2(); diff --git a/sdk-tests/sdk-light-token-test/tests/test_decompress_mint.rs b/sdk-tests/sdk-light-token-test/tests/test_decompress_mint.rs index e22111be8d..4f3613aac9 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_decompress_mint.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_decompress_mint.rs @@ -7,10 +7,10 @@ use borsh::BorshDeserialize; use light_client::{indexer::Indexer, rpc::Rpc}; use light_compressible::compression_info::CompressionInfo; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_token::instruction::derive_mint_compressed_address; -use light_token_client::instructions::mint_action::{ +use light_test_utils::actions::legacy::instructions::mint_action::{ create_mint_action_instruction, MintActionParams, MintActionType, }; +use light_token::instruction::derive_mint_compressed_address; use light_token_interface::state::Mint; use solana_sdk::signer::Signer; diff --git a/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs b/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs index 26f9563c18..6ff8560390 100644 --- a/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs +++ b/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs @@ -9,8 +9,10 @@ use light_compressed_token_sdk::compressed_token::{ }; use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::instruction::PackedAccounts; -use light_test_utils::airdrop_lamports; -use light_token_client::{actions::mint_action_comprehensive, instructions::mint_action::NewMint}; +use light_test_utils::{ + actions::{legacy::instructions::mint_action::NewMint, mint_action_comprehensive}, + airdrop_lamports, +}; use light_token_interface::instructions::mint_action::{MintWithContext, Recipient}; use sdk_token_test::mint_compressed_tokens_cpi_write::MintCompressedTokensCpiWriteParams; use solana_sdk::{ diff --git a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs index 968107d32f..fd978aeba7 100644 --- a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs +++ b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs @@ -123,7 +123,7 @@ async fn create_compressed_mints_and_tokens( .expect("Compressed token account for mint1 should exist"); let decompress_instruction = - light_token_client::instructions::transfer2::create_decompress_instruction( + light_test_utils::actions::legacy::instructions::transfer2::create_decompress_instruction( rpc, std::slice::from_ref(mint1_token_account), decompress_amount, diff --git a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs index a97a3cddc8..badb7dce42 100644 --- a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs +++ b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs @@ -8,11 +8,11 @@ use light_compressed_token_sdk::compressed_token::{ }; use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; +use light_test_utils::actions::legacy::instructions::transfer2::create_decompress_instruction; use light_token::instruction::{ config_pda, derive_token_ata, rent_sponsor_pda, CompressibleParams, CreateAssociatedTokenAccount, }; -use light_token_client::instructions::transfer2::create_decompress_instruction; use light_token_interface::{ instructions::mint_action::{MintWithContext, Recipient}, state::{BaseMint, Mint, MintMetadata, TokenDataVersion, ACCOUNT_TYPE_MINT}, diff --git a/sdk-tests/token-client-test/Cargo.toml b/sdk-tests/token-client-test/Cargo.toml new file mode 100644 index 0000000000..afad75afa6 --- /dev/null +++ b/sdk-tests/token-client-test/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "token-client-test" +version = "0.1.0" +description = "Integration tests for light-token-client actions" +repository = "https://github.com/Lightprotocol/light-protocol" +license = "Apache-2.0" +edition = "2021" + +[features] +test-sbf = [] + +[dev-dependencies] +light-program-test = { workspace = true, features = ["devenv"] } +light-client = { workspace = true, features = ["v2"] } +light-token-client = { workspace = true } +light-token = { workspace = true } +light-token-interface = { workspace = true } +light-test-utils = { workspace = true } +tokio = { workspace = true } +solana-sdk = { workspace = true } +borsh = { workspace = true } +spl-token = { workspace = true } + +[lints.rust.unexpected_cfgs] +level = "allow" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sdk-tests/token-client-test/tests/test_approve_revoke.rs b/sdk-tests/token-client-test/tests/test_approve_revoke.rs new file mode 100644 index 0000000000..974023fcef --- /dev/null +++ b/sdk-tests/token-client-test/tests/test_approve_revoke.rs @@ -0,0 +1,427 @@ +//! Tests for the approve and revoke actions in light-token-client. + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::instruction::derive_token_ata; +use light_token_client::actions::{Approve, CreateAta, CreateMint, MintTo, Revoke, Transfer}; +use light_token_interface::state::{AccountState, Token}; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; + +fn get_expected_token( + actual: &Token, + mint: Pubkey, + owner: Pubkey, + amount: u64, + delegate: Option, + delegated_amount: u64, +) -> Token { + Token { + mint: mint.to_bytes().into(), + owner: owner.to_bytes().into(), + amount, + delegate: delegate.map(|d| d.to_bytes().into()), + state: AccountState::Initialized, + is_native: None, + delegated_amount, + close_authority: None, + account_type: actual.account_type, + extensions: actual.extensions.clone(), + } +} + +/// Test approving a delegate for a token account. +#[tokio::test] +async fn test_approve_basic() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create ATA for payer + let owner = payer.pubkey(); + let (token_account, _) = derive_token_ata(&owner, &mint); + + CreateAta { + mint, + owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens + let mint_amount = 1000u64; + MintTo { + mint, + destination: token_account, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Approve delegate + let delegate = Pubkey::new_unique(); + let delegate_amount = 500u64; + + Approve { + token_account, + delegate, + amount: delegate_amount, + owner: None, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Verify delegation + let account_data = rpc.get_account(token_account).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let expected = get_expected_token( + &token_state, + mint, + owner, + mint_amount, + Some(delegate), + delegate_amount, + ); + assert_eq!(token_state, expected); +} + +/// Test approving with a separate owner keypair. +#[tokio::test] +async fn test_approve_with_separate_owner() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create ATA for a different owner + let owner = Keypair::new(); + let (token_account, _) = derive_token_ata(&owner.pubkey(), &mint); + + CreateAta { + mint, + owner: owner.pubkey(), + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens + let mint_amount = 1000u64; + MintTo { + mint, + destination: token_account, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Approve delegate with separate owner + let delegate = Pubkey::new_unique(); + let delegate_amount = 300u64; + + Approve { + token_account, + delegate, + amount: delegate_amount, + owner: Some(owner.pubkey()), + } + .execute_with_owner(&mut rpc, &payer, &owner) + .await + .unwrap(); + + // Verify delegation + let account_data = rpc.get_account(token_account).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let expected = get_expected_token( + &token_state, + mint, + owner.pubkey(), + mint_amount, + Some(delegate), + delegate_amount, + ); + assert_eq!(token_state, expected); +} + +/// Test revoking a delegate. +#[tokio::test] +async fn test_revoke_basic() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create ATA + let owner = payer.pubkey(); + let (token_account, _) = derive_token_ata(&owner, &mint); + + CreateAta { + mint, + owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens + let mint_amount = 1000u64; + MintTo { + mint, + destination: token_account, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Approve delegate first + let delegate = Pubkey::new_unique(); + let delegate_amount = 500u64; + + Approve { + token_account, + delegate, + amount: delegate_amount, + owner: None, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Verify delegation is set + let account_data = rpc.get_account(token_account).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let expected_with_delegate = get_expected_token( + &token_state, + mint, + owner, + mint_amount, + Some(delegate), + delegate_amount, + ); + assert_eq!(token_state, expected_with_delegate); + + // Revoke delegate + Revoke { + token_account, + owner: None, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Verify delegation is revoked + let account_data = rpc.get_account(token_account).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let expected_revoked = get_expected_token(&token_state, mint, owner, mint_amount, None, 0); + assert_eq!(token_state, expected_revoked); +} + +/// Test revoking with a separate owner keypair. +#[tokio::test] +async fn test_revoke_with_separate_owner() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create ATA for a different owner + let owner = Keypair::new(); + let (token_account, _) = derive_token_ata(&owner.pubkey(), &mint); + + CreateAta { + mint, + owner: owner.pubkey(), + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens + let mint_amount = 1000u64; + MintTo { + mint, + destination: token_account, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Approve delegate + let delegate = Pubkey::new_unique(); + Approve { + token_account, + delegate, + amount: 500, + owner: Some(owner.pubkey()), + } + .execute_with_owner(&mut rpc, &payer, &owner) + .await + .unwrap(); + + // Revoke with separate owner + Revoke { + token_account, + owner: Some(owner.pubkey()), + } + .execute_with_owner(&mut rpc, &payer, &owner) + .await + .unwrap(); + + // Verify delegation is revoked + let account_data = rpc.get_account(token_account).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let expected = get_expected_token(&token_state, mint, owner.pubkey(), mint_amount, None, 0); + assert_eq!(token_state, expected); +} + +/// Test delegate transfer using approved amount. +#[tokio::test] +async fn test_approve_and_delegate_transfer() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create source ATA + let source_owner = payer.pubkey(); + let (source_ata, _) = derive_token_ata(&source_owner, &mint); + + CreateAta { + mint, + owner: source_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Create destination ATA + let dest_owner = Pubkey::new_unique(); + let (dest_ata, _) = derive_token_ata(&dest_owner, &mint); + + CreateAta { + mint, + owner: dest_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens + let mint_amount = 1000u64; + MintTo { + mint, + destination: source_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create a delegate keypair + let delegate = Keypair::new(); + let delegate_amount = 500u64; + + // Approve the delegate + Approve { + token_account: source_ata, + delegate: delegate.pubkey(), + amount: delegate_amount, + owner: None, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Transfer using delegate authority + let transfer_amount = 300u64; + Transfer { + source: source_ata, + destination: dest_ata, + amount: transfer_amount, + } + .execute(&mut rpc, &payer, &delegate) + .await + .unwrap(); + + // Verify source account (delegated amount should be reduced) + let source_data = rpc.get_account(source_ata).await.unwrap().unwrap(); + let source_state = Token::deserialize(&mut &source_data.data[..]).unwrap(); + let expected_source = get_expected_token( + &source_state, + mint, + source_owner, + mint_amount - transfer_amount, + Some(delegate.pubkey()), + delegate_amount - transfer_amount, + ); + assert_eq!(source_state, expected_source); + + // Verify destination account + let dest_data = rpc.get_account(dest_ata).await.unwrap().unwrap(); + let dest_state = Token::deserialize(&mut &dest_data.data[..]).unwrap(); + let expected_dest = get_expected_token(&dest_state, mint, dest_owner, transfer_amount, None, 0); + assert_eq!(dest_state, expected_dest); +} diff --git a/sdk-tests/token-client-test/tests/test_create_mint.rs b/sdk-tests/token-client-test/tests/test_create_mint.rs new file mode 100644 index 0000000000..208c0e6328 --- /dev/null +++ b/sdk-tests/token-client-test/tests/test_create_mint.rs @@ -0,0 +1,160 @@ +//! Tests for the create_mint action in light-token-client. + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::instruction::find_mint_address; +use light_token_client::actions::{CreateMint, TokenMetadata}; +use light_token_interface::state::Mint; +use solana_sdk::{signature::Keypair, signer::Signer}; + +/// Test creating a new mint using the create_mint action with all fields. +#[tokio::test] +async fn test_create_mint_with_metadata() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + let freeze_authority = payer.pubkey(); + let seed = Keypair::new(); + + // Create mint with all fields + let (_, mint) = CreateMint { + decimals, + freeze_authority: Some(freeze_authority), + token_metadata: Some(TokenMetadata { + name: "Test Token".to_string(), + symbol: "TEST".to_string(), + uri: "https://example.com/metadata.json".to_string(), + update_authority: Some(payer.pubkey()), + additional_metadata: Some(vec![ + ("key1".to_string(), "value1".to_string()), + ("key2".to_string(), "value2".to_string()), + ]), + }), + seed: Some(seed), + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify the mint was created + let mint_account = rpc.get_account(mint).await.unwrap(); + assert!(mint_account.is_some(), "Mint account should exist"); + + let mint_data = mint_account.unwrap(); + let mint_state = Mint::deserialize(&mut &mint_data.data[..]).unwrap(); + + // Verify mint fields + assert_eq!(mint_state.base.decimals, decimals); + assert_eq!( + mint_state.base.mint_authority, + Some(payer.pubkey().to_bytes().into()) + ); + assert_eq!( + mint_state.base.freeze_authority, + Some(freeze_authority.to_bytes().into()) + ); + assert_eq!(mint_state.base.supply, 0); + assert!(mint_state.base.is_initialized); + + // Verify metadata + assert_eq!(mint_state.metadata.mint.to_bytes(), mint.to_bytes()); + assert!(mint_state.metadata.mint_decompressed); +} + +/// Test creating a mint with freeze authority. +#[tokio::test] +async fn test_create_mint_with_freeze_authority() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 6u8; + let freeze_authority = payer.pubkey(); + + // Create mint with freeze authority + let (_, mint) = CreateMint { + decimals, + freeze_authority: Some(freeze_authority), + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify the mint was created + let mint_account = rpc.get_account(mint).await.unwrap(); + assert!(mint_account.is_some(), "Mint account should exist"); + + let mint_data = mint_account.unwrap(); + let mint_state = Mint::deserialize(&mut &mint_data.data[..]).unwrap(); + + // Verify freeze authority + assert_eq!( + mint_state.base.freeze_authority, + Some(freeze_authority.to_bytes().into()) + ); +} + +/// Test creating a mint with deterministic seed. +#[tokio::test] +async fn test_create_mint_with_seed() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let seed = Keypair::new(); + let expected_mint = find_mint_address(&seed.pubkey()).0; + + // Create mint with explicit seed + let (_, mint) = CreateMint { + decimals: 9, + seed: Some(seed), + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify the mint address matches the expected derived address + assert_eq!(mint, expected_mint); + + // Verify the mint was created + let mint_account = rpc.get_account(mint).await.unwrap(); + assert!(mint_account.is_some(), "Mint account should exist"); +} + +/// Test creating multiple mints. +#[tokio::test] +async fn test_create_multiple_mints() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + // Create first mint + let (_, mint1) = CreateMint { + decimals: 9, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create second mint + let (_, mint2) = CreateMint { + decimals: 6, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify both mints are different + assert_ne!(mint1, mint2, "Mints should be different"); + + // Verify both mints exist + assert!(rpc.get_account(mint1).await.unwrap().is_some()); + assert!(rpc.get_account(mint2).await.unwrap().is_some()); +} diff --git a/sdk-tests/token-client-test/tests/test_transfer.rs b/sdk-tests/token-client-test/tests/test_transfer.rs new file mode 100644 index 0000000000..218037ffa2 --- /dev/null +++ b/sdk-tests/token-client-test/tests/test_transfer.rs @@ -0,0 +1,193 @@ +//! Tests for the transfer action in light-token-client. + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::instruction::derive_token_ata; +use light_token_client::actions::{CreateAta, CreateMint, MintTo, Transfer}; +use light_token_interface::state::{AccountState, Token}; +use solana_sdk::{pubkey::Pubkey, signer::Signer}; + +fn get_expected_token( + actual: &Token, + mint: Pubkey, + owner: Pubkey, + amount: u64, + delegate: Option, + delegated_amount: u64, +) -> Token { + Token { + mint: mint.to_bytes().into(), + owner: owner.to_bytes().into(), + amount, + delegate: delegate.map(|d| d.to_bytes().into()), + state: AccountState::Initialized, + is_native: None, + delegated_amount, + close_authority: None, + account_type: actual.account_type, + extensions: actual.extensions.clone(), + } +} + +/// Test transferring tokens between Light Token accounts. +#[tokio::test] +async fn test_transfer_basic() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create source and destination ATAs + let source_owner = payer.pubkey(); + let dest_owner = Pubkey::new_unique(); + + let (source_ata, _) = derive_token_ata(&source_owner, &mint); + let (dest_ata, _) = derive_token_ata(&dest_owner, &mint); + + CreateAta { + mint, + owner: source_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + CreateAta { + mint, + owner: dest_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens to source + let mint_amount = 1000u64; + MintTo { + mint, + destination: source_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Transfer tokens + let transfer_amount = 500u64; + Transfer { + source: source_ata, + destination: dest_ata, + amount: transfer_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify source account + let source_data = rpc.get_account(source_ata).await.unwrap().unwrap(); + let source_state = Token::deserialize(&mut &source_data.data[..]).unwrap(); + let expected_source = get_expected_token( + &source_state, + mint, + source_owner, + mint_amount - transfer_amount, + None, + 0, + ); + assert_eq!(source_state, expected_source); + + // Verify destination account + let dest_data = rpc.get_account(dest_ata).await.unwrap().unwrap(); + let dest_state = Token::deserialize(&mut &dest_data.data[..]).unwrap(); + let expected_dest = get_expected_token(&dest_state, mint, dest_owner, transfer_amount, None, 0); + assert_eq!(dest_state, expected_dest); +} + +/// Test transferring full balance. +#[tokio::test] +async fn test_transfer_full_balance() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create source and destination ATAs + let source_owner = payer.pubkey(); + let dest_owner = Pubkey::new_unique(); + + let (source_ata, _) = derive_token_ata(&source_owner, &mint); + let (dest_ata, _) = derive_token_ata(&dest_owner, &mint); + + CreateAta { + mint, + owner: source_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + CreateAta { + mint, + owner: dest_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens to source + let mint_amount = 1000u64; + MintTo { + mint, + destination: source_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Transfer full balance + Transfer { + source: source_ata, + destination: dest_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify source account (zero balance) + let source_data = rpc.get_account(source_ata).await.unwrap().unwrap(); + let source_state = Token::deserialize(&mut &source_data.data[..]).unwrap(); + let expected_source = get_expected_token(&source_state, mint, source_owner, 0, None, 0); + assert_eq!(source_state, expected_source); + + // Verify destination account + let dest_data = rpc.get_account(dest_ata).await.unwrap().unwrap(); + let dest_state = Token::deserialize(&mut &dest_data.data[..]).unwrap(); + let expected_dest = get_expected_token(&dest_state, mint, dest_owner, mint_amount, None, 0); + assert_eq!(dest_state, expected_dest); +} diff --git a/sdk-tests/token-client-test/tests/test_transfer_checked.rs b/sdk-tests/token-client-test/tests/test_transfer_checked.rs new file mode 100644 index 0000000000..070e3dcf04 --- /dev/null +++ b/sdk-tests/token-client-test/tests/test_transfer_checked.rs @@ -0,0 +1,206 @@ +//! Tests for the transfer_checked action in light-token-client. + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::instruction::derive_token_ata; +use light_token_client::actions::{CreateAta, CreateMint, MintTo, TransferChecked}; +use light_token_interface::state::{AccountState, Token}; +use solana_sdk::{pubkey::Pubkey, signer::Signer}; + +fn get_expected_token( + actual: &Token, + mint: Pubkey, + owner: Pubkey, + amount: u64, + delegate: Option, + delegated_amount: u64, +) -> Token { + Token { + mint: mint.to_bytes().into(), + owner: owner.to_bytes().into(), + amount, + delegate: delegate.map(|d| d.to_bytes().into()), + state: AccountState::Initialized, + is_native: None, + delegated_amount, + close_authority: None, + account_type: actual.account_type, + extensions: actual.extensions.clone(), + } +} + +/// Test transfer_checked with correct decimals. +#[tokio::test] +async fn test_transfer_checked_basic() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create source and destination ATAs + let source_owner = payer.pubkey(); + let dest_owner = Pubkey::new_unique(); + + let (source_ata, _) = derive_token_ata(&source_owner, &mint); + let (dest_ata, _) = derive_token_ata(&dest_owner, &mint); + + CreateAta { + mint, + owner: source_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + CreateAta { + mint, + owner: dest_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens to source + let mint_amount = 1000u64; + MintTo { + mint, + destination: source_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Transfer with checked decimals + let transfer_amount = 500u64; + TransferChecked { + source: source_ata, + mint, + destination: dest_ata, + amount: transfer_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify source account + let source_data = rpc.get_account(source_ata).await.unwrap().unwrap(); + let source_state = Token::deserialize(&mut &source_data.data[..]).unwrap(); + let expected_source = get_expected_token( + &source_state, + mint, + source_owner, + mint_amount - transfer_amount, + None, + 0, + ); + assert_eq!(source_state, expected_source); + + // Verify destination account + let dest_data = rpc.get_account(dest_ata).await.unwrap().unwrap(); + let dest_state = Token::deserialize(&mut &dest_data.data[..]).unwrap(); + let expected_dest = get_expected_token(&dest_state, mint, dest_owner, transfer_amount, None, 0); + assert_eq!(dest_state, expected_dest); +} + +/// Test transfer_checked with different decimals token. +#[tokio::test] +async fn test_transfer_checked_different_decimals() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + // Use 6 decimals (like USDC) + let decimals = 6u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create source and destination ATAs + let source_owner = payer.pubkey(); + let dest_owner = Pubkey::new_unique(); + + let (source_ata, _) = derive_token_ata(&source_owner, &mint); + let (dest_ata, _) = derive_token_ata(&dest_owner, &mint); + + CreateAta { + mint, + owner: source_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + CreateAta { + mint, + owner: dest_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens to source (1000 tokens with 6 decimals = 1_000_000_000 base units) + let mint_amount = 1_000_000_000u64; + MintTo { + mint, + destination: source_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Transfer 500 tokens (500_000_000 base units) + let transfer_amount = 500_000_000u64; + TransferChecked { + source: source_ata, + mint, + destination: dest_ata, + amount: transfer_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify source account + let source_data = rpc.get_account(source_ata).await.unwrap().unwrap(); + let source_state = Token::deserialize(&mut &source_data.data[..]).unwrap(); + let expected_source = get_expected_token( + &source_state, + mint, + source_owner, + mint_amount - transfer_amount, + None, + 0, + ); + assert_eq!(source_state, expected_source); + + // Verify destination account + let dest_data = rpc.get_account(dest_ata).await.unwrap().unwrap(); + let dest_state = Token::deserialize(&mut &dest_data.data[..]).unwrap(); + let expected_dest = get_expected_token(&dest_state, mint, dest_owner, transfer_amount, None, 0); + assert_eq!(dest_state, expected_dest); +} diff --git a/sdk-tests/token-client-test/tests/test_transfer_interface.rs b/sdk-tests/token-client-test/tests/test_transfer_interface.rs new file mode 100644 index 0000000000..a5f8d0cba8 --- /dev/null +++ b/sdk-tests/token-client-test/tests/test_transfer_interface.rs @@ -0,0 +1,215 @@ +//! Tests for the transfer_interface action in light-token-client. + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::instruction::derive_token_ata; +use light_token_client::actions::{CreateAta, CreateMint, MintTo, TransferInterface}; +use light_token_interface::state::{AccountState, Token}; +use solana_sdk::{pubkey::Pubkey, signer::Signer}; + +fn get_expected_token( + actual: &Token, + mint: Pubkey, + owner: Pubkey, + amount: u64, + delegate: Option, + delegated_amount: u64, +) -> Token { + Token { + mint: mint.to_bytes().into(), + owner: owner.to_bytes().into(), + amount, + delegate: delegate.map(|d| d.to_bytes().into()), + state: AccountState::Initialized, + is_native: None, + delegated_amount, + close_authority: None, + account_type: actual.account_type, + extensions: actual.extensions.clone(), + } +} + +/// Test transfer_interface for Light -> Light transfer. +#[tokio::test] +async fn test_transfer_interface_light_to_light() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create source and destination ATAs + let source_owner = payer.pubkey(); + let dest_owner = Pubkey::new_unique(); + + let (source_ata, _) = derive_token_ata(&source_owner, &mint); + let (dest_ata, _) = derive_token_ata(&dest_owner, &mint); + + CreateAta { + mint, + owner: source_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + CreateAta { + mint, + owner: dest_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens to source + let mint_amount = 1000u64; + MintTo { + mint, + destination: source_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Transfer using interface (Light -> Light, no SPL token program needed) + let transfer_amount = 500u64; + TransferInterface { + source: source_ata, + mint, + destination: dest_ata, + amount: transfer_amount, + decimals, + spl_token_program: None, // No SPL token program needed for Light -> Light + restricted: false, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify source account + let source_data = rpc.get_account(source_ata).await.unwrap().unwrap(); + let source_state = Token::deserialize(&mut &source_data.data[..]).unwrap(); + let expected_source = get_expected_token( + &source_state, + mint, + source_owner, + mint_amount - transfer_amount, + None, + 0, + ); + assert_eq!(source_state, expected_source); + + // Verify destination account + let dest_data = rpc.get_account(dest_ata).await.unwrap().unwrap(); + let dest_state = Token::deserialize(&mut &dest_data.data[..]).unwrap(); + let expected_dest = get_expected_token(&dest_state, mint, dest_owner, transfer_amount, None, 0); + assert_eq!(dest_state, expected_dest); +} + +/// Test transfer_interface for multiple Light -> Light transfers. +#[tokio::test] +async fn test_transfer_interface_multiple_transfers() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = 9u8; + + // Create mint + let (_, mint) = CreateMint { + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Create source and destination ATAs + let source_owner = payer.pubkey(); + let dest_owner = Pubkey::new_unique(); + + let (source_ata, _) = derive_token_ata(&source_owner, &mint); + let (dest_ata, _) = derive_token_ata(&dest_owner, &mint); + + CreateAta { + mint, + owner: source_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + CreateAta { + mint, + owner: dest_owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Mint tokens to source + let mint_amount = 1000u64; + MintTo { + mint, + destination: source_ata, + amount: mint_amount, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Transfer 300 + TransferInterface { + source: source_ata, + mint, + destination: dest_ata, + amount: 300, + decimals, + spl_token_program: None, + restricted: false, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Transfer 200 + TransferInterface { + source: source_ata, + mint, + destination: dest_ata, + amount: 200, + decimals, + spl_token_program: None, + restricted: false, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify source account (1000 - 300 - 200 = 500) + let source_data = rpc.get_account(source_ata).await.unwrap().unwrap(); + let source_state = Token::deserialize(&mut &source_data.data[..]).unwrap(); + let expected_source = get_expected_token(&source_state, mint, source_owner, 500, None, 0); + assert_eq!(source_state, expected_source); + + // Verify destination account (300 + 200 = 500) + let dest_data = rpc.get_account(dest_ata).await.unwrap().unwrap(); + let dest_state = Token::deserialize(&mut &dest_data.data[..]).unwrap(); + let expected_dest = get_expected_token(&dest_state, mint, dest_owner, 500, None, 0); + assert_eq!(dest_state, expected_dest); +} diff --git a/sdk-tests/token-client-test/tests/test_wrap_unwrap.rs b/sdk-tests/token-client-test/tests/test_wrap_unwrap.rs new file mode 100644 index 0000000000..aa5a212f7e --- /dev/null +++ b/sdk-tests/token-client-test/tests/test_wrap_unwrap.rs @@ -0,0 +1,328 @@ +//! Tests for the wrap and unwrap actions in light-token-client. +//! +//! These tests verify: +//! - Wrapping SPL tokens into Light Token accounts +//! - Unwrapping Light Tokens back to SPL token accounts + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_test_utils::spl::{ + create_mint_helper, create_token_account, mint_spl_tokens, CREATE_MINT_HELPER_DECIMALS, +}; +use light_token::instruction::derive_token_ata; +use light_token_client::actions::{CreateAta, Unwrap, Wrap}; +use light_token_interface::state::Token; +use solana_sdk::{program_pack::Pack, signature::Keypair, signer::Signer}; +use spl_token::state::Account as SplTokenAccount; + +/// Test wrapping SPL tokens into a Light Token account. +#[tokio::test] +async fn test_wrap_basic() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = CREATE_MINT_HELPER_DECIMALS; + + // Create SPL mint + let mint = create_mint_helper(&mut rpc, &payer).await; + + // Create SPL token account for payer + let spl_token_account = Keypair::new(); + create_token_account(&mut rpc, &mint, &spl_token_account, &payer) + .await + .unwrap(); + + // Mint SPL tokens + let mint_amount = 1000u64; + mint_spl_tokens( + &mut rpc, + &mint, + &spl_token_account.pubkey(), + &payer.pubkey(), + &payer, + mint_amount, + false, + ) + .await + .unwrap(); + + // Create Light Token ATA for destination + let owner = payer.pubkey(); + let (light_token_ata, _) = derive_token_ata(&owner, &mint); + + CreateAta { + mint, + owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Wrap SPL tokens to Light Token + let wrap_amount = 500u64; + Wrap { + source_spl_ata: spl_token_account.pubkey(), + destination: light_token_ata, + mint, + amount: wrap_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify Light Token balance + let light_token_data = rpc.get_account(light_token_ata).await.unwrap().unwrap(); + let light_token_state = Token::deserialize(&mut &light_token_data.data[..]).unwrap(); + assert_eq!(light_token_state.amount, wrap_amount); + + // Verify SPL token balance decreased + let spl_token_data = rpc + .get_account(spl_token_account.pubkey()) + .await + .unwrap() + .unwrap(); + let spl_state = SplTokenAccount::unpack(&spl_token_data.data).unwrap(); + assert_eq!(spl_state.amount, mint_amount - wrap_amount); +} + +/// Test unwrapping Light Tokens back to SPL tokens. +#[tokio::test] +async fn test_unwrap_basic() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = CREATE_MINT_HELPER_DECIMALS; + + // Create SPL mint + let mint = create_mint_helper(&mut rpc, &payer).await; + + // Create SPL token account + let spl_token_account = Keypair::new(); + create_token_account(&mut rpc, &mint, &spl_token_account, &payer) + .await + .unwrap(); + + // Mint SPL tokens + let mint_amount = 1000u64; + mint_spl_tokens( + &mut rpc, + &mint, + &spl_token_account.pubkey(), + &payer.pubkey(), + &payer, + mint_amount, + false, + ) + .await + .unwrap(); + + // Create Light Token ATA + let owner = payer.pubkey(); + let (light_token_ata, _) = derive_token_ata(&owner, &mint); + + CreateAta { + mint, + owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Wrap all SPL tokens to Light Token first + Wrap { + source_spl_ata: spl_token_account.pubkey(), + destination: light_token_ata, + mint, + amount: mint_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify Light Token has all tokens + let light_token_data = rpc.get_account(light_token_ata).await.unwrap().unwrap(); + let light_token_state = Token::deserialize(&mut &light_token_data.data[..]).unwrap(); + assert_eq!(light_token_state.amount, mint_amount); + + // Unwrap some tokens back to SPL + let unwrap_amount = 300u64; + Unwrap { + source: light_token_ata, + destination_spl_ata: spl_token_account.pubkey(), + mint, + amount: unwrap_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify Light Token balance decreased + let light_token_data = rpc.get_account(light_token_ata).await.unwrap().unwrap(); + let light_token_state = Token::deserialize(&mut &light_token_data.data[..]).unwrap(); + assert_eq!(light_token_state.amount, mint_amount - unwrap_amount); + + // Verify SPL token balance increased + let spl_token_data = rpc + .get_account(spl_token_account.pubkey()) + .await + .unwrap() + .unwrap(); + let spl_state = SplTokenAccount::unpack(&spl_token_data.data).unwrap(); + assert_eq!(spl_state.amount, unwrap_amount); +} + +/// Test wrap and unwrap round trip. +#[tokio::test] +async fn test_wrap_unwrap_round_trip() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = CREATE_MINT_HELPER_DECIMALS; + + // Create SPL mint + let mint = create_mint_helper(&mut rpc, &payer).await; + + // Create SPL token account + let spl_token_account = Keypair::new(); + create_token_account(&mut rpc, &mint, &spl_token_account, &payer) + .await + .unwrap(); + + // Mint SPL tokens + let mint_amount = 1000u64; + mint_spl_tokens( + &mut rpc, + &mint, + &spl_token_account.pubkey(), + &payer.pubkey(), + &payer, + mint_amount, + false, + ) + .await + .unwrap(); + + // Create Light Token ATA + let owner = payer.pubkey(); + let (light_token_ata, _) = derive_token_ata(&owner, &mint); + + CreateAta { + mint, + owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Wrap all tokens + Wrap { + source_spl_ata: spl_token_account.pubkey(), + destination: light_token_ata, + mint, + amount: mint_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Unwrap all tokens back + Unwrap { + source: light_token_ata, + destination_spl_ata: spl_token_account.pubkey(), + mint, + amount: mint_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify Light Token has 0 balance + let light_token_data = rpc.get_account(light_token_ata).await.unwrap().unwrap(); + let light_token_state = Token::deserialize(&mut &light_token_data.data[..]).unwrap(); + assert_eq!(light_token_state.amount, 0); + + // Verify SPL token has original balance + let spl_token_data = rpc + .get_account(spl_token_account.pubkey()) + .await + .unwrap() + .unwrap(); + let spl_state = SplTokenAccount::unpack(&spl_token_data.data).unwrap(); + assert_eq!(spl_state.amount, mint_amount); +} + +/// Test wrapping with large amounts. +#[tokio::test] +async fn test_wrap_large_amount() { + let config = ProgramTestConfig::new_v2(true, None); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let decimals = CREATE_MINT_HELPER_DECIMALS; + + // Create SPL mint + let mint = create_mint_helper(&mut rpc, &payer).await; + + // Create SPL token account + let spl_token_account = Keypair::new(); + create_token_account(&mut rpc, &mint, &spl_token_account, &payer) + .await + .unwrap(); + + // Mint large amount of SPL tokens + let mint_amount = 1_000_000_000u64; + mint_spl_tokens( + &mut rpc, + &mint, + &spl_token_account.pubkey(), + &payer.pubkey(), + &payer, + mint_amount, + false, + ) + .await + .unwrap(); + + // Create Light Token ATA + let owner = payer.pubkey(); + let (light_token_ata, _) = derive_token_ata(&owner, &mint); + + CreateAta { + mint, + owner, + idempotent: false, + } + .execute(&mut rpc, &payer) + .await + .unwrap(); + + // Wrap half the tokens + let wrap_amount = 500_000_000u64; + Wrap { + source_spl_ata: spl_token_account.pubkey(), + destination: light_token_ata, + mint, + amount: wrap_amount, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await + .unwrap(); + + // Verify Light Token balance + let light_token_data = rpc.get_account(light_token_ata).await.unwrap().unwrap(); + let light_token_state = Token::deserialize(&mut &light_token_data.data[..]).unwrap(); + assert_eq!(light_token_state.amount, wrap_amount); +} From 5a405743286c85b4d5bb62109f210180239062a5 Mon Sep 17 00:00:00 2001 From: ananas Date: Fri, 23 Jan 2026 20:49:02 +0000 Subject: [PATCH 2/4] fix: wrap unwrap for any spl program, add check in approve --- sdk-libs/token-client/src/actions/approve.rs | 14 +++++++++ sdk-libs/token-client/src/actions/unwrap.rs | 30 +++++++++++++++++--- sdk-libs/token-client/src/actions/wrap.rs | 27 +++++++++++++++--- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/sdk-libs/token-client/src/actions/approve.rs b/sdk-libs/token-client/src/actions/approve.rs index 875b19632d..547acb1b1d 100644 --- a/sdk-libs/token-client/src/actions/approve.rs +++ b/sdk-libs/token-client/src/actions/approve.rs @@ -96,12 +96,26 @@ impl Approve { /// /// # Returns /// `Result` - The transaction signature + /// + /// # Errors + /// Returns an error if `self.owner` is `Some` and does not equal `owner.pubkey()`. pub async fn execute_with_owner( self, rpc: &mut R, payer: &Keypair, owner: &Keypair, ) -> Result { + // Guard: if self.owner is set, it must match the provided owner keypair + if let Some(expected_owner) = self.owner { + if expected_owner != owner.pubkey() { + return Err(RpcError::CustomError(format!( + "owner mismatch: self.owner ({}) does not match owner.pubkey() ({})", + expected_owner, + owner.pubkey() + ))); + } + } + let ix = ApproveInstruction { token_account: self.token_account, delegate: self.delegate, diff --git a/sdk-libs/token-client/src/actions/unwrap.rs b/sdk-libs/token-client/src/actions/unwrap.rs index 72af63de42..db92a315a8 100644 --- a/sdk-libs/token-client/src/actions/unwrap.rs +++ b/sdk-libs/token-client/src/actions/unwrap.rs @@ -4,8 +4,9 @@ use light_client::rpc::{Rpc, RpcError}; use light_token::{ - constants::SPL_TOKEN_PROGRAM_ID, - instruction::{get_spl_interface_pda_and_bump, TransferToSpl}, + constants::SPL_TOKEN_2022_PROGRAM_ID, + instruction::TransferToSpl, + spl_interface::{find_spl_interface_pda, has_restricted_extensions}, }; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -56,7 +57,28 @@ impl Unwrap { payer: &Keypair, authority: &Keypair, ) -> Result { - let (spl_interface_pda, bump) = get_spl_interface_pda_and_bump(&self.mint); + // Get the destination account to determine the token program + let destination_account_info = rpc + .get_account(self.destination_spl_ata) + .await? + .ok_or_else(|| { + RpcError::CustomError("Destination SPL token account not found".to_string()) + })?; + + let spl_token_program = destination_account_info.owner; + + // Check for restricted extensions if using Token-2022 + let restricted = if spl_token_program == SPL_TOKEN_2022_PROGRAM_ID { + let mint_account = rpc + .get_account(self.mint) + .await? + .ok_or_else(|| RpcError::CustomError("Mint account not found".to_string()))?; + has_restricted_extensions(&mint_account.data) + } else { + false + }; + + let (spl_interface_pda, bump) = find_spl_interface_pda(&self.mint, restricted); let ix = TransferToSpl { source: self.source, @@ -68,7 +90,7 @@ impl Unwrap { spl_interface_pda, spl_interface_pda_bump: bump, decimals: self.decimals, - spl_token_program: SPL_TOKEN_PROGRAM_ID, + spl_token_program, } .instruction() .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; diff --git a/sdk-libs/token-client/src/actions/wrap.rs b/sdk-libs/token-client/src/actions/wrap.rs index 89ca53ca82..200076958a 100644 --- a/sdk-libs/token-client/src/actions/wrap.rs +++ b/sdk-libs/token-client/src/actions/wrap.rs @@ -4,8 +4,9 @@ use light_client::rpc::{Rpc, RpcError}; use light_token::{ - constants::SPL_TOKEN_PROGRAM_ID, - instruction::{get_spl_interface_pda_and_bump, TransferFromSpl}, + constants::SPL_TOKEN_2022_PROGRAM_ID, + instruction::TransferFromSpl, + spl_interface::{find_spl_interface_pda, has_restricted_extensions}, }; use solana_keypair::Keypair; use solana_pubkey::Pubkey; @@ -57,7 +58,25 @@ impl Wrap { payer: &Keypair, authority: &Keypair, ) -> Result { - let (spl_interface_pda, bump) = get_spl_interface_pda_and_bump(&self.mint); + // Get the source account to determine the token program + let source_account_info = rpc.get_account(self.source_spl_ata).await?.ok_or_else(|| { + RpcError::CustomError("Source SPL token account not found".to_string()) + })?; + + let spl_token_program = source_account_info.owner; + + // Check for restricted extensions if using Token-2022 + let restricted = if spl_token_program == SPL_TOKEN_2022_PROGRAM_ID { + let mint_account = rpc + .get_account(self.mint) + .await? + .ok_or_else(|| RpcError::CustomError("Mint account not found".to_string()))?; + has_restricted_extensions(&mint_account.data) + } else { + false + }; + + let (spl_interface_pda, bump) = find_spl_interface_pda(&self.mint, restricted); let ix = TransferFromSpl { amount: self.amount, @@ -69,7 +88,7 @@ impl Wrap { mint: self.mint, payer: payer.pubkey(), spl_interface_pda, - spl_token_program: SPL_TOKEN_PROGRAM_ID, + spl_token_program, } .instruction() .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?; From e73e5015b51909e94080add4cf421f514a6cf688 Mon Sep 17 00:00:00 2001 From: ananas Date: Fri, 23 Jan 2026 22:26:33 +0000 Subject: [PATCH 3/4] fix: remove light-test-utils from forester, fix actions --- Cargo.lock | 3 + forester-utils/Cargo.toml | 3 + .../instructions/compress_and_close_mint.rs | 118 ++++++++++++++++++ forester-utils/src/instructions/mod.rs | 3 + forester/Cargo.toml | 1 - forester/src/compressible/mint/compressor.rs | 65 +++++----- scripts/check-dependency-constraints.sh | 17 +++ sdk-libs/token-client/src/actions/unwrap.rs | 16 ++- sdk-libs/token-client/src/actions/wrap.rs | 16 ++- 9 files changed, 203 insertions(+), 39 deletions(-) create mode 100644 forester-utils/src/instructions/compress_and_close_mint.rs diff --git a/Cargo.lock b/Cargo.lock index b53dd361f8..2527e72705 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2364,11 +2364,14 @@ dependencies = [ "anchor-lang", "async-trait", "bb8", + "borsh 0.10.4", "governor 0.8.1", "light-account-checks", "light-batched-merkle-tree", "light-client", "light-compressed-account", + "light-compressed-token-sdk", + "light-compressible", "light-concurrent-merkle-tree", "light-hash-set", "light-hasher", diff --git a/forester-utils/Cargo.toml b/forester-utils/Cargo.toml index 72c92b7e64..b03aa100c3 100644 --- a/forester-utils/Cargo.toml +++ b/forester-utils/Cargo.toml @@ -28,6 +28,9 @@ light-prover-client = { workspace = true } light-registry = { workspace = true, features = ["cpi"] } account-compression = { workspace = true, features = ["cpi"] } light-token-interface = { workspace = true } +light-compressed-token-sdk = { workspace = true } +light-compressible = { workspace = true } +borsh = { workspace = true } solana-instruction = { workspace = true } solana-pubkey = { workspace = true } diff --git a/forester-utils/src/instructions/compress_and_close_mint.rs b/forester-utils/src/instructions/compress_and_close_mint.rs new file mode 100644 index 0000000000..4dcd3c2639 --- /dev/null +++ b/forester-utils/src/instructions/compress_and_close_mint.rs @@ -0,0 +1,118 @@ +use borsh::BorshDeserialize; +use light_client::{ + indexer::Indexer, + rpc::{Rpc, RpcError}, +}; +use light_compressed_account::instruction_data::traits::LightInstructionData; +use light_compressed_token_sdk::compressed_token::{ + create_compressed_mint::find_mint_address, mint_action::MintActionMetaConfig, +}; +use light_compressible::config::CompressibleConfig; +use light_token_interface::{ + instructions::mint_action::{ + CompressAndCloseMintAction, MintActionCompressedInstructionData, MintWithContext, + }, + state::Mint, + LIGHT_TOKEN_PROGRAM_ID, +}; +use solana_instruction::Instruction; +use solana_pubkey::Pubkey; + +/// Creates a CompressAndCloseMint instruction by fetching required data from RPC/indexer. +/// +/// This is permissionless - anyone can call when the mint is compressible (rent expired). +/// +/// # Parameters +/// - `rpc`: RPC client that also implements Indexer +/// - `payer`: Account paying for transaction fees +/// - `compressed_mint_address`: The 32-byte compressed address of the mint +/// - `mint_seed`: The seed pubkey used to derive the mint PDA +/// - `idempotent`: If true, succeed silently when Mint doesn't exist +pub async fn create_compress_and_close_mint_instruction( + rpc: &mut R, + payer: Pubkey, + compressed_mint_address: [u8; 32], + mint_seed: Pubkey, + idempotent: bool, +) -> Result { + // Derive the mint PDA from mint_seed + let (mint_pda, _bump) = find_mint_address(&mint_seed); + + // Get the compressed mint account + let compressed_mint_account = rpc + .get_compressed_account(compressed_mint_address, None) + .await? + .value + .ok_or_else(|| RpcError::AccountDoesNotExist(format!("{:?}", compressed_mint_address)))?; + + // Try to deserialize the compressed mint - may be None if Mint is already decompressed + let compressed_mint: Option = compressed_mint_account + .data + .as_ref() + .and_then(|d| BorshDeserialize::deserialize(&mut d.data.as_slice()).ok()); + + // Get validity proof for the compressed mint + let rpc_proof_result = rpc + .get_validity_proof(vec![compressed_mint_account.hash], vec![], None) + .await? + .value; + + // Build MintWithContext + let compressed_mint_inputs = MintWithContext { + prove_by_index: rpc_proof_result.accounts[0].root_index.proof_by_index(), + leaf_index: compressed_mint_account.leaf_index, + root_index: rpc_proof_result.accounts[0] + .root_index + .root_index() + .unwrap_or_default(), + address: compressed_mint_address, + mint: compressed_mint.map(|m| m.try_into().unwrap()), + }; + + // Build instruction data with CompressAndCloseMint action + let instruction_data = MintActionCompressedInstructionData::new( + compressed_mint_inputs, + rpc_proof_result.proof.into(), + ) + .with_compress_and_close_mint(CompressAndCloseMintAction { + idempotent: if idempotent { 1 } else { 0 }, + }); + + // Get CompressibleConfig for rent_sponsor + let config_address = CompressibleConfig::light_token_v1_config_pda(); + let compressible_config: CompressibleConfig = rpc + .get_anchor_account(&config_address) + .await? + .ok_or_else(|| { + RpcError::CustomError(format!( + "CompressibleConfig not found at {}", + config_address + )) + })?; + + // Build account metas configuration + let state_tree_info = rpc_proof_result.accounts[0].tree_info; + let config = MintActionMetaConfig::new( + payer, + payer, // authority - permissionless, using payer + state_tree_info.tree, + state_tree_info.queue, + state_tree_info.queue, + ) + .with_compressible_mint(mint_pda, config_address, compressible_config.rent_sponsor); + + // Get account metas + let account_metas = config.to_account_metas(); + + // Serialize instruction data + let data = instruction_data + .data() + .map_err(|e| RpcError::CustomError(format!("Failed to serialize instruction: {:?}", e)))?; + + // Build final instruction + Ok(Instruction { + program_id: LIGHT_TOKEN_PROGRAM_ID.into(), + accounts: account_metas, + data, + }) +} diff --git a/forester-utils/src/instructions/mod.rs b/forester-utils/src/instructions/mod.rs index 4173dcc027..493e2d3eca 100644 --- a/forester-utils/src/instructions/mod.rs +++ b/forester-utils/src/instructions/mod.rs @@ -3,4 +3,7 @@ pub mod create_account; pub use create_account::create_account_instruction; pub mod claim; +pub mod compress_and_close_mint; pub mod withdraw_funding_pool; + +pub use compress_and_close_mint::create_compress_and_close_mint_instruction; diff --git a/forester/Cargo.toml b/forester/Cargo.toml index c648a54156..5b23dc2855 100644 --- a/forester/Cargo.toml +++ b/forester/Cargo.toml @@ -31,7 +31,6 @@ light-compressible = { workspace = true, default-features = false, features = [" light-token-interface = { workspace = true } light-token-client = { workspace = true } light-token = { workspace = true } -light-test-utils = { workspace = true } light-compressed-token-sdk = { workspace = true } solana-rpc-client-api = { workspace = true } solana-transaction-status = { workspace = true } diff --git a/forester/src/compressible/mint/compressor.rs b/forester/src/compressible/mint/compressor.rs index db3d4bd6d2..1c2cd6e317 100644 --- a/forester/src/compressible/mint/compressor.rs +++ b/forester/src/compressible/mint/compressor.rs @@ -3,12 +3,11 @@ use std::sync::{ Arc, }; -use forester_utils::rpc_pool::SolanaRpcPool; +use forester_utils::{ + instructions::create_compress_and_close_mint_instruction, rpc_pool::SolanaRpcPool, +}; use futures::StreamExt; use light_client::{indexer::Indexer, rpc::Rpc}; -use light_test_utils::actions::legacy::instructions::mint_action::{ - create_mint_action_instruction, MintActionParams, MintActionType, -}; use solana_sdk::{ instruction::Instruction, signature::{Keypair, Signature}, @@ -71,24 +70,21 @@ impl MintCompressor { async move { let mut rpc = rpc_pool.get_connection().await?; - let params = MintActionParams { - compressed_mint_address: compressed_address, - mint_seed, - authority: payer, + let ix = create_compress_and_close_mint_instruction( + &mut *rpc, payer, - actions: vec![MintActionType::CompressAndCloseMint { idempotent: true }], - new_mint: None, - }; - - let ix = create_mint_action_instruction(&mut *rpc, params) - .await - .map_err(|e| { - anyhow::anyhow!( - "Failed to build CompressAndCloseMint instruction for {}: {:?}", - mint_pda, - e - ) - })?; + compressed_address, + mint_seed, + true, // idempotent + ) + .await + .map_err(|e| { + anyhow::anyhow!( + "Failed to build CompressAndCloseMint instruction for {}: {:?}", + mint_pda, + e + ) + })?; Ok::(ix) } @@ -219,22 +215,19 @@ impl MintCompressor { let mut rpc = self.rpc_pool.get_connection().await?; - // Build the CompressAndCloseMint instruction using the mint action builder + // Build the CompressAndCloseMint instruction // This is idempotent - succeeds silently if mint doesn't exist or is already compressed - let params = MintActionParams { - compressed_mint_address: compressed_address, - mint_seed: *mint_seed, - authority: self.payer_keypair.pubkey(), - payer: self.payer_keypair.pubkey(), - actions: vec![MintActionType::CompressAndCloseMint { idempotent: true }], - new_mint: None, - }; - - let ix = create_mint_action_instruction(&mut *rpc, params) - .await - .map_err(|e| { - anyhow::anyhow!("Failed to build CompressAndCloseMint instruction: {:?}", e) - })?; + let ix = create_compress_and_close_mint_instruction( + &mut *rpc, + self.payer_keypair.pubkey(), + compressed_address, + *mint_seed, + true, // idempotent + ) + .await + .map_err(|e| { + anyhow::anyhow!("Failed to build CompressAndCloseMint instruction: {:?}", e) + })?; debug!( "Built CompressAndCloseMint instruction for Mint {}", diff --git a/scripts/check-dependency-constraints.sh b/scripts/check-dependency-constraints.sh index 1238aea38d..303b18e666 100755 --- a/scripts/check-dependency-constraints.sh +++ b/scripts/check-dependency-constraints.sh @@ -90,4 +90,21 @@ if [ "$CONSTRAINT_FAILED" -eq 1 ]; then exit 1 fi +# Check that no crates have light-test-utils as a regular dependency +# (dev-dependencies are allowed, --edges normal excludes them) +# Excludes: program-tests/*, sdk-tests/*, xtask (these are test/build crates) +echo "" +echo "Checking that no crates depend on light-test-utils (dev-deps allowed)..." + +# Use inverse lookup to find what depends on light-test-utils +# Skip the first line (light-test-utils itself) and filter out test crates +dependents=$(cargo tree --workspace --edges normal -i light-test-utils 2>/dev/null | tail -n +2 | grep -v "program-tests/" | grep -v "sdk-tests/" | grep -v "xtask" || true) +if [ -n "$dependents" ]; then + echo "ERROR: Found crates with light-test-utils as a regular dependency:" + echo "$dependents" + echo "" + echo "FAILED: light-test-utils should only be used as a dev-dependency." + exit 1 +fi + echo "All dependency constraints satisfied." diff --git a/sdk-libs/token-client/src/actions/unwrap.rs b/sdk-libs/token-client/src/actions/unwrap.rs index db92a315a8..1ac030c075 100644 --- a/sdk-libs/token-client/src/actions/unwrap.rs +++ b/sdk-libs/token-client/src/actions/unwrap.rs @@ -4,7 +4,7 @@ use light_client::rpc::{Rpc, RpcError}; use light_token::{ - constants::SPL_TOKEN_2022_PROGRAM_ID, + constants::{SPL_TOKEN_2022_PROGRAM_ID, SPL_TOKEN_PROGRAM_ID}, instruction::TransferToSpl, spl_interface::{find_spl_interface_pda, has_restricted_extensions}, }; @@ -67,6 +67,20 @@ impl Unwrap { let spl_token_program = destination_account_info.owner; + // Validate that the destination account is owned by a supported SPL token program + if spl_token_program != SPL_TOKEN_PROGRAM_ID + && spl_token_program != SPL_TOKEN_2022_PROGRAM_ID + { + return Err(RpcError::CustomError(format!( + "Destination SPL token account {} is owned by an unsupported program {}. \ + Expected SPL Token ({}) or Token-2022 ({}).", + self.destination_spl_ata, + destination_account_info.owner, + SPL_TOKEN_PROGRAM_ID, + SPL_TOKEN_2022_PROGRAM_ID + ))); + } + // Check for restricted extensions if using Token-2022 let restricted = if spl_token_program == SPL_TOKEN_2022_PROGRAM_ID { let mint_account = rpc diff --git a/sdk-libs/token-client/src/actions/wrap.rs b/sdk-libs/token-client/src/actions/wrap.rs index 200076958a..17a058f895 100644 --- a/sdk-libs/token-client/src/actions/wrap.rs +++ b/sdk-libs/token-client/src/actions/wrap.rs @@ -4,7 +4,7 @@ use light_client::rpc::{Rpc, RpcError}; use light_token::{ - constants::SPL_TOKEN_2022_PROGRAM_ID, + constants::{SPL_TOKEN_2022_PROGRAM_ID, SPL_TOKEN_PROGRAM_ID}, instruction::TransferFromSpl, spl_interface::{find_spl_interface_pda, has_restricted_extensions}, }; @@ -65,6 +65,20 @@ impl Wrap { let spl_token_program = source_account_info.owner; + // Validate that the source account is owned by a supported SPL token program + if spl_token_program != SPL_TOKEN_PROGRAM_ID + && spl_token_program != SPL_TOKEN_2022_PROGRAM_ID + { + return Err(RpcError::CustomError(format!( + "Source SPL token account {} is owned by an unsupported program {}. \ + Expected SPL Token ({}) or Token-2022 ({}).", + self.source_spl_ata, + source_account_info.owner, + SPL_TOKEN_PROGRAM_ID, + SPL_TOKEN_2022_PROGRAM_ID + ))); + } + // Check for restricted extensions if using Token-2022 let restricted = if spl_token_program == SPL_TOKEN_2022_PROGRAM_ID { let mint_account = rpc From d07db073054ec3a9bf1b6f47f341db62372bd1b4 Mon Sep 17 00:00:00 2001 From: ananas Date: Sat, 24 Jan 2026 20:12:15 +0000 Subject: [PATCH 4/4] chore: remove new ata named functions --- .../token-client/src/actions/create_ata.rs | 32 ++----------------- sdk-libs/token-client/src/actions/mod.rs | 6 +++- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/sdk-libs/token-client/src/actions/create_ata.rs b/sdk-libs/token-client/src/actions/create_ata.rs index 8d5838cf10..57ab4e0bb2 100644 --- a/sdk-libs/token-client/src/actions/create_ata.rs +++ b/sdk-libs/token-client/src/actions/create_ata.rs @@ -3,9 +3,7 @@ //! These actions provide clean interfaces for creating Light Token ATAs. use light_client::rpc::{Rpc, RpcError}; -use light_token::instruction::{ - derive_associated_token_account, get_associated_token_address, CreateAssociatedTokenAccount, -}; +use light_token::instruction::{get_associated_token_address, CreateAssociatedTokenAccount}; use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signature::Signature; @@ -68,32 +66,6 @@ impl CreateAta { .create_and_send_transaction(&[ix], &payer.pubkey(), &[payer]) .await?; - Ok((signature, get_ata_address(&self.mint, &self.owner))) + Ok((signature, get_associated_token_address(&self.owner, &self.mint))) } } - -/// Get the associated token address for a given owner and mint. -/// -/// This is a pure function that computes the ATA address without any RPC calls. -/// -/// # Arguments -/// * `mint` - The mint public key -/// * `owner` - The owner public key -/// -/// # Returns -/// `Pubkey` - The ATA address -pub fn get_ata_address(mint: &Pubkey, owner: &Pubkey) -> Pubkey { - get_associated_token_address(owner, mint) -} - -/// Derive the associated token address with bump seed. -/// -/// # Arguments -/// * `mint` - The mint public key -/// * `owner` - The owner public key -/// -/// # Returns -/// `(Pubkey, u8)` - The ATA address and bump seed -pub fn derive_ata_address(mint: &Pubkey, owner: &Pubkey) -> (Pubkey, u8) { - derive_associated_token_account(owner, mint) -} diff --git a/sdk-libs/token-client/src/actions/mod.rs b/sdk-libs/token-client/src/actions/mod.rs index 7b902fb8f8..c4ba800d60 100644 --- a/sdk-libs/token-client/src/actions/mod.rs +++ b/sdk-libs/token-client/src/actions/mod.rs @@ -25,7 +25,11 @@ pub mod wrap; // Re-export all action structs pub use approve::Approve; -pub use create_ata::{derive_ata_address, get_ata_address, CreateAta}; +pub use create_ata::CreateAta; +pub use light_token::instruction::{ + derive_associated_token_account, get_associated_token_address, + get_associated_token_address_and_bump, +}; pub use create_mint::{CreateMint, TokenMetadata}; pub use mint_to::MintTo; pub use revoke::Revoke;