Enforce passphrase confirmation match on the restore and create wallet flows (#3474)

* fix: enforce passphrase confirmation in restore passphrase bottom sheet The confirm-passphrase field in AddPassphraseBottomSheet already had a validator comparing it to the first field, but the fields were not inside a Form and nothing ever called validate(), so the validator never ran. Tapping "Restore" restored the wallet with the first field's value even when the confirmation did not match, defeating the typo check. Wrap both fields in a Form and validate it before completing the restore, matching the existing pattern in advanced_privacy_settings_page.dart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DtLNfYw1H81p7zZrkNDXM * Validate passphrase confirmation when the passphrase field is empty --------- Co-authored-by: Claude <noreply@anthropic.com>

claude[bot] committed Aug 2, 2026 at 09:43 UTC f6566e3bafcd8d6cc29caf072eb3db98bce1273d
2 files changed +62 -49
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+3 -5
@@ -360,11 +360,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
360
361 widget.nodeViewModel.save();
362 }
363 - if (passphraseController.text.isNotEmpty) {
364 - if (_passphraseFormKey.currentState != null &&
365 - !_passphraseFormKey.currentState!.validate()) {
366 - return;
367 - }
363 + if (_passphraseFormKey.currentState != null &&
364 + !_passphraseFormKey.currentState!.validate()) {
365 + return;
366 }
367
368 widget.seedTypeViewModel.setPassphrase(passphraseController.text);
lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart
+59 -44
@@ -20,6 +20,7 @@ class AddPassphraseBottomSheet extends StatefulWidget {
20 class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
21 late final TextEditingController passphraseController;
22 late final TextEditingController confirmPassphraseController;
23 + final _passphraseFormKey = GlobalKey<FormState>();
24
25 @override
26 void initState() {
@@ -112,51 +113,60 @@ class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
113 ),
114 ),
115 SizedBox(height: 24),
115 - BaseTextFormField(
116 - key: ValueKey('add_passphrase_bottom_sheet_widget_passphrase_textfield_key'),
117 - controller: passphraseController,
118 - obscureText: obscurePassphrase,
119 - contentPadding: EdgeInsets.symmetric(horizontal: 12),
120 - hintText: S.of(context).required_passphrase,
121 - suffixIcon: GestureDetector(
122 - onTap: () {
123 - setState(() {
124 - obscurePassphrase = !obscurePassphrase;
125 - });
126 - },
127 - child: Icon(
128 - obscurePassphrase ? Icons.visibility_off : Icons.visibility,
129 - size: 24,
130 - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
131 - ),
132 - ),
133 - ),
134 - SizedBox(height: 8),
135 - BaseTextFormField(
136 - key: ValueKey('add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key'),
137 - controller: confirmPassphraseController,
138 - obscureText: obscurePassphrase,
139 - contentPadding: EdgeInsets.symmetric(horizontal: 12),
140 - hintText: S.of(context).confirm_passphrase,
141 - suffixIcon: GestureDetector(
142 - onTap: () {
143 - setState(() {
144 - obscurePassphrase = !obscurePassphrase;
145 - });
146 - },
147 - child: Icon(
148 - obscurePassphrase ? Icons.visibility_off : Icons.visibility,
149 - size: 24,
150 - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
151 - ),
152 - ),
153 - validator: (text) {
154 - if (text == passphraseController.text) {
155 - return null;
156 - }
116 + Form(
117 + key: _passphraseFormKey,
118 + child: Column(
119 + children: [
120 + BaseTextFormField(
121 + key: ValueKey('add_passphrase_bottom_sheet_widget_passphrase_textfield_key'),
122 + controller: passphraseController,
123 + obscureText: obscurePassphrase,
124 + contentPadding: EdgeInsets.symmetric(horizontal: 12),
125 + hintText: S.of(context).required_passphrase,
126 + suffixIcon: GestureDetector(
127 + onTap: () {
128 + setState(() {
129 + obscurePassphrase = !obscurePassphrase;
130 + });
131 + },
132 + child: Icon(
133 + obscurePassphrase ? Icons.visibility_off : Icons.visibility,
134 + size: 24,
135 + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
136 + ),
137 + ),
138 + ),
139 + SizedBox(height: 8),
140 + BaseTextFormField(
141 + key: ValueKey(
142 + 'add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key',
143 + ),
144 + controller: confirmPassphraseController,
145 + obscureText: obscurePassphrase,
146 + contentPadding: EdgeInsets.symmetric(horizontal: 12),
147 + hintText: S.of(context).confirm_passphrase,
148 + suffixIcon: GestureDetector(
149 + onTap: () {
150 + setState(() {
151 + obscurePassphrase = !obscurePassphrase;
152 + });
153 + },
154 + child: Icon(
155 + obscurePassphrase ? Icons.visibility_off : Icons.visibility,
156 + size: 24,
157 + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
158 + ),
159 + ),
160 + validator: (text) {
161 + if (text == passphraseController.text) {
162 + return null;
163 + }
164
158 - return S.of(context).passphrases_doesnt_match;
159 - },
165 + return S.of(context).passphrases_doesnt_match;
166 + },
167 + ),
168 + ],
169 + ),
170 ),
171 SizedBox(height: 16),
172 Padding(
@@ -184,6 +194,11 @@ class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
194 child: PrimaryButton(
195 key: ValueKey('add_passphrase_bottom_sheet_widget_restore_button_key'),
196 onPressed: () {
197 + if (_passphraseFormKey.currentState != null &&
198 + !_passphraseFormKey.currentState!.validate()) {
199 + return;
200 + }
201 +
202 Navigator.pop(context);
203 widget.onRestoreButtonPressed(passphraseController.text);
204 },