diff --git a/Package.swift b/Package.swift
index 7e933efe93d..68de23747c4 100644
--- a/Package.swift
+++ b/Package.swift
@@ -12,13 +12,13 @@ let package = Package(
targets: [
.binaryTarget(
name: "WalletCore",
- url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.12/WalletCore.xcframework.zip",
- checksum: "1632bbbab1c6a588689eec77a24e1468d9a6746968652cf0a7e669e996c3d24d"
+ url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.21/WalletCore.xcframework.zip",
+ checksum: "503937e1013bb7c1f610a8a4ec74a6ccdedb06bdec9fa9126ac47e25a90da06a"
),
.binaryTarget(
name: "SwiftProtobuf",
- url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.12/SwiftProtobuf.xcframework.zip",
- checksum: "33d80c20428c9db4fcf99d1272ba19655f7c6ee7e5b1809fa8a7e7d4aa1b222b"
+ url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.21/SwiftProtobuf.xcframework.zip",
+ checksum: "f6da2b8fafdce5e8d46ea305972f1ad942cc796f63026a32c883331dd3813285"
)
]
)
diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt
index a7f1ca53348..535970c9957 100644
--- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt
+++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt
@@ -118,4 +118,19 @@ class TestSolanaTransaction {
val expectedString = "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA"
assertEquals(output.encoded, expectedString)
}
+
+ @Test
+ fun testSetFeePayer() {
+ val originalTx = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA=="
+
+ // Step 1 - Add fee payer to the transaction.
+ val updatedTx = SolanaTransaction.setFeePayer(originalTx, "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ")
+ assertEquals(updatedTx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==")
+
+ // This case originates from a test case in C++. Here, only the most critical function is verified for correctness,
+ // while the remaining steps have been omitted.
+ // Step 2 - Decode transaction into a `RawMessage` Protobuf.
+ // Step 3 - Obtain preimage hash.
+ // Step 4 - Compile transaction info.
+ }
}
\ No newline at end of file
diff --git a/docs/registry.md b/docs/registry.md
index a5045889d19..8a109c526d2 100644
--- a/docs/registry.md
+++ b/docs/registry.md
@@ -131,7 +131,7 @@ This list is generated from [./registry.json](../registry.json)
| 10004689 | IoTeX EVM | IOTX | | |
| 10007000 | NativeZetaChain | ZETA | | |
| 10007700 | NativeCanto | CANTO | | |
-| 10008217 | Kaia | KLAY | | |
+| 10008217 | Kaia | KAIA | | |
| 10009000 | Avalanche C-Chain | AVAX | | |
| 10009001 | Evmos | EVMOS | | |
| 10042170 | Arbitrum Nova | ETH | | |
diff --git a/include/TrustWalletCore/TWSolanaTransaction.h b/include/TrustWalletCore/TWSolanaTransaction.h
index 871393fc840..420a84ca7b6 100644
--- a/include/TrustWalletCore/TWSolanaTransaction.h
+++ b/include/TrustWalletCore/TWSolanaTransaction.h
@@ -50,7 +50,7 @@ TWString *_Nullable TWSolanaTransactionGetComputeUnitLimit(TWString *_Nonnull en
/// and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
-/// \price Unit Price as a decimal string.
+/// \param price Unit Price as a decimal string.
/// \return base64 encoded Solana transaction. Null if an error occurred.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWSolanaTransactionSetComputeUnitPrice(TWString *_Nonnull encodedTx, TWString *_Nonnull price);
@@ -59,9 +59,17 @@ TWString *_Nullable TWSolanaTransactionSetComputeUnitPrice(TWString *_Nonnull en
/// and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
-/// \limit Unit Limit as a decimal string.
+/// \param limit Unit Limit as a decimal string.
/// \return base64 encoded Solana transaction. Null if an error occurred.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWSolanaTransactionSetComputeUnitLimit(TWString *_Nonnull encodedTx, TWString *_Nonnull limit);
+/// Adds fee payer to the given transaction and returns the updated transaction.
+///
+/// \param encodedTx base64 encoded Solana transaction.
+/// \param feePayer fee payer account address. Must be a base58 encoded public key. It must NOT be in the account list yet.
+/// \return base64 encoded Solana transaction. Null if an error occurred.
+TW_EXPORT_STATIC_METHOD
+TWString *_Nullable TWSolanaTransactionSetFeePayer(TWString *_Nonnull encodedTx, TWString *_Nonnull feePayer);
+
TW_EXTERN_C_END
diff --git a/registry.json b/registry.json
index 2934d0c651c..5e43fe7ae90 100644
--- a/registry.json
+++ b/registry.json
@@ -4066,7 +4066,7 @@
"id": "kaia",
"name": "Kaia",
"coinId": 10008217,
- "symbol": "KLAY",
+ "symbol": "KAIA",
"decimals": 18,
"blockchain": "Ethereum",
"derivation": [
diff --git a/rust/chains/tw_solana/src/modules/insert_instruction.rs b/rust/chains/tw_solana/src/modules/insert_instruction.rs
index 22643e349ce..36c1c583de3 100644
--- a/rust/chains/tw_solana/src/modules/insert_instruction.rs
+++ b/rust/chains/tw_solana/src/modules/insert_instruction.rs
@@ -91,6 +91,37 @@ pub trait InsertInstruction {
Ok(account_added_at)
}
+ /// Adds a fee payer account to the message.
+ /// Note: The fee payer must NOT be in the account list yet.
+ fn set_fee_payer(&mut self, account: SolanaAddress) -> SigningResult<()> {
+ if self.account_keys_mut().contains(&account) {
+ // For security reasons, we don't allow adding a fee payer if it's already in the account list.
+ //
+ // If the fee payer is already in the transaction and there is a malicious instruction to
+ // transfer tokens from the fee payer to another account, The fee payer may have inadvertently
+ // signed off on such transactions, which is not what they would expect.
+ //
+ // Such examples may be difficult to exploit, but we still took precautionary measures to prohibit
+ // the new fee payer from appearing in the account list of the transaction out of caution
+ return SigningError::err(SigningErrorType::Error_internal)
+ .context("Fee payer account is already in the account list");
+ }
+
+ // Insert the fee payer account at the beginning of the account list.
+ self.account_keys_mut().insert(0, account);
+ self.message_header_mut().num_required_signatures += 1;
+
+ // Update `program id indexes` and `account id indexes` in every instruction as we inserted the account at the beginning of the list.
+ self.instructions_mut().iter_mut().for_each(|ix| {
+ ix.program_id_index += 1; // Update `program id indexes`
+ ix.accounts
+ .iter_mut()
+ .for_each(|account_id| *account_id += 1); // Update `account id indexes`
+ });
+
+ Ok(())
+ }
+
/// Returns ALT (Address Lookup Tables) if supported by the message version.
fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]>;
diff --git a/rust/chains/tw_solana/src/modules/tx_signer.rs b/rust/chains/tw_solana/src/modules/tx_signer.rs
index 5fb421e6876..754fa372efe 100644
--- a/rust/chains/tw_solana/src/modules/tx_signer.rs
+++ b/rust/chains/tw_solana/src/modules/tx_signer.rs
@@ -44,13 +44,6 @@ impl TxSigner {
) -> SigningResult {
let mut tx = versioned::VersionedTransaction::unsigned(unsigned_msg);
- let actual_signatures = key_signs.len();
- let expected_signatures = tx.message.num_required_signatures();
- if actual_signatures != expected_signatures {
- return SigningError::err(SigningErrorType::Error_signatures_count)
- .with_context(|| format!("Expected '{expected_signatures}' signatures, provided '{actual_signatures}'"));
- }
-
for (signing_pubkey, ed25519_signature) in key_signs {
// Find an index of the corresponding account.
let account_index = tx
diff --git a/rust/chains/tw_solana/src/modules/utils.rs b/rust/chains/tw_solana/src/modules/utils.rs
index da19b9d17d8..0d7f61dd8a3 100644
--- a/rust/chains/tw_solana/src/modules/utils.rs
+++ b/rust/chains/tw_solana/src/modules/utils.rs
@@ -2,6 +2,7 @@
//
// Copyright © 2017 Trust Wallet.
+use crate::address::SolanaAddress;
use crate::defined_addresses::{COMPUTE_BUDGET_ADDRESS, SYSTEM_PROGRAM_ID_ADDRESS};
use crate::modules::insert_instruction::InsertInstruction;
use crate::modules::instruction_builder::compute_budget_instruction::{
@@ -158,6 +159,20 @@ impl SolanaTransaction {
tx.to_base64().tw_err(|_| SigningErrorType::Error_internal)
}
+
+ pub fn set_fee_payer(encoded_tx: &str, fee_payer: SolanaAddress) -> SigningResult {
+ let tx_bytes = base64::decode(encoded_tx, STANDARD)?;
+ let mut tx: VersionedTransaction =
+ bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?;
+
+ tx.message.set_fee_payer(fee_payer)?;
+
+ // Set the correct number of zero signatures
+ let unsigned_tx = VersionedTransaction::unsigned(tx.message);
+ unsigned_tx
+ .to_base64()
+ .tw_err(|_| SigningErrorType::Error_internal)
+ }
}
fn try_instruction_as_compute_budget(
diff --git a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs
index 0bcfb626960..f2ee8880590 100644
--- a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs
+++ b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs
@@ -60,11 +60,11 @@ where
.input_args()
.iter()
.enumerate()
- .map(|(input_index, utxo)| {
+ .map(|(signing_input_index, utxo)| {
let signing_method = utxo.signing_method;
let utxo_args = UtxoPreimageArgs {
- input_index,
+ input_index: signing_input_index,
script_pubkey: utxo.script_pubkey.clone(),
amount: utxo.amount,
// TODO move `leaf_hash_code_separator` to `UtxoTaprootPreimageArgs`.
@@ -90,12 +90,14 @@ where
let tr_spent_script_pubkeys: Vec