fix: SPL token balance display for xstocks and non-6-decimal tokens (#3052)
What changed, and why it matters
This commit fixes how Cake Wallet displays balances for certain Solana tokens (SPL tokens) that do not use the standard 6 decimal places. Previously, the wallet could show incorrect token balances because it assumed all tokens had 6 decimals. The fix uses the raw balance amount reported by the Solana network and the separately provided user-facing amount, so the displayed balance matches reality. There is no direct evidence this is a security vulnerability, but incorrect balance display could theoretically lead users to make wrong transaction decisions.
Treat as a routine bug fix. No immediate security response is required. If the project maintains a security model around financial display correctness, consider documenting the decimal-handling change in release notes and verifying edge cases for tokens with 0, 8, or 9 decimals.
Security signals we found
Balance display correctness issue for non-standard SPL token decimals
Potential user confusion or transaction decision impact from incorrect displayed balance
No direct exploit path identified in the diff
Evidence from the diff
The patch introduces a new SolanaBalance.forToken constructor that stores the raw on-chain token amount (BigInt) and the UI amount (double) separately, rather than deriving the raw amount from the formatted UI string with a hardcoded 6-decimal assumption. The SolanaWalletClient now sums both raw amounts and uiAmount values across token accounts, and callers use the new constructor. This resolves decimal mismatch issues for tokens like xStocks that do not use 6 decimals. The change is a correctness/UI fix; no cryptographic, authorization, or network-layer security controls are modified.
Changed components
cw_solana/lib/solana_balance.dartcw_solana/lib/solana_client.dartcw_solana/lib/solana_wallet.dartInspect captured patch +17 / −11
diff --git a/cw_solana/lib/solana_balance.dart b/cw_solana/lib/solana_balance.dart
index 5b8a533e..7be967fb 100644
--- a/cw_solana/lib/solana_balance.dart
+++ b/cw_solana/lib/solana_balance.dart
@@ -7,12 +7,15 @@ class SolanaBalance extends Balance {
BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0),
BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0));
+ // Using raw amount from RPC to avoid decimals mismatch for SPL tokens.
+ SolanaBalance.forToken(BigInt rawAmount, double uiAmount)
+ : balance = uiAmount,
+ super(rawAmount, rawAmount);
+
final double balance;
- @override
String get formattedAdditionalBalance => _balanceFormatted();
- @override
String get formattedAvailableBalance => _balanceFormatted();
String _balanceFormatted() {
diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart
index 5ceb7f75..aa86c15f 100644
--- a/cw_solana/lib/solana_client.dart
+++ b/cw_solana/lib/solana_client.dart
@@ -111,22 +111,24 @@ class SolanaWalletClient {
return null;
}
- // Sum the balances of all accounts with the specified mint address
- double totalBalance = 0.0;
+ // Sum raw amounts and ui amounts across all token accounts
+ BigInt totalRaw = BigInt.zero;
+ double totalUi = 0.0;
for (var tokenAccount in tokenAccounts) {
final tokenAmountResult = await _provider!.request(
SolanaRPCGetTokenAccountBalance(account: tokenAccount.pubkey),
);
- final balance = tokenAmountResult.uiAmountString;
+ final raw = BigInt.tryParse(tokenAmountResult.amount) ?? BigInt.zero;
+ totalRaw += raw;
- final balanceAsDouble = double.tryParse(balance ?? '0.0') ?? 0.0;
-
- totalBalance += balanceAsDouble;
+ final ui = tokenAmountResult.uiAmount ??
+ (double.tryParse(tokenAmountResult.uiAmountString ?? '0') ?? 0.0);
+ totalUi += ui;
}
- return SolanaBalance(totalBalance, true);
+ return SolanaBalance.forToken(totalRaw, totalUi);
} catch (_) {
if (throwOnError) {
rethrow;
diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart
index 7ffc1b24..51ed325e 100644
--- a/cw_solana/lib/solana_wallet.dart
+++ b/cw_solana/lib/solana_wallet.dart
@@ -588,7 +588,8 @@ abstract class SolanaWalletBase
for (final entry in results) {
final token = entry.key;
final fetchedBalance = entry.value;
- final currentBalance = balance[token] ?? SolanaBalance(0.0, true);
+ final currentBalance = balance[token] ??
+ SolanaBalance.forToken(BigInt.zero, 0.0);
balance[token] = fetchedBalance ?? currentBalance;
}
}
@@ -636,7 +637,7 @@ abstract class SolanaWalletBase
if (token.enabled) {
final tokenBalance = await _client.getSplTokenBalance(token.mintAddress, solanaAddress) ??
balance[token] ??
- SolanaBalance(0.0, true);
+ SolanaBalance.forToken(BigInt.zero, 0.0);
balance[token] = tokenBalance;
} else {
Why this scored 19/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.