perf(ordinals): cache ordinal-spend check off the build path
What changed, and why it matters
This commit is a performance refactor, not a security fix. It moves a check that warns users if they are about to spend a Bitcoin 'ordinal' inscription UTXO from the UI build path into an asynchronous initialization step, caching the result. The warning itself and the underlying behavior are unchanged. There is no indication this patch addresses a vulnerability or that the prior code was exploitable.
No security action required. Treat as a routine performance refactor. If reviewing for quality, verify that the async `findFirst()` call properly handles widget disposal and that `_spendsOrdinal` state is reset appropriately if `widget.txData` changes.
Security signals we found
No security-relevant keywords in commit title or message
Refactor only: logic moved from build-time to init-time with equivalent query semantics
No change to query filters, comparison operators, or trust assumptions
No input validation, authentication, authorization, or cryptographic changes
No vendor or researcher attribution indicating vulnerability disclosure
Evidence from the diff
The change refactors confirm_transaction_view.dart in the Stack Wallet Flutter app. Previously, the ordinal-spend warning was computed synchronously inside a Builder widget on every build, using findFirstSync() against the Isar database. The patch introduces _spendsOrdinal state and an initState()-triggered _checkForOrdinalSpend() async method using findFirst(), then conditionally renders the warning widget based on the cached boolean. This is a UI performance optimization; no security-sensitive logic, trust boundary, or data validation was altered.
Changed components
lib/pages/send_view/confirm_transaction_view.dartOrdinal inscription spend warning UIInspect captured patch +64 / −62
diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart
index 84af619..d925645 100644
--- a/lib/pages/send_view/confirm_transaction_view.dart
+++ b/lib/pages/send_view/confirm_transaction_view.dart
@@ -47,6 +47,7 @@ import '../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../wallets/wallet/impl/mimblewimblecoin_wallet.dart';
import '../../wallets/wallet/impl/solana_wallet.dart';
+import '../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
import '../../widgets/background.dart';
import '../../widgets/conditional_parent.dart';
@@ -110,6 +111,36 @@ class _ConfirmTransactionViewState
late final FocusNode _onChainNoteFocusNode;
late final TextEditingController onChainNoteController;
+ bool _spendsOrdinal = false;
+
+ Future<void> _checkForOrdinalSpend() async {
+ final wallet = ref.read(pWallets).getWallet(walletId);
+ if (wallet is! OrdinalsInterface) return;
+
+ final usedUtxos = widget.txData.usedUTXOs;
+ if (usedUtxos == null || usedUtxos.isEmpty) return;
+
+ final db = ref.read(mainDBProvider);
+ for (final input in usedUtxos) {
+ if (input is! StandardInput) continue;
+ final ordinal = await db.isar.ordinals
+ .where()
+ .filter()
+ .walletIdEqualTo(walletId)
+ .and()
+ .utxoTXIDEqualTo(input.utxo.txid)
+ .and()
+ .utxoVOUTEqualTo(input.utxo.vout)
+ .findFirst();
+ if (ordinal != null) {
+ if (mounted) {
+ setState(() => _spendsOrdinal = true);
+ }
+ return;
+ }
+ }
+ }
+
/// Handle MWC slatepack creation for manual exchange.
Future<void> _handleMwcSlatepackCreation(
BuildContext context,
@@ -537,6 +568,8 @@ class _ConfirmTransactionViewState
onChainNoteController.text = widget.txData.noteOnChain ?? "";
super.initState();
+
+ _checkForOrdinalSpend();
}
@override
@@ -1421,71 +1454,40 @@ class _ConfirmTransactionViewState
),
),
),
- // Ordinal UTXO spend warning
- Builder(
- builder: (context) {
- final usedUtxos = widget.txData.usedUTXOs;
- if (usedUtxos == null || usedUtxos.isEmpty) {
- return const SizedBox.shrink();
- }
-
- final db = ref.read(mainDBProvider);
- bool hasOrdinal = false;
- for (final input in usedUtxos) {
- if (input is StandardInput) {
- final ordinal = db.isar.ordinals
- .where()
- .filter()
- .walletIdEqualTo(walletId)
- .and()
- .utxoTXIDEqualTo(input.utxo.txid)
- .and()
- .utxoVOUTEqualTo(input.utxo.vout)
- .findFirstSync();
- if (ordinal != null) {
- hasOrdinal = true;
- break;
- }
- }
- }
-
- if (!hasOrdinal) return const SizedBox.shrink();
-
- return Padding(
- padding: isDesktop
- ? const EdgeInsets.symmetric(horizontal: 32, vertical: 8)
- : const EdgeInsets.symmetric(vertical: 8),
- child: RoundedContainer(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.warningBackground,
- child: Row(
- children: [
- Icon(
- Icons.warning_amber_rounded,
- color: Theme.of(
- context,
- ).extension<StackColors>()!.warningForeground,
- size: 20,
- ),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- "This transaction spends a UTXO containing "
- "an ordinal inscription.",
- style: STextStyles.smallMed12(context).copyWith(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.warningForeground,
- ),
+ if (_spendsOrdinal)
+ Padding(
+ padding: isDesktop
+ ? const EdgeInsets.symmetric(horizontal: 32, vertical: 8)
+ : const EdgeInsets.symmetric(vertical: 8),
+ child: RoundedContainer(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.warningBackground,
+ child: Row(
+ children: [
+ Icon(
+ Icons.warning_amber_rounded,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.warningForeground,
+ size: 20,
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Text(
+ "This transaction spends a UTXO containing "
+ "an ordinal inscription.",
+ style: STextStyles.smallMed12(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.warningForeground,
),
),
- ],
- ),
+ ),
+ ],
),
- );
- },
- ),
+ ),
+ ),
SizedBox(height: isDesktop ? 28 : 16),
Padding(
padding: isDesktop
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.