fix(spl): init rpc even when you skip opening the parent wallet
What changed, and why it matters
This commit fixes a bug in Stack Wallet's Solana token wallet support. When a user chose to sync only selected wallets and skipped opening the main Solana wallet, the token wallet would not initialize its connection to the Solana network (RPC client). The fix makes the token wallet explicitly initialize that connection during its own startup, so token balances and transactions can work even when the parent wallet was not opened first.
Review whether any other sub-wallets or wallet types have similar dependencies on parent wallet initialization for network client setup. Consider adding defensive checks or centralized initialization to ensure RPC clients are always configured before use, especially when 'only sync selected wallets' is enabled. No immediate user action is indicated beyond updating to the fixed version.
Security signals we found
RPC client initialization skipped under specific wallet sync configuration
Potential use of uninitialized network client in token wallet operations
Tor/plain network option may not be applied if client is not initialized
Rename of private method to public suggests cross-class dependency introduced
Evidence from the diff
The patch renames SolanaWallet._checkClient() to public checkClient() and calls it from SolanaTokenWallet.init(). Previously, _checkClient() was private and only called from within SolanaWallet methods, meaning the RPC client (with Tor/plain network configuration) was only set up when the parent Solana wallet was opened. If a user enabled ‘only sync selected wallets’ and selected only a Solana SPL token wallet, the parent wallet’s init path would not run, leaving the token wallet without an initialized RPC client. The fix ensures the token wallet initializes the RPC client itself during init().
Changed components
lib/wallets/wallet/impl/solana_wallet.dartlib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dartSolana SPL token wallet sync/init pathRPC/Tor network initialization for Solana walletsInspect captured patch +18 / −13
diff --git a/lib/wallets/wallet/impl/solana_wallet.dart b/lib/wallets/wallet/impl/solana_wallet.dart
index b7a49be..4e0882a 100644
--- a/lib/wallets/wallet/impl/solana_wallet.dart
+++ b/lib/wallets/wallet/impl/solana_wallet.dart
@@ -75,13 +75,13 @@ class SolanaWallet extends Bip39Wallet<Solana> {
}
Future<BigInt> _getCurrentBalanceInLamports() async {
- _checkClient();
+ checkClient();
final balance = await _rpcClient?.getBalance((await _getKeyPair()).address);
return BigInt.from(balance!.value);
}
Future<BigInt?> _getEstimatedNetworkFee(Amount transferAmount) async {
- _checkClient();
+ checkClient();
final latestBlockhash = await _rpcClient?.getLatestBlockhash();
final pubKey = (await _getKeyPair()).publicKey;
@@ -134,7 +134,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override
Future<TxData> prepareSend({required TxData txData}) async {
try {
- _checkClient();
+ checkClient();
if (txData.recipients == null || txData.recipients!.length != 1) {
throw Exception("$runtimeType prepareSend requires 1 recipient");
@@ -195,7 +195,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override
Future<TxData> confirmSend({required TxData txData}) async {
try {
- _checkClient();
+ checkClient();
final keyPair = await _getKeyPair();
final recipientAccount = txData.recipients!.first;
@@ -280,7 +280,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override
Future<Amount> estimateFeeFor(Amount amount, BigInt feeRate) async {
- _checkClient();
+ checkClient();
if (info.cachedBalance.spendable.raw == BigInt.zero) {
return Amount(
@@ -292,12 +292,15 @@ class SolanaWallet extends Bip39Wallet<Solana> {
// The feeRate parameter contains the total fee amount to use.
// For Solana, this is already calculated based on priority tier.
// Simply return it as the fee estimate.
- return Amount(rawValue: feeRate, fractionDigits: cryptoCurrency.fractionDigits);
+ return Amount(
+ rawValue: feeRate,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ );
}
@override
Future<FeeObject> get fees async {
- _checkClient();
+ checkClient();
final baseFee = await _getEstimatedNetworkFee(
Amount.fromDecimal(
@@ -348,7 +351,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
Future<bool> pingCheck() async {
String? health;
try {
- _checkClient();
+ checkClient();
health = await _rpcClient?.getHealth();
return health != null;
} catch (e, s) {
@@ -387,7 +390,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override
Future<void> updateBalance() async {
- _checkClient();
+ checkClient();
try {
final address = await getCurrentReceivingAddress();
@@ -437,7 +440,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override
Future<void> updateChainHeight() async {
try {
- _checkClient();
+ checkClient();
final int blockHeight = await _rpcClient?.getSlot() ?? 0;
// TODO [prio=low]: Revisit null condition.
@@ -478,7 +481,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override
Future<void> updateTransactions() async {
try {
- _checkClient();
+ checkClient();
final transactionsList = await _rpcClient?.getTransactionsList(
(await _getKeyPair()).publicKey,
@@ -665,7 +668,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
}
/// Make sure the Solana RpcClient uses Tor if it's enabled.
- void _checkClient() {
+ void checkClient() {
final node = getCurrentNode();
final netOption = TorPlainNetworkOption.fromNodeData(
diff --git a/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart b/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart
index 8ae30e3..84555c2 100644
--- a/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart
+++ b/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart
@@ -77,7 +77,9 @@ class SolanaTokenWallet extends Wallet {
@override
Future<void> init() async {
await super.init();
- // TODO: Initialize token account address derivation.
+
+ parentSolanaWallet.checkClient();
+
await Future<void>.delayed(const Duration(milliseconds: 100));
}
Why this scored 35/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.