From 1f9e794dd506dcd7a5de04fc592c47f2d76d85df Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Wed, 3 Jun 2026 22:19:37 +0200 Subject: [PATCH] fix(receive): guard wallet-address rendering against RangeError on short input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QRAddressWidget sliced the address with fixed indices (substring 0,6 / 6,21 / 21,36 / 36), which threw a RangeError for any address shorter than 36 characters — crashing both the Receive screen and Settings → Wallet address. Replace the raw slices with a length-clamping helper: a full 0x-address still groups into the same chunks, while a short or empty address renders gracefully instead of crashing. Regression: test/screens/receive/widgets/qr_address_widget_test.dart ("renders a short/unexpected address without a RangeError") Issue #657 — Part 6, finding F1 (HIGH, crash). --- .../receive/widgets/qr_address_widget.dart | 18 ++++++++++++++---- .../widgets/qr_address_widget_test.dart | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/screens/receive/widgets/qr_address_widget.dart b/lib/screens/receive/widgets/qr_address_widget.dart index 677ba900..2d189c08 100644 --- a/lib/screens/receive/widgets/qr_address_widget.dart +++ b/lib/screens/receive/widgets/qr_address_widget.dart @@ -39,20 +39,20 @@ class QRAddressWidget extends StatelessWidget { TextSpan( children: [ TextSpan( - text: subtitle.substring(0, 6), + text: _slice(subtitle, 0, 6), style: const TextStyle(fontWeight: .bold), ), const TextSpan(text: ' '), TextSpan( - text: subtitle.substring(6, 21), + text: _slice(subtitle, 6, 21), ), const TextSpan(text: '\n'), TextSpan( - text: subtitle.substring(21, 36), + text: _slice(subtitle, 21, 36), ), const TextSpan(text: ' '), TextSpan( - text: subtitle.substring(36), + text: _slice(subtitle, 36), style: const TextStyle(fontWeight: .bold), ), ], @@ -72,4 +72,14 @@ class QRAddressWidget extends StatelessWidget { ); Future _copyToClipboard() => Clipboard.setData(ClipboardData(text: subtitle)); + + // Length-safe slice: a full 0x-address is grouped into fixed chunks, but a + // short or empty address must not crash with a RangeError (issue #657 P6 — + // this widget renders on both Receive and Settings). Clamps the bounds to + // the string length instead of slicing past the end. + static String _slice(String value, int start, [int? end]) { + if (start >= value.length) return ''; + final clampedEnd = end == null || end > value.length ? value.length : end; + return value.substring(start, clampedEnd); + } } diff --git a/test/screens/receive/widgets/qr_address_widget_test.dart b/test/screens/receive/widgets/qr_address_widget_test.dart index abac737f..72ef8186 100644 --- a/test/screens/receive/widgets/qr_address_widget_test.dart +++ b/test/screens/receive/widgets/qr_address_widget_test.dart @@ -47,5 +47,24 @@ void main() { await tester.tap(find.byType(InkWell)); await tester.pump(); }); + + testWidgets( + 'renders a short/unexpected address without a RangeError ' + '(issue #657 P6 regression)', (tester) async { + // A too-short subtitle used to crash on the fixed-index substring(6, 21) + // etc. — it must now render gracefully on Receive and Settings. + await tester.pumpWidget(_host( + const QRAddressWidget(uri: '', subtitle: '0x1234'), + )); + + expect(tester.takeException(), isNull); + expect(find.textContaining('0x1234'), findsAtLeastNWidgets(1)); + + // The extreme case: an empty address must also not throw. + await tester.pumpWidget(_host( + const QRAddressWidget(uri: '', subtitle: ''), + )); + expect(tester.takeException(), isNull); + }); }); }