refactor send validation and near token parsing (#3351)
* refactor send validation and near token parsing * fix exchange send validation[skip ci]
Serhii committed
Jul 13, 2026 at 04:23 UTC
0cf80439ecadee4cbb77767ffeb6adf4820ace81
2 files changed
+74
-49
lib/exchange/provider/near_Intents_exchange_provider.dart
+13
-8
@@ -271,17 +271,15 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
271
throw Exception('Failed to parse to currency from assetId: $toAssetId');
272
}
273
274
- final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency.$1,
275
- tag: fromCurrency.$2);
276
- final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency.$1,
277
- tag: toCurrency.$2);
274
+ final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency.$1, tag: fromCurrency.$2);
275
+ final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency.$1, tag: toCurrency.$2);
276
277
278
final trade = Trade(
279
id: depositAddress,
280
// Using deposit address as trade ID
281
from: request.fromCurrency,
284
- to: request.toCurrency,
282
+ to: request.fromCurrency,
283
provider: description,
284
providerName: title,
285
state: TradeState.created,
@@ -575,10 +573,17 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
573
final token = supported.firstWhereOrNull((t) => t.assetId == assetId);
574
575
if (token == null) return null;
578
- final title = token.symbol;
576
+
577
+ final title = token.symbol.toUpperCase()
578
+ .replaceAll(RegExp(r'\s*\([^)]*\)'), '');
579
+
580
final normalizedNetwork = _normalizeNearBlockchainToTag(token.blockchain);
580
- final tag =
581
- normalizedNetwork == title.toUpperCase() ? null : normalizedNetwork;
581
+
582
+ final isNativeAsset = assetId.contains(':native:coin');
583
+
584
+ final tag = isNativeAsset || normalizedNetwork == title
585
+ ? null
586
+ : normalizedNetwork;
587
588
return (title, tag);
589
}
lib/view_model/exchange/exchange_trade_view_model.dart
+61
-41
@@ -1,6 +1,7 @@
1
import 'dart:async';
2
3
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
4
+import 'package:cake_wallet/core/execution_state.dart';
5
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
6
import 'package:cake_wallet/entities/fiat_currency.dart';
7
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
@@ -24,6 +25,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart';
25
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
26
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
27
import 'package:cake_wallet/store/dashboard/trades_store.dart';
28
+import 'package:cake_wallet/utils/exchange_provider_logger.dart';
29
import 'package:cake_wallet/utils/qr_util.dart';
30
import 'package:cake_wallet/utils/token_utilities.dart';
31
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
@@ -48,7 +50,6 @@ abstract class ExchangeTradeViewModelBase with Store {
50
required this.feesViewModel,
51
required this.fiatConversionStore,
52
}) : trade = tradesStore.trade!,
51
- isSendable = _checkIfCanSend(tradesStore, wallet),
53
isSwapsXYZCanSendFromExternal = _checkIfSwapsXYZCanSendFromExternal(tradesStore.trade!, wallet),
54
items = ObservableList<ExchangeTradeItem>() {
55
setUpOutput();
@@ -115,12 +116,10 @@ abstract class ExchangeTradeViewModelBase with Store {
116
@observable
117
Trade trade;
118
118
- @observable
119
- bool isSendable;
120
-
121
-
119
bool isSwapsXYZCanSendFromExternal;
120
121
+ bool get isSendable => checkIfCanSend(trade, wallet) == null;
122
+
123
/// Providers that should hide the "send from external" button
124
static const List<Type> _providersThatHideExternalSend = [
125
JupiterExchangeProvider,
@@ -212,7 +211,13 @@ abstract class ExchangeTradeViewModelBase with Store {
211
212
@action
213
Future<void> confirmSending() async {
215
- if (!isSendable) return;
214
+ final canSendError = checkIfCanSend(trade, wallet);
215
+
216
+ if (canSendError != null) {
217
+ sendViewModel.state = FailureState(canSendError);
218
+ return;
219
+ }
220
+
221
222
final selected = trade.from;
223
if (selected == null) {
@@ -348,48 +353,63 @@ abstract class ExchangeTradeViewModelBase with Store {
353
);
354
}
355
351
- static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) {
352
- final trade = tradesStore.trade!;
353
- final tradeFrom = trade.from;
354
-
355
- bool _sameCurrency(CryptoCurrency? a, CryptoCurrency? b) =>
356
- a != null && b != null && a.titleAndTagEqual(b);
357
-
358
- bool _isEthToken() =>
359
- wallet.currency == CryptoCurrency.eth && tradeFrom?.tag == CryptoCurrency.eth.title;
356
+ String? checkIfCanSend(Trade? trade, WalletBase wallet) {
357
+ try {
358
361
- bool _isPolygonToken() =>
362
- wallet.currency == CryptoCurrency.maticpoly &&
363
- tradeFrom?.tag == CryptoCurrency.maticpoly.tag;
359
+ if (trade == null) throw Exception('Trade is null');
360
365
- bool _isBaseToken() =>
366
- wallet.currency == CryptoCurrency.baseEth && tradeFrom?.tag == CryptoCurrency.baseEth.tag;
361
+ final tradeFrom = trade.from;
362
+ if (tradeFrom == null) throw Exception('Trade from currency is null');
363
368
- bool _isArbitrumToken() =>
369
- wallet.currency == CryptoCurrency.arbEth &&
370
- (tradeFrom?.tag == CryptoCurrency.arbEth.tag ||
371
- tradeFrom?.title == CryptoCurrency.arbEth.tag); // This is to handle the CryptoCurrency.arb that doesn't have a tag but fully belongs to the Arbitrum chain
364
+ bool _sameCurrency(CryptoCurrency a, CryptoCurrency b) => a.titleAndTagEqual(b);
365
373
- bool _isTronToken() =>
374
- wallet.currency == CryptoCurrency.trx && tradeFrom?.tag == CryptoCurrency.trx.title;
366
+ bool _isTokenBelongingToWallet(CryptoCurrency cur) {
367
+ final chainTag = cur.tag ?? cur.title;
368
+ return wallet.currency == cur &&
369
+ tradeFrom.tag?.toUpperCase() == chainTag.toUpperCase();
370
+ }
371
376
- bool _isSplToken() =>
377
- wallet.currency == CryptoCurrency.sol && tradeFrom?.tag == CryptoCurrency.sol.title;
372
+ final canSend = _sameCurrency(tradeFrom, wallet.currency) ||
373
+ (_sameCurrency(tradeFrom, CryptoCurrency.btcln) &&
374
+ wallet.currency == CryptoCurrency.btc) ||
375
+ trade.provider == ExchangeProviderDescription.xmrto ||
376
+ _isTokenBelongingToWallet(CryptoCurrency.eth) ||
377
+ _isTokenBelongingToWallet(CryptoCurrency.maticpoly) ||
378
+ _isTokenBelongingToWallet(CryptoCurrency.baseEth) ||
379
+ _isTokenBelongingToWallet(CryptoCurrency.arbEth) ||
380
+ _isTokenBelongingToWallet(CryptoCurrency.trx) ||
381
+ _isTokenBelongingToWallet(CryptoCurrency.sol) ||
382
+ _isTokenBelongingToWallet(CryptoCurrency.bnb);
383
+
384
+ if (!canSend) {
385
+ throw Exception(
386
+ 'Wallet currency ${wallet.currency.title} does not match trade from currency ${tradeFrom.title} or is not a supported token for this wallet.',
387
+ );
388
+ }
389
379
- bool _isBscToken() =>
380
- wallet.currency == CryptoCurrency.bnb && tradeFrom?.tag == CryptoCurrency.bnb.tag;
390
+ return null;
391
+ } catch (e, s) {
392
+ final trade = tradesStore.trade;
393
+
394
+ ExchangeProviderLogger.logError(
395
+ provider: trade?.provider,
396
+ function: '_checkIfCanSend',
397
+ error: e,
398
+ stackTrace: s,
399
+ requestData: {
400
+ 'tradeId': trade?.id,
401
+ 'tradeFrom': trade?.from?.title,
402
+ 'tradeFromTag': trade?.from?.tag,
403
+ 'tradeTo': trade?.to?.title,
404
+ 'tradeToTag': trade?.to?.tag,
405
+ 'walletName': wallet.name,
406
+ 'walletCurrency': wallet.currency.title,
407
+ 'walletCurrencyTag': wallet.currency.tag,
408
+ },
409
+ );
410
382
- return _sameCurrency(tradeFrom, wallet.currency) ||
383
- (_sameCurrency(tradeFrom, CryptoCurrency.btcln) &&
384
- wallet.currency == CryptoCurrency.btc) ||
385
- tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
386
- _isEthToken() ||
387
- _isPolygonToken() ||
388
- _isSplToken() ||
389
- _isTronToken() ||
390
- _isBaseToken() ||
391
- _isArbitrumToken() ||
392
- _isBscToken();
411
+ return e.toString();
412
+ }
413
}
414
415
static bool _checkIfSwapsXYZCanSendFromExternal(Trade trade, WalletBase wallet) {