@@ -159,7 +159,6 @@ class _SendViewState extends ConsumerState<SendView> {
159159 try {
160160 // auto fill address
161161 _address = paymentData.address.trim ();
162- sendToController.text = _address! ;
163162
164163 // autofill notes field
165164 if (paymentData.message != null ) {
@@ -179,7 +178,25 @@ class _SendViewState extends ConsumerState<SendView> {
179178 ref.read (pSendAmount.notifier).state = amount;
180179 }
181180
181+ // Extract OP_RETURN data if present (for Rosen Bridge and other protocols)
182+ // Must be set BEFORE sendToController.text to avoid re-entrant
183+ // onChanged handler reading stale null value.
184+ if (paymentData.additionalParams.containsKey ('op_return' )) {
185+ final data = paymentData.additionalParams['op_return' ];
186+ ref.read (pOpReturnData.notifier).state = data;
187+ Logging .instance.i (
188+ "Extracted OP_RETURN data from URI, length: ${data !.length ~/ 2 } bytes" ,
189+ );
190+ } else {
191+ ref.read (pOpReturnData.notifier).state = null ;
192+ }
193+
182194 _setValidAddressProviders (_address);
195+
196+ // Assign controller.text last — it triggers onChanged which depends
197+ // on pOpReturnData already being set above.
198+ sendToController.text = _address! ;
199+
183200 setState (() {
184201 _addressToggleFlag = sendToController.text.isNotEmpty;
185202 });
@@ -919,6 +936,7 @@ class _SendViewState extends ConsumerState<SendView> {
919936 selectedUTXOs.isNotEmpty)
920937 ? selectedUTXOs
921938 : null ,
939+ opReturnData: ref.read (pOpReturnData),
922940 ),
923941 );
924942 } else if (wallet is FiroWallet ) {
@@ -960,6 +978,7 @@ class _SendViewState extends ConsumerState<SendView> {
960978 utxos: (coinControlEnabled && selectedUTXOs.isNotEmpty)
961979 ? selectedUTXOs
962980 : null ,
981+ opReturnData: ref.read (pOpReturnData),
963982 ),
964983 );
965984 }
@@ -1131,6 +1150,7 @@ class _SendViewState extends ConsumerState<SendView> {
11311150 memoController.text = "" ;
11321151 _address = "" ;
11331152 _addressToggleFlag = false ;
1153+ ref.read (pOpReturnData.notifier).state = null ;
11341154 if (mounted) {
11351155 setState (() {});
11361156 }
@@ -1720,9 +1740,10 @@ class _SendViewState extends ConsumerState<SendView> {
17201740 final trimmed = newValue.trim ();
17211741
17221742 if ((trimmed.length -
1723- (_address? .length ?? 0 ))
1724- .abs () >
1725- 1 ) {
1743+ (_address? .length ?? 0 ))
1744+ .abs () >
1745+ 1 ||
1746+ trimmed.contains (':' )) {
17261747 final parsed =
17271748 AddressUtils .parsePaymentUri (
17281749 trimmed,
@@ -1731,6 +1752,8 @@ class _SendViewState extends ConsumerState<SendView> {
17311752 if (parsed != null ) {
17321753 _applyUri (parsed);
17331754 } else {
1755+ ref.read (pOpReturnData.notifier).state =
1756+ null ;
17341757 await _checkSparkNameAndOrSetAddress (
17351758 newValue,
17361759 );
@@ -1943,6 +1966,38 @@ class _SendViewState extends ConsumerState<SendView> {
19431966 ),
19441967 ),
19451968 ),
1969+ if (ref.watch (pOpReturnData) != null &&
1970+ _address != null &&
1971+ _address! .isNotEmpty &&
1972+ (ref.watch (pValidSendToAddress) ||
1973+ ref.watch (pValidSparkSendToAddress)) &&
1974+ balType == BalanceType .public)
1975+ Align (
1976+ alignment: Alignment .topLeft,
1977+ child: Padding (
1978+ padding: const EdgeInsets .only (
1979+ left: 12.0 ,
1980+ top: 4.0 ,
1981+ ),
1982+ child: Tooltip (
1983+ message: AddressUtils .formatOpReturnTooltip (
1984+ ref.watch (pOpReturnData)! ,
1985+ ),
1986+ child: Text (
1987+ "Transaction includes metadata "
1988+ "(${ref .watch (pOpReturnData )!.length ~/ 2 } bytes) "
1989+ "\u 2014 tap for details" ,
1990+ textAlign: TextAlign .left,
1991+ style: STextStyles .label (context)
1992+ .copyWith (
1993+ color: Theme .of (context)
1994+ .extension < StackColors > ()!
1995+ .accentColorGreen,
1996+ ),
1997+ ),
1998+ ),
1999+ ),
2000+ ),
19462001 Builder (
19472002 builder: (_) {
19482003 final String ? error;
@@ -2660,16 +2715,42 @@ class _SendViewState extends ConsumerState<SendView> {
26602715 ),
26612716 const Spacer (),
26622717 const SizedBox (height: 12 ),
2718+ if (ref.watch (pOpReturnData) != null &&
2719+ balType == BalanceType .private)
2720+ Padding (
2721+ padding: const EdgeInsets .only (
2722+ left: 12.0 ,
2723+ right: 12.0 ,
2724+ bottom: 12.0 ,
2725+ ),
2726+ child: Text (
2727+ "Bridge data detected but Spark (private) "
2728+ "transactions cannot carry OP_RETURN data. "
2729+ "Switch to public balance to complete the "
2730+ "bridge transaction." ,
2731+ textAlign: TextAlign .left,
2732+ style: STextStyles .label (context).copyWith (
2733+ color: Theme .of (
2734+ context,
2735+ ).extension < StackColors > ()! .textError,
2736+ ),
2737+ ),
2738+ ),
26632739 TextButton (
26642740 onPressed:
2665- ref.watch (pPreviewTxButtonEnabled (coin))
2741+ ref.watch (pPreviewTxButtonEnabled (coin)) &&
2742+ (ref.watch (pOpReturnData) == null ||
2743+ balType != BalanceType .private)
26662744 ? isMwcSlatepack
26672745 ? _createSlatepack
26682746 : isEpicSlatepack
26692747 ? _createEpicSlatepack
26702748 : _previewTransaction
26712749 : null ,
2672- style: ref.watch (pPreviewTxButtonEnabled (coin))
2750+ style:
2751+ ref.watch (pPreviewTxButtonEnabled (coin)) &&
2752+ (ref.watch (pOpReturnData) == null ||
2753+ balType != BalanceType .private)
26732754 ? Theme .of (context)
26742755 .extension < StackColors > ()!
26752756 .getPrimaryEnabledButtonStyle (context)
0 commit comments