What changed, and why it matters
This commit replaces placeholder Solana token fee estimates with real ones fetched from the Solana network. Previously the wallet used a hard-coded 5000 lamport fee and threw an error when asked for fee details. Now it builds a sample token transfer transaction and asks the Solana RPC node how much that transaction would cost. This is a normal feature improvement and does not appear to introduce a security vulnerability.
No security action required. As a routine review step, verify that the fallback fee (5000 lamports) is reasonable and that RPC failures do not silently cause users to over- or under-pay fees. Consider surfacing fee-estimation failures to the user instead of silently falling back.
Security signals we found
Removal of hard-coded/mock fee values
Addition of RPC-based fee estimation with fallback
No secret material, signing, or network trust boundary changes observed
No input from untrusted sources is passed to dangerous APIs
Evidence from the diff
The change removes mock/placeholder fee logic in SolanaTokenWallet and delegates fee estimation to the parent SolanaWallet or to a new helper that compiles an SPL token transfer message and calls rpcClient.getFeeForMessage. It also removes an unused _estimateTransactionFee helper. The code now uses live blockhash and RPC fee data with a 5000-lamport fallback. There is no evidence of malicious behavior, injection, or unsafe handling of secrets; the change is a functional completion of fee estimation.
Changed components
lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dartSolana SPL token fee estimationSolanaTokenWallet.estimateFeeForSolanaTokenWallet.feesInspect captured patch +54 / −20
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 6239c48..508ebdf 100644
--- a/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart
+++ b/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart
@@ -161,10 +161,14 @@ class SolanaTokenWallet extends Wallet {
amount: txData.amount!.raw.toInt(),
);
- // Estimate fee.
- // For now, use a default fee estimate.
- // TODO: Implement proper fee estimation using compiled message.
- const feeEstimate = 5000;
+ // Estimate fee using RPC call.
+ final feeEstimate = await _getEstimatedTokenTransferFee(
+ senderTokenAccountKey: senderTokenAccountKey,
+ recipientTokenAccountKey: recipientTokenAccountKey,
+ ownerPublicKey: keyPair.publicKey,
+ amount: txData.amount!.raw.toInt(),
+ rpcClient: rpcClient,
+ ) ?? 5000;
// Return prepared TxData.
return txData.copyWith(
@@ -331,14 +335,16 @@ class SolanaTokenWallet extends Wallet {
@override
Future<Amount> estimateFeeFor(Amount amount, BigInt feeRate) async {
- // Mock fee estimation: 5000 lamports for token transfer.
- return Amount.zeroWith(fractionDigits: tokenDecimals);
+ // Delegate to parent SolanaWallet for fee estimation.
+ // For token transfers, the fee is the same as a regular SOL transfer.
+ return parentSolanaWallet.estimateFeeFor(amount, feeRate);
}
@override
Future<FeeObject> get fees async {
- // TODO: Return real Solana fee estimates.
- throw UnimplementedError("fees not yet implemented");
+ // Delegate to parent SolanaWallet for fee information.
+ // For token transfers, the fees are the same as regular SOL transfers.
+ return parentSolanaWallet.fees;
}
@override
@@ -475,33 +481,61 @@ class SolanaTokenWallet extends Wallet {
}
}
- /// Estimate the transaction fee by simulating it on-chain.
+ /// Estimate the fee for an SPL token transfer transaction.
+ ///
+ /// Builds a token transfer message with the given parameters and uses
+ /// the RPC `getFeeForMessage` call to get an accurate fee estimate.
///
- /// Falls back to default fee (5000 lamports) if estimation fails.
- /// Note: Currently unused but kept for future implementation of proper fee estimation.
- // ignore: unused_element
- Future<int> _estimateTransactionFee({
- required List<int> messageBytes,
+ /// Returns the estimated fee in lamports, or null if estimation fails.
+ Future<int?> _getEstimatedTokenTransferFee({
+ required Ed25519HDPublicKey senderTokenAccountKey,
+ required Ed25519HDPublicKey recipientTokenAccountKey,
+ required Ed25519HDPublicKey ownerPublicKey,
+ required int amount,
required RpcClient rpcClient,
}) async {
try {
+ // Get latest blockhash for message compilation.
+ final latestBlockhash = await rpcClient.getLatestBlockhash();
+
+ // Build the token transfer instruction.
+ final instruction = TokenInstruction.transfer(
+ source: senderTokenAccountKey,
+ destination: recipientTokenAccountKey,
+ owner: ownerPublicKey,
+ amount: amount,
+ );
+
+ // Compile the message with the blockhash.
+ final compiledMessage = Message(
+ instructions: [instruction],
+ ).compile(
+ recentBlockhash: latestBlockhash.value.blockhash,
+ feePayer: ownerPublicKey,
+ );
+
+ // Get the fee for this compiled message.
final feeEstimate = await rpcClient.getFeeForMessage(
- base64Encode(messageBytes),
+ base64Encode(compiledMessage.toByteArray().toList()),
commitment: Commitment.confirmed,
);
if (feeEstimate != null) {
+ Logging.instance.i(
+ "$runtimeType Estimated token transfer fee: $feeEstimate lamports (from RPC)",
+ );
return feeEstimate;
}
- // Fallback to default fee
- return 5000;
+ Logging.instance.w(
+ "$runtimeType getFeeForMessage returned null",
+ );
+ return null;
} catch (e) {
Logging.instance.w(
- "$runtimeType _estimateTransactionFee error: $e, using default fee",
+ "$runtimeType _getEstimatedTokenTransferFee error: $e",
);
- // Default fee: 5000 lamports
- return 5000;
+ return null;
}
}
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.