feat(spl): save custom tokens and include in wallet token list
What changed, and why it matters
This commit adds support for saving user-added custom Solana tokens and showing them alongside the wallet's default token list. It also includes minor code cleanup (removing an unused import and reformatting). There is no clear security vulnerability in the change itself.
No immediate action required. As a defensive follow-up, review how custom mint addresses are validated before being passed to `updateSolanaTokens`, ensure they are stored safely in Isar, and verify the UI does not treat arbitrary user input as trusted token metadata.
Security signals we found
No explicit security fix or hardening
New persistence path for user-supplied token mint addresses
No input validation visible in the diff for custom mint addresses
Minor code cleanup only
Evidence from the diff
The patch modifies two files. In wallet_info_provider.dart, the token address provider now merges solanaTokenMintAddresses with solanaCustomTokenMintAddresses for Solana wallets. In solana_wallet.dart, a new updateSolanaTokens method persists a set of custom mint addresses via info.updateSolanaCustomTokenMintAddresses and fires a background-update event. The rest of the diff is formatting and a removed unused tuple import. No input validation, sanitization, or authorization changes are visible in this commit.
Changed components
lib/wallets/isar/providers/wallet_info_provider.dartlib/wallets/wallet/impl/solana_wallet.dartInspect captured patch +39 / −15
diff --git a/lib/wallets/isar/providers/wallet_info_provider.dart b/lib/wallets/isar/providers/wallet_info_provider.dart
index c79ab8a..354658d 100644
--- a/lib/wallets/isar/providers/wallet_info_provider.dart
+++ b/lib/wallets/isar/providers/wallet_info_provider.dart
@@ -101,7 +101,7 @@ final pWalletReceivingAddress = Provider.family<String, String>((
/// Returns the appropriate token list based on the wallet's coin type.
///
/// For Ethereum wallets: returns tokenContractAddresses.
-/// For Solana wallets: returns solanaTokenMintAddresses.
+/// For Solana wallets: returns solanaTokenMintAddresses + solanaCustomTokenMintAddresses combined.
final pWalletTokenAddresses = Provider.family<List<String>, String>((
ref,
walletId,
@@ -109,7 +109,12 @@ final pWalletTokenAddresses = Provider.family<List<String>, String>((
final walletInfo = ref.watch(pWalletInfo(walletId));
if (walletInfo.coin.prettyName == 'Solana') {
- return walletInfo.solanaTokenMintAddresses;
+ // Combine both default and custom token mint addresses.
+ final allTokens = <String>{
+ ...walletInfo.solanaTokenMintAddresses,
+ ...walletInfo.solanaCustomTokenMintAddresses,
+ };
+ return allTokens.toList();
} else {
return walletInfo.tokenContractAddresses;
}
diff --git a/lib/wallets/wallet/impl/solana_wallet.dart b/lib/wallets/wallet/impl/solana_wallet.dart
index 7f9c941..6caa875 100644
--- a/lib/wallets/wallet/impl/solana_wallet.dart
+++ b/lib/wallets/wallet/impl/solana_wallet.dart
@@ -7,7 +7,6 @@ import 'package:isar_community/isar.dart';
import 'package:socks5_proxy/socks_client.dart';
import 'package:solana/dto.dart';
import 'package:solana/solana.dart';
-import 'package:tuple/tuple.dart';
import '../../../app_config.dart';
import '../../../exceptions/wallet/node_tor_mismatch_config_exception.dart';
@@ -19,6 +18,8 @@ import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
import '../../../models/isar/models/isar_models.dart';
import '../../../models/node_model.dart';
import '../../../models/paymint/fee_object_model.dart';
+import '../../../services/event_bus/events/global/updated_in_background_event.dart';
+import '../../../services/event_bus/global_event_bus.dart';
import '../../../services/node_service.dart';
import '../../../services/tor_service.dart';
import '../../../utilities/amount/amount.dart';
@@ -256,11 +257,11 @@ class SolanaWallet extends Bip39Wallet<Solana> {
),
],
version: -1,
- type: isToSelf ? isar.TransactionType.sentToSelf : isar.TransactionType.outgoing,
+ type: isToSelf
+ ? isar.TransactionType.sentToSelf
+ : isar.TransactionType.outgoing,
subType: isar.TransactionSubType.none,
- otherData: jsonEncode({
- "overrideFee": txData.fee!.toJsonString(),
- }),
+ otherData: jsonEncode({"overrideFee": txData.fee!.toJsonString()}),
);
await mainDB.updateOrPutTransactionV2s([tempTx]);
@@ -484,7 +485,9 @@ class SolanaWallet extends Bip39Wallet<Solana> {
}
final parsedTx = tx.transaction as ParsedTransaction;
- final txid = parsedTx.signatures.isNotEmpty ? parsedTx.signatures[0] : null;
+ final txid = parsedTx.signatures.isNotEmpty
+ ? parsedTx.signatures[0]
+ : null;
if (txid == null) {
skippedCount++;
continue;
@@ -492,10 +495,9 @@ class SolanaWallet extends Bip39Wallet<Solana> {
// Determine transaction direction.
final senderAddress = parsedTx.message.accountKeys[0].pubkey;
- var receiverAddress =
- parsedTx.message.accountKeys.length > 1
- ? parsedTx.message.accountKeys[1].pubkey
- : senderAddress;
+ var receiverAddress = parsedTx.message.accountKeys.length > 1
+ ? parsedTx.message.accountKeys[1].pubkey
+ : senderAddress;
var txType = isar.TransactionType.unknown;
if ((senderAddress == myAddress.value) &&
@@ -555,7 +557,8 @@ class SolanaWallet extends Bip39Wallet<Solana> {
blockHash: null,
hash: txid,
txid: txid,
- timestamp: tx.blockTime ?? DateTime.now().millisecondsSinceEpoch ~/ 1000,
+ timestamp:
+ tx.blockTime ?? DateTime.now().millisecondsSinceEpoch ~/ 1000,
height: tx.slot,
inputs: [
InputV2.isarCantDoRequiredInDefaultConstructor(
@@ -582,7 +585,9 @@ class SolanaWallet extends Bip39Wallet<Solana> {
version: -1,
type: txType,
subType: isar.TransactionSubType.none,
- otherData: otherDataMap.isNotEmpty ? jsonEncode(otherDataMap) : null,
+ otherData: otherDataMap.isNotEmpty
+ ? jsonEncode(otherDataMap)
+ : null,
);
txns.add(txn);
@@ -621,8 +626,22 @@ class SolanaWallet extends Bip39Wallet<Solana> {
return false;
}
+ /// Update the list of custom Solana token mint addresses for this wallet.
+ Future<void> updateSolanaTokens(Set<String> mintAddresses) async {
+ await info.updateSolanaCustomTokenMintAddresses(
+ newMintAddresses: mintAddresses,
+ isar: mainDB.isar,
+ );
+
+ GlobalEventBus.instance.fire(
+ UpdatedInBackgroundEvent(
+ "Solana custom tokens updated for: $walletId ${info.name}",
+ walletId,
+ ),
+ );
+ }
+
/// Make sure the Solana RpcClient uses Tor if it's enabled.
- ///
void _checkClient() {
final node = getCurrentNode();
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.