@@ -5,8 +5,12 @@ import 'package:flutter/material.dart';
55import 'package:flutter_riverpod/flutter_riverpod.dart' ;
66import 'package:tuple/tuple.dart' ;
77
8+ import '../../models/isar/models/ethereum/eth_contract.dart' ;
89import '../../models/send_view_auto_fill_data.dart' ;
910import '../../notifications/show_flush_bar.dart' ;
11+ import '../../providers/db/main_db_provider.dart' ;
12+ import '../../providers/providers.dart' ;
13+ import '../../services/open_crypto_pay/evm_uri.dart' ;
1014import '../../services/open_crypto_pay/method_support.dart' ;
1115import '../../services/open_crypto_pay/models.dart' ;
1216import '../../services/open_crypto_pay/open_crypto_pay_api.dart' ;
@@ -15,12 +19,18 @@ import '../../utilities/address_utils.dart';
1519import '../../utilities/logger.dart' ;
1620import '../../utilities/text_styles.dart' ;
1721import '../../wallets/crypto_currency/crypto_currency.dart' ;
22+ import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart' ;
23+ import '../../wallets/isar/providers/wallet_info_provider.dart' ;
24+ import '../../wallets/wallet/impl/ethereum_wallet.dart' ;
25+ import '../../wallets/wallet/impl/sub_wallets/eth_token_wallet.dart' ;
26+ import '../../wallets/wallet/wallet.dart' ;
1827import '../../widgets/background.dart' ;
1928import '../../widgets/custom_buttons/app_bar_icon_button.dart' ;
2029import '../../widgets/desktop/primary_button.dart' ;
2130import '../../widgets/loading_indicator.dart' ;
2231import '../../widgets/rounded_white_container.dart' ;
2332import '../send_view/send_view.dart' ;
33+ import '../send_view/token_send_view.dart' ;
2434
2535enum OpenCryptoPayConfirmResult { quoteExpired }
2636
@@ -102,6 +112,18 @@ class _OpenCryptoPayConfirmViewState
102112 /// attached to the address.
103113 ({String ? address, Decimal ? amount, int ? chainId, String ? scheme})
104114 _parseTransactionUri (String uri) {
115+ final evmUri = OpenCryptoPayEvmUri .tryParse (uri);
116+ if (evmUri != null && ! evmUri.isTokenTransfer) {
117+ return (
118+ address: evmUri.targetAddress,
119+ amount: evmUri.isNativeTransfer
120+ ? evmUri.amount (fractionDigits: widget.coin.fractionDigits)
121+ : Decimal .tryParse (widget.selectedAsset.amount),
122+ chainId: evmUri.chainId,
123+ scheme: evmUri.scheme,
124+ );
125+ }
126+
105127 final parsedUri = Uri .tryParse (uri);
106128 final data = AddressUtils .parsePaymentUri (uri, logging: Logging .instance);
107129 var address = data? .address ?? parsedUri? .path;
@@ -125,6 +147,37 @@ class _OpenCryptoPayConfirmViewState
125147 );
126148 }
127149
150+ EthContract ? _enabledErc20Token (String contractAddress) {
151+ final normalized = contractAddress.toLowerCase ();
152+ final mainDB = ref.read (mainDBProvider);
153+ for (final address in ref.read (pWalletTokenAddresses (widget.walletId))) {
154+ final contract = mainDB.getEthContractSync (address);
155+ if (contract == null || contract.type != EthContractType .erc20) {
156+ continue ;
157+ }
158+ if (contract.address.toLowerCase () == normalized) {
159+ return contract;
160+ }
161+ }
162+ return null ;
163+ }
164+
165+ Future <EthTokenWallet > _loadTokenWallet (EthContract contract) async {
166+ final wallet = ref.read (pWallets).getWallet (widget.walletId);
167+ if (wallet is ! EthereumWallet ) {
168+ throw Exception ("Ethereum wallet not loaded" );
169+ }
170+
171+ final old = ref.read (tokenServiceStateProvider);
172+ final tokenWallet =
173+ Wallet .loadTokenWallet (ethWallet: wallet, contract: contract)
174+ as EthTokenWallet ;
175+ await tokenWallet.init ();
176+ unawaited (old? .exit ());
177+ ref.read (tokenServiceStateProvider.state).state = tokenWallet;
178+ return tokenWallet;
179+ }
180+
128181 Future <void > _proceedToSend () async {
129182 if (_isExpired) {
130183 _warn ("Quote expired, refreshing..." );
@@ -140,32 +193,11 @@ class _OpenCryptoPayConfirmViewState
140193 return ;
141194 }
142195
143- final parsed = _parseTransactionUri (uri);
144- if (parsed.address == null ) {
145- _warn ("Could not parse payment address" );
146- return ;
147- }
148- if (parsed.amount == null ) {
149- _warn ("Could not parse payment amount" );
150- return ;
151- }
152- if (parsed.scheme != null &&
153- parsed.scheme! .isNotEmpty &&
154- parsed.scheme != widget.coin.uriScheme) {
155- _warn ("Payment URI does not match this wallet" );
156- return ;
157- }
158196 if (_txDetails? .blockchain != null &&
159197 _txDetails! .blockchain != widget.selectedMethod.method) {
160198 _warn ("Payment details do not match the selected method" );
161199 return ;
162200 }
163- if (widget.selectedMethod.method == 'Ethereum' &&
164- parsed.chainId != null &&
165- parsed.chainId != 1 ) {
166- _warn ("Payment URI is for a different Ethereum network" );
167- return ;
168- }
169201
170202 final submissionFlow = OpenCryptoPayMethodSupport .submissionFlowFor (
171203 widget.selectedMethod.method,
@@ -187,6 +219,63 @@ class _OpenCryptoPayConfirmViewState
187219 widget.paymentDetails.displayName ??
188220 "OpenCryptoPay" ;
189221
222+ final evmUri = widget.selectedMethod.method == 'Ethereum'
223+ ? OpenCryptoPayEvmUri .tryParse (uri)
224+ : null ;
225+ if (widget.selectedMethod.method == 'Ethereum' ) {
226+ if (evmUri == null ) {
227+ _warn ("Could not parse Ethereum payment details" );
228+ return ;
229+ }
230+ if (evmUri.chainId != null && evmUri.chainId != 1 ) {
231+ _warn ("Payment URI is for a different Ethereum network" );
232+ return ;
233+ }
234+ if (evmUri.functionName != null && ! evmUri.isTokenTransfer) {
235+ _warn ("Unsupported Ethereum payment request" );
236+ return ;
237+ }
238+ if (evmUri.isTokenTransfer) {
239+ if (evmUri.chainId != 1 ) {
240+ _warn ("Payment URI is for a different Ethereum network" );
241+ return ;
242+ }
243+ if (widget.selectedAsset.asset.toUpperCase () ==
244+ widget.coin.ticker.toUpperCase ()) {
245+ _warn ("Payment token details are invalid" );
246+ return ;
247+ }
248+ await _proceedToTokenSend (
249+ evmUri: evmUri,
250+ expiresAt: expiresAt,
251+ recipient: recipient,
252+ submissionFlow: submissionFlow,
253+ );
254+ return ;
255+ }
256+ if (widget.selectedAsset.asset.toUpperCase () !=
257+ widget.coin.ticker.toUpperCase ()) {
258+ _warn ("Payment token details are invalid" );
259+ return ;
260+ }
261+ }
262+
263+ final parsed = _parseTransactionUri (uri);
264+ if (parsed.address == null ) {
265+ _warn ("Could not parse payment address" );
266+ return ;
267+ }
268+ if (parsed.amount == null ) {
269+ _warn ("Could not parse payment amount" );
270+ return ;
271+ }
272+ if (parsed.scheme != null &&
273+ parsed.scheme! .isNotEmpty &&
274+ parsed.scheme != widget.coin.uriScheme) {
275+ _warn ("Payment URI does not match this wallet" );
276+ return ;
277+ }
278+
190279 if (! mounted) return ;
191280 await Navigator .of (context).pushNamed (
192281 SendView .routeName,
@@ -214,6 +303,65 @@ class _OpenCryptoPayConfirmViewState
214303 );
215304 }
216305
306+ Future <void > _proceedToTokenSend ({
307+ required OpenCryptoPayEvmUri evmUri,
308+ required DateTime expiresAt,
309+ required String recipient,
310+ required OpenCryptoPaySubmissionFlow submissionFlow,
311+ }) async {
312+ final contract = _enabledErc20Token (evmUri.targetAddress);
313+ if (contract == null ) {
314+ _warn ("This token is not enabled in this wallet" );
315+ return ;
316+ }
317+ if (contract.symbol.toUpperCase () !=
318+ widget.selectedAsset.asset.toUpperCase ()) {
319+ _warn ("Payment token does not match the selected asset" );
320+ return ;
321+ }
322+
323+ try {
324+ await _loadTokenWallet (contract);
325+ } catch (e, s) {
326+ Logging .instance.e (
327+ "OpenCryptoPay token wallet load failed" ,
328+ error: e,
329+ stackTrace: s,
330+ );
331+ _warn ("Could not load token wallet" );
332+ return ;
333+ }
334+
335+ final amount = evmUri.amount (fractionDigits: contract.decimals);
336+ if (! mounted) return ;
337+ await Navigator .of (context).pushNamed (
338+ TokenSendView .routeName,
339+ arguments: Tuple4 (
340+ widget.walletId,
341+ widget.coin,
342+ contract,
343+ SendViewAutoFillData (
344+ address: evmUri.recipientAddress! ,
345+ contactLabel: recipient,
346+ amount: amount,
347+ note: "OpenCryptoPay: $recipient " ,
348+ openCryptoPayCommit: OpenCryptoPayCommit (
349+ callbackUrl: widget.paymentDetails.callback,
350+ quoteId: widget.paymentDetails.quote! .id,
351+ method: widget.selectedMethod.method,
352+ asset: widget.selectedAsset.asset,
353+ expiresAt: expiresAt,
354+ submissionFlow: submissionFlow,
355+ minFee: widget.selectedMethod.minFee,
356+ recipientAddress: evmUri.recipientAddress! ,
357+ amount: amount,
358+ tokenContractAddress: contract.address,
359+ ),
360+ ),
361+ ),
362+ );
363+ }
364+
217365 void _warn (String message) {
218366 unawaited (
219367 showFloatingFlushBar (
0 commit comments