diff --git a/rust/chains/tw_solana/src/defined_addresses.rs b/rust/chains/tw_solana/src/defined_addresses.rs index 4047605b435..f7e663ef1f4 100644 --- a/rust/chains/tw_solana/src/defined_addresses.rs +++ b/rust/chains/tw_solana/src/defined_addresses.rs @@ -16,6 +16,7 @@ macro_rules! define { define!(SYSTEM_PROGRAM_ID_ADDRESS = "11111111111111111111111111111111"); define!(STAKE_PROGRAM_ID_ADDRESS = "Stake11111111111111111111111111111111111111"); define!(TOKEN_PROGRAM_ID_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); +define!(TOKEN_2022_PROGRAM_ID_ADDRESS = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); define!(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); define!(SYSVAR_RENT_ID_ADDRESS = "SysvarRent111111111111111111111111111111111"); define!(SYSVAR_CLOCK_ID_ADDRESS = "SysvarC1ock11111111111111111111111111111111"); diff --git a/rust/chains/tw_solana/src/modules/instruction_builder/token_instruction.rs b/rust/chains/tw_solana/src/modules/instruction_builder/token_instruction.rs index 8cfb3248bad..cc5a47957e3 100644 --- a/rust/chains/tw_solana/src/modules/instruction_builder/token_instruction.rs +++ b/rust/chains/tw_solana/src/modules/instruction_builder/token_instruction.rs @@ -74,6 +74,7 @@ impl TokenInstructionBuilder { other_main_pubkey: SolanaAddress, token_mint_pubkey: SolanaAddress, token_pubkey: SolanaAddress, + token_program_id: SolanaAddress, ) -> Instruction { let account_metas = vec![ AccountMeta::new(funding_pubkey, true), @@ -81,7 +82,7 @@ impl TokenInstructionBuilder { AccountMeta::readonly(other_main_pubkey, false), AccountMeta::readonly(token_mint_pubkey, false), AccountMeta::readonly(*SYSTEM_PROGRAM_ID_ADDRESS, false), - AccountMeta::readonly(*TOKEN_PROGRAM_ID_ADDRESS, false), + AccountMeta::readonly(token_program_id, false), AccountMeta::readonly(*SYSVAR_RENT_ID_ADDRESS, false), ]; let data = Data::default(); @@ -96,6 +97,7 @@ impl TokenInstructionBuilder { signer: SolanaAddress, amount: u64, decimals: u8, + token_program_id: SolanaAddress, ) -> Instruction { let account_metas = vec![ AccountMeta::new(sender_token_pubkey, false), @@ -105,6 +107,6 @@ impl TokenInstructionBuilder { ]; let data = TokenInstruction::TransferChecked { amount, decimals }.pack(); - Instruction::new(*TOKEN_PROGRAM_ID_ADDRESS, data, account_metas) + Instruction::new(token_program_id, data, account_metas) } } diff --git a/rust/chains/tw_solana/src/modules/message_builder.rs b/rust/chains/tw_solana/src/modules/message_builder.rs index 0ce11d8b67a..c4852154b1f 100644 --- a/rust/chains/tw_solana/src/modules/message_builder.rs +++ b/rust/chains/tw_solana/src/modules/message_builder.rs @@ -4,6 +4,7 @@ use crate::address::SolanaAddress; use crate::blockhash::Blockhash; +use crate::defined_addresses::{TOKEN_2022_PROGRAM_ID_ADDRESS, TOKEN_PROGRAM_ID_ADDRESS}; use crate::instruction::Instruction; use crate::modules::compiled_instructions::compile_instructions; use crate::modules::compiled_keys::CompiledKeys; @@ -333,6 +334,7 @@ impl<'a> MessageBuilder<'a> { other_main_address, token_mint_address, token_address, + match_program_id(create_token_acc.token_program_id), ); let mut builder = InstructionBuilder::default(); builder @@ -370,7 +372,6 @@ impl<'a> MessageBuilder<'a> { .context("Invalid token decimals. Expected lower than 256")?; let references = Self::parse_references(&token_transfer.references)?; - let transfer_instruction = TokenInstructionBuilder::transfer_checked( sender_token_address, token_mint_address, @@ -378,6 +379,7 @@ impl<'a> MessageBuilder<'a> { signer, token_transfer.amount, decimals, + match_program_id(token_transfer.token_program_id), ) .with_references(references); @@ -432,6 +434,7 @@ impl<'a> MessageBuilder<'a> { recipient_main_address, token_mint_address, recipient_token_address, + match_program_id(create_and_transfer.token_program_id), ); let transfer_instruction = TokenInstructionBuilder::transfer_checked( sender_token_address, @@ -440,6 +443,7 @@ impl<'a> MessageBuilder<'a> { signer, create_and_transfer.amount, decimals, + match_program_id(create_and_transfer.token_program_id), ) .with_references(references); @@ -769,3 +773,10 @@ where { u8::try_from(num).tw_err(|_| SigningErrorType::Error_tx_too_big) } + +fn match_program_id(program_id: Proto::TokenProgramId) -> SolanaAddress { + match program_id { + Proto::TokenProgramId::TokenProgram => *TOKEN_PROGRAM_ID_ADDRESS, + Proto::TokenProgramId::Token2022Program => *TOKEN_2022_PROGRAM_ID_ADDRESS, + } +} diff --git a/rust/tw_any_coin/tests/chains/solana/solana_sign.rs b/rust/tw_any_coin/tests/chains/solana/solana_sign.rs index f59c1fd74bf..27c09741d3f 100644 --- a/rust/tw_any_coin/tests/chains/solana/solana_sign.rs +++ b/rust/tw_any_coin/tests/chains/solana/solana_sign.rs @@ -392,6 +392,7 @@ fn test_solana_sign_create_token_account() { main_address: "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V".into(), token_mint_address: "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt".into(), token_address: "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP".into(), + ..Proto::CreateTokenAccount::default() }; let input = Proto::SigningInput { private_key: b58("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"), @@ -413,6 +414,7 @@ fn test_solana_sign_create_token_account_5ktpn1() { main_address: "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ".into(), token_mint_address: "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt".into(), token_address: "ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf".into(), + ..Proto::CreateTokenAccount::default() }; let input = Proto::SigningInput { private_key: "4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7" @@ -438,6 +440,7 @@ fn test_solana_sign_create_token_account_for_other_3e6ufv() { main_address: "3xJ3MoUVFPNFEHfWdtNFa8ajXUHsJPzXcBSWMKLd76ft".into(), token_mint_address: "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt".into(), token_address: "67BrwFYt7qUnbAcYBVx7sQ4jeD2KWN1ohP6bMikmmQV3".into(), + ..Proto::CreateTokenAccount::default() }; let input = Proto::SigningInput { private_key: "4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7" @@ -469,6 +472,7 @@ fn test_solana_sign_create_token_account_with_priority_fee_price() { // WBTC token_mint_address: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh".into(), token_address: "94JfTH8qCeBQDBatvULREG43msjSQscT7oHnqx7jppX".into(), + ..Proto::CreateTokenAccount::default() }; let input = Proto::SigningInput { @@ -611,6 +615,7 @@ fn test_solana_sign_create_and_transfer_token_with_memo_and_references() { "CuieVDEDtLo7FypA9SbLM9saXFdb1dsshEkyErMqkRQq".into(), "tFpP7tZUt6zb7YZPpQ11kXNmsc5YzpMXmahGMvCHhqS".into(), ], + ..Proto::CreateAndTransferToken::default() }; let input = Proto::SigningInput { private_key: b58("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"), @@ -918,3 +923,53 @@ fn test_solana_sign_raw_message_v0() { // Successfully broadcasted: https://explorer.solana.com/tx/4ffBzXxLPYEEdCYpQGETkCTCCsH6iTdmKzwUZXZZgFemdhRpxQwboguFFoKCeGF3SsZPzuwwE7LbRwLgJbsyRqyP?cluster=testnet assert_eq!(output.encoded, "6NijVxwQoDjqt6A41HXCK9kXwNDp48uLgvRyE8uz6NY5dEzaEDLzjzuMnc5TGatHZZUXehKrzUGzbg9jPSdn6pVsMc9TXNH6JGe5RJLmHwWey3MC1p8Hs2zhjw5P439P57NToatraDX9ZwvBtK4EzZzRjWbyGdicheTPjeYKCzvPCLxDkTFtPCM9VZGGXSN2Bne92NLDvf6ntNm5pxsPkZGxPe4w9Eq26gkE83hZyrYXKaiDh8TbqbHatSkw"); } + +#[test] +fn test_solana_sign_create_and_transfer_token_2022() { + let create_transfer_token: Proto::CreateAndTransferToken = Proto::CreateAndTransferToken { + recipient_main_address: "EbHdsfVpWzeQV4TceYQ2xENS8meBHyztyTKVSFtgHPUw".into(), + token_mint_address: "BSQCmMAFB9itonyVSLsUxX92Ne1rgBZFqothBk3q91k6".into(), + recipient_token_address: "FzsLNpzsLMBbm1LWpM6P3W4tKrCkd8KqnMmADNvArW5d".into(), + sender_token_address: "EQxRyhzjyhRX4TJXt7FmQ3HfFdRcu49krjxHMszidQYS".into(), + amount: 1000000000, + decimals: 9, + token_program_id: Proto::TokenProgramId::Token2022Program, + ..Proto::CreateAndTransferToken::default() + }; + let input = Proto::SigningInput { + private_key: b58("MCyXa2gTJELxTPemyVi5ydDcQ3vVgFyddQYXj6UM3tw"), + recent_blockhash: "5oba9g5nWnvutTTb935aBMkHBYGXoak1ot4U2p34zEiJ".into(), + transaction_type: TransactionType::create_and_transfer_token_transaction( + create_transfer_token, + ), + ..Proto::SigningInput::default() + }; + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Solana, input); + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.encoded, "2xzg9AVGv8wWEn9S4m8954WSzh2MUQPCTCyFmyrSs4DJCkSaZRMAbGL8NcyDeJFT3RwUabHsX1m5CFuqzJ5Jg9knNwG6uBjYjWjNjGLBEBURa3ARqziaMAL2mZY8uZwaZETE33WZeSxNrm7zv1jJYLfqbWxquEedGND9vB9AuEspHg7TCZxfJbzY4W8QtLqyQ598z9adxWgwNXanHzqu7B4bNsp1wfKPPyx8AGQaVSx6fepaevDEZX9h2Rg1daW9TjVpktp7EHrriYVs4m44WJ18fejWLyqituXqQPdhos5oZ3e5vNXE8KcgARKXtwsXCGwwKwc9ZEVNvUp6qyUZZV8os2FHorodrT9g3Xrso5dgdsRCb42AUrKHyDdXMpRA1PmeZX6UdzgL8knt2xfzCFxzGPuMKeTtvZKFcEPJvNg73CSMPVH1mm3jz75nATdChR7xu5R4m5Gy8vhr5ndEnb8fM5P1gv6hDbfmesAEf5wye4mKTVAC4B8Mhf8WC8YNaGUG7CcxeQZXrjEfUQenboArhqbxqHFYrURK3GJLAQojXmkwSMGwv4TYL"); +} + +#[test] +fn test_solana_sign_transfer_token_2022() { + let transfer_token: Proto::TokenTransfer = Proto::TokenTransfer { + amount: 1000000000, + decimals: 9, + token_program_id: Proto::TokenProgramId::Token2022Program, + token_mint_address: "BSQCmMAFB9itonyVSLsUxX92Ne1rgBZFqothBk3q91k6".into(), + sender_token_address: "EQxRyhzjyhRX4TJXt7FmQ3HfFdRcu49krjxHMszidQYS".into(), + recipient_token_address: "FzsLNpzsLMBbm1LWpM6P3W4tKrCkd8KqnMmADNvArW5d".into(), + ..Proto::TokenTransfer::default() + }; + let input = Proto::SigningInput { + private_key: b58("MCyXa2gTJELxTPemyVi5ydDcQ3vVgFyddQYXj6UM3tw"), + recent_blockhash: "9U2eTS9b2Essvo1s5hDmwgC1atkSCCUipj2FemLvdWbj".into(), + transaction_type: TransactionType::token_transfer_transaction(transfer_token), + ..Proto::SigningInput::default() + }; + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Solana, input); + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.encoded, "SAXNFUd7dNBu956Gi4XNuvMkKKjS9vp6puz45ErYMHFpMNwC3AQxDxGbweXt4GzY2FnUZ6ubm231NrdwWa8dg9bqgRMaHPLuPiy99YwtvcQ1E6mHxHqq8nL5VaN8wiVnrMU57zCLfHsSsVCHZc5peHHAPXMDE318uMCLLBwgDWuD1FfAvUAyXRSYniXzWG3jtBdDhuDohh13E2TMrtqTcKVv3crejFqFjtsNuW7KCqrZwxCv1ASNiiL2XScQBdHwStyjH2UTqLmT6wjGLiDYy7PZ88Tbz65r8NLr4Vb1aYSTChasfVjMLdybetfNaf4nJuBE4ZuXca7W66txKbHesxQbzrjUCXX12JFbKyaA8KJKBpbgkc9jWJjQkzyn"); + // https://explorer.solana.com/tx/Lg1xWzsC9GatQMu1ZXv23t7snC92RRvbKJe22bsS76GUb8C8a9q3HPkiUnFoK6AWKSoNSsmko1EBnvKkCnL8b7w?cluster=devnet +} diff --git a/src/proto/Solana.proto b/src/proto/Solana.proto index 24a411c8002..0649cf72671 100644 --- a/src/proto/Solana.proto +++ b/src/proto/Solana.proto @@ -69,6 +69,11 @@ message WithdrawAllStake { repeated StakeAccountValue stake_accounts = 1; } +enum TokenProgramId { + TokenProgram = 0; + Token2022Program = 1; +} + // Create a token account under a main account for a token type message CreateTokenAccount { // main account -- can be same as signer, or other main account (if done on some other account's behalf) @@ -79,6 +84,9 @@ message CreateTokenAccount { // Token address string token_address = 3; + + // optional token program id + TokenProgramId token_program_id = 4; } // Transfer tokens @@ -103,6 +111,9 @@ message TokenTransfer { // optional referenced public keys repeated string references = 7; + + // optional token program id + TokenProgramId token_program_id = 8; } // CreateTokenAccount and TokenTransfer combined @@ -130,6 +141,9 @@ message CreateAndTransferToken { // optional referenced public keys repeated string references = 8; + + // optional token program id + TokenProgramId token_program_id = 9; } message CreateNonceAccount {