What changed, and why it matters
This commit adds a user-interface feature that displays Solana transaction memos in the wallet app. It reads existing on-chain memo data and shows it to the user, but does not change how transactions are created, signed, or validated. There is no security-relevant change visible in the code.
No security action required. Treat as a normal feature commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch extends TransactionV2 with a read-only memo accessor backed by a new key ‘onChainMemo’ in the otherData JSON blob. SolanaWallet and SolanaTokenWallet now extract the first parsed spl-memo instruction from fetched transactions and store it in otherData. The transaction details UI renders this memo if present. No signing, parsing, input handling, or trust-boundary logic is modified.
Changed components
lib/models/isar/models/blockchain_data/v2/transaction_v2.dartlib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dartlib/wallets/wallet/impl/solana_wallet.dartlib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dartInspect captured patch +35 / −0
diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart
index a507ba8..4bb9e1f 100644
--- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart
+++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart
@@ -87,6 +87,9 @@ class TransactionV2 {
);
}
+ @ignore
+ String? get memo => _getFromOtherData(key: TxV2OdKeys.memo) as String?;
+
@ignore
int? get size => _getFromOtherData(key: TxV2OdKeys.size) as int?;
@@ -447,4 +450,5 @@ abstract final class TxV2OdKeys {
static const isInstantLock = "isInstantLock";
static const salviumTypeInt = "salviumTypeInt";
static const salviumTypeString = "salviumTypeString";
+ static const memo = "onChainMemo";
}
diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart
index 8e7e392..e10200c 100644
--- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart
+++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart
@@ -1264,6 +1264,15 @@ class _TransactionV2DetailsViewState
label: "Nonce",
detail: _transaction.nonce.toString(),
),
+ if (_transaction.memo != null)
+ isDesktop
+ ? const _Divider()
+ : const SizedBox(height: 12),
+ if (_transaction.memo != null)
+ _DetailItem(
+ label: "Memo",
+ detail: _transaction.memo!,
+ ),
if (coin is Salvium &&
_transaction.salviumTypeString != null)
isDesktop
diff --git a/lib/wallets/wallet/impl/solana_wallet.dart b/lib/wallets/wallet/impl/solana_wallet.dart
index 97ff59c..9abc4cf 100644
--- a/lib/wallets/wallet/impl/solana_wallet.dart
+++ b/lib/wallets/wallet/impl/solana_wallet.dart
@@ -579,6 +579,16 @@ class SolanaWallet extends Bip39Wallet<Solana> {
txType = isar.TransactionType.unknown;
}
+ // check for memo
+ final memos = parsedTx.message.instructions
+ .map((e) => e.toJson())
+ .where(
+ (e) => e["parsed"] is String && e["program"] == "spl-memo",
+ );
+ final String? memo = memos.isEmpty
+ ? null
+ : memos.first["parsed"] as String;
+
// Create TransactionV2 object.
final txn = TransactionV2(
walletId: walletId,
@@ -615,6 +625,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
subType: isar.TransactionSubType.none,
otherData: jsonEncode({
TxV2OdKeys.overrideFee: tx.meta!.fee.toString(),
+ if (memo != null) TxV2OdKeys.memo: memo,
}),
);
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 523c03f..a311e05 100644
--- a/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart
+++ b/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart
@@ -544,6 +544,16 @@ class SolanaTokenWallet extends Wallet {
txType = TransactionType.unknown;
}
+ // check for memo
+ final memos = parsedTx.message.instructions
+ .map((e) => e.toJson())
+ .where(
+ (e) => e["parsed"] is String && e["program"] == "spl-memo",
+ );
+ final String? memo = memos.isEmpty
+ ? null
+ : memos.first["parsed"] as String;
+
// Create placeholder TransactionV2 object.
final txn = TransactionV2(
walletId: walletId,
@@ -583,6 +593,7 @@ class SolanaTokenWallet extends Wallet {
TxV2OdKeys.contractAddress: tokenMint,
TxV2OdKeys.isCancelled: (txDetails.meta!.err != null),
TxV2OdKeys.overrideFee: txDetails.meta!.fee.toString(),
+ if (memo != null) TxV2OdKeys.memo: memo,
}),
);
Why this scored 15/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.