@@ -160,7 +160,6 @@ class _SendViewState extends ConsumerState<SendView> {
160160 try {
161161 // auto fill address
162162 _address = paymentData.address.trim ();
163- sendToController.text = _address! ;
164163
165164 // autofill notes field
166165 if (paymentData.message != null ) {
@@ -180,7 +179,25 @@ class _SendViewState extends ConsumerState<SendView> {
180179 ref.read (pSendAmount.notifier).state = amount;
181180 }
182181
182+ // Extract OP_RETURN data if present (for Rosen Bridge and other protocols)
183+ // Must be set BEFORE sendToController.text to avoid re-entrant
184+ // onChanged handler reading stale null value.
185+ if (paymentData.additionalParams.containsKey ('op_return' )) {
186+ final data = paymentData.additionalParams['op_return' ];
187+ ref.read (pOpReturnData.notifier).state = data;
188+ Logging .instance.i (
189+ "Extracted OP_RETURN data from URI, length: ${data !.length ~/ 2 } bytes" ,
190+ );
191+ } else {
192+ ref.read (pOpReturnData.notifier).state = null ;
193+ }
194+
183195 _setValidAddressProviders (_address);
196+
197+ // Assign controller.text last — it triggers onChanged which depends
198+ // on pOpReturnData already being set above.
199+ sendToController.text = _address! ;
200+
184201 setState (() {
185202 _addressToggleFlag = sendToController.text.isNotEmpty;
186203 });
@@ -923,6 +940,7 @@ class _SendViewState extends ConsumerState<SendView> {
923940 selectedUTXOs.isNotEmpty)
924941 ? selectedUTXOs
925942 : null ,
943+ opReturnData: ref.read (pOpReturnData),
926944 ),
927945 );
928946 } else if (wallet is FiroWallet ) {
@@ -964,6 +982,7 @@ class _SendViewState extends ConsumerState<SendView> {
964982 utxos: (coinControlEnabled && selectedUTXOs.isNotEmpty)
965983 ? selectedUTXOs
966984 : null ,
985+ opReturnData: ref.read (pOpReturnData),
967986 ),
968987 );
969988 }
@@ -1136,6 +1155,7 @@ class _SendViewState extends ConsumerState<SendView> {
11361155 memoController.text = "" ;
11371156 _address = "" ;
11381157 _addressToggleFlag = false ;
1158+ ref.read (pOpReturnData.notifier).state = null ;
11391159 if (mounted) {
11401160 setState (() {});
11411161 }
@@ -1726,9 +1746,10 @@ class _SendViewState extends ConsumerState<SendView> {
17261746 final trimmed = newValue.trim ();
17271747
17281748 if ((trimmed.length -
1729- (_address? .length ?? 0 ))
1730- .abs () >
1731- 1 ) {
1749+ (_address? .length ?? 0 ))
1750+ .abs () >
1751+ 1 ||
1752+ trimmed.contains (':' )) {
17321753 final parsed =
17331754 AddressUtils .parsePaymentUri (
17341755 trimmed,
@@ -1737,6 +1758,8 @@ class _SendViewState extends ConsumerState<SendView> {
17371758 if (parsed != null ) {
17381759 _applyUri (parsed);
17391760 } else {
1761+ ref.read (pOpReturnData.notifier).state =
1762+ null ;
17401763 await _checkSparkNameAndOrSetAddress (
17411764 newValue,
17421765 );
@@ -1949,6 +1972,38 @@ class _SendViewState extends ConsumerState<SendView> {
19491972 ),
19501973 ),
19511974 ),
1975+ if (ref.watch (pOpReturnData) != null &&
1976+ _address != null &&
1977+ _address! .isNotEmpty &&
1978+ (ref.watch (pValidSendToAddress) ||
1979+ ref.watch (pValidSparkSendToAddress)) &&
1980+ balType == BalanceType .public)
1981+ Align (
1982+ alignment: Alignment .topLeft,
1983+ child: Padding (
1984+ padding: const EdgeInsets .only (
1985+ left: 12.0 ,
1986+ top: 4.0 ,
1987+ ),
1988+ child: Tooltip (
1989+ message: AddressUtils .formatOpReturnTooltip (
1990+ ref.watch (pOpReturnData)! ,
1991+ ),
1992+ child: Text (
1993+ "Transaction includes metadata "
1994+ "(${ref .watch (pOpReturnData )!.length ~/ 2 } bytes) "
1995+ "\u 2014 tap for details" ,
1996+ textAlign: TextAlign .left,
1997+ style: STextStyles .label (context)
1998+ .copyWith (
1999+ color: Theme .of (context)
2000+ .extension < StackColors > ()!
2001+ .accentColorGreen,
2002+ ),
2003+ ),
2004+ ),
2005+ ),
2006+ ),
19522007 Builder (
19532008 builder: (_) {
19542009 final String ? error;
@@ -2666,16 +2721,42 @@ class _SendViewState extends ConsumerState<SendView> {
26662721 ),
26672722 const Spacer (),
26682723 const SizedBox (height: 12 ),
2724+ if (ref.watch (pOpReturnData) != null &&
2725+ balType == BalanceType .private)
2726+ Padding (
2727+ padding: const EdgeInsets .only (
2728+ left: 12.0 ,
2729+ right: 12.0 ,
2730+ bottom: 12.0 ,
2731+ ),
2732+ child: Text (
2733+ "Bridge data detected but Spark (private) "
2734+ "transactions cannot carry OP_RETURN data. "
2735+ "Switch to public balance to complete the "
2736+ "bridge transaction." ,
2737+ textAlign: TextAlign .left,
2738+ style: STextStyles .label (context).copyWith (
2739+ color: Theme .of (
2740+ context,
2741+ ).extension < StackColors > ()! .textError,
2742+ ),
2743+ ),
2744+ ),
26692745 TextButton (
26702746 onPressed:
2671- ref.watch (pPreviewTxButtonEnabled (coin))
2747+ ref.watch (pPreviewTxButtonEnabled (coin)) &&
2748+ (ref.watch (pOpReturnData) == null ||
2749+ balType != BalanceType .private)
26722750 ? isMwcSlatepack
26732751 ? _createSlatepack
26742752 : isEpicSlatepack
26752753 ? _createEpicSlatepack
26762754 : _previewTransaction
26772755 : null ,
2678- style: ref.watch (pPreviewTxButtonEnabled (coin))
2756+ style:
2757+ ref.watch (pPreviewTxButtonEnabled (coin)) &&
2758+ (ref.watch (pOpReturnData) == null ||
2759+ balType != BalanceType .private)
26792760 ? Theme .of (context)
26802761 .extension < StackColors > ()!
26812762 .getPrimaryEnabledButtonStyle (context)
0 commit comments