mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
* fix android CI * feat: add anypay core models, parser and routing engine * feat: add anypay resolver, anypay service and wallet switch service * refactor: route the send page payment flow through anypay service * fix: show message when there's no swap providers for a pair and reset the trade guard on dismissal * refactor: merge the swap from network and send to network decision pages together and clean up anypay widgets * feat: reapply deep links to a currently open send flow and register the solana scheme * feat: add support for zcash address detection to anypay and other minor fixes * refactor: remove trailing icon * feat: hide swap flow for sp and mweb addresses * feat: enhance zcash address detection and deeplink handling * fix: pin bitcoin_base * refactor: Move the anypay decision navigation out of the send page and address minor review comments * refactor: Move the anypay decision navigation out of the send page and address minor review comments * feat: handle exception in switch service and mvoe balance compute to call site * refactor: combine anypay entry points to one and extract selected mode widget --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
142 lines
4.5 KiB
Dart
142 lines
4.5 KiB
Dart
import "package:cake_wallet/core/address_validator.dart";
|
|
import "package:cake_wallet/core/anypay/anypay_models.dart";
|
|
import "package:cake_wallet/reactions/wallet_connect.dart";
|
|
import "package:cw_core/crypto_currency.dart";
|
|
import "package:cw_core/wallet_type.dart";
|
|
|
|
class AnyPayRouter {
|
|
static final List<RegExp> _swapIncompatibleAddressPatterns = [
|
|
RegExp("^(?:${AddressValidator.silentPaymentAddressPatternMainnet})\$"),
|
|
RegExp("^(?:${AddressValidator.silentPaymentAddressPatternTestnet})\$"),
|
|
RegExp("^(?:${AddressValidator.mWebAddressPattern})\$"),
|
|
];
|
|
|
|
static bool _isSwapIncompatibleAddress(String address) =>
|
|
_swapIncompatibleAddressPatterns.any((pattern) => pattern.hasMatch(address));
|
|
|
|
static AnyPayDecision route(
|
|
AnyPayRequest request,
|
|
WalletSnapshot snapshot,
|
|
AnyPayResolution resolution,
|
|
) {
|
|
if (request.rawInput.trim().isEmpty) {
|
|
return const AnyPayEmptyInput();
|
|
}
|
|
|
|
final detection = request.detection;
|
|
final detectedType = detection.detectedWalletType;
|
|
|
|
if (!detection.isValid || detectedType == null) {
|
|
return const AnyPayApplyToCurrentWallet();
|
|
}
|
|
|
|
if (detectedType == WalletType.solana && !snapshot.hasSolanaProxy) {
|
|
return const AnyPayApplyToCurrentWallet();
|
|
}
|
|
|
|
if (detectedType == WalletType.tron && !snapshot.hasTronProxy) {
|
|
return const AnyPayApplyToCurrentWallet();
|
|
}
|
|
|
|
if (isEVMCompatibleChain(detectedType)) {
|
|
if (!snapshot.hasEvmProxy) {
|
|
return const AnyPayApplyToCurrentWallet();
|
|
}
|
|
|
|
return _routeEvm(request, snapshot, resolution);
|
|
}
|
|
|
|
if (resolution is TokenUnknown) {
|
|
return AnyPayUnsupportedToken(resolution.contract);
|
|
}
|
|
|
|
if (detectedType == snapshot.type) {
|
|
return AnyPayApplyToCurrentWallet(
|
|
token: resolution is TokenResolved ? resolution.token : null,
|
|
amountOverride: resolution is TokenResolved ? resolution.amountOverride : null,
|
|
fallbackCurrency: _nativeRequestCurrency(request),
|
|
);
|
|
}
|
|
|
|
return AnyPayCrossChainPayment(
|
|
targetWalletType: detectedType,
|
|
wallets: snapshot.walletsOfType(detectedType),
|
|
token: resolution is TokenResolved ? resolution.token : null,
|
|
amountOverride: resolution is TokenResolved ? resolution.amountOverride : null,
|
|
canSwap: !_isSwapIncompatibleAddress(request.address),
|
|
);
|
|
}
|
|
|
|
static AnyPayDecision _routeEvm(
|
|
AnyPayRequest request,
|
|
WalletSnapshot snapshot,
|
|
AnyPayResolution resolution,
|
|
) {
|
|
final requestedChainId = requestedEvmChainId(request, snapshot);
|
|
|
|
if (requestedChainId == null) {
|
|
if (snapshot.currentWalletIsEvm) {
|
|
return const AnyPayApplyToCurrentWallet();
|
|
}
|
|
|
|
return const AnyPayEvmNetworkChoice();
|
|
}
|
|
|
|
if (!snapshot.supportedEvmChains.containsKey(requestedChainId)) {
|
|
return AnyPayUnsupportedNetwork(requestedChainId);
|
|
}
|
|
|
|
if (resolution is TokenUnknown) {
|
|
return AnyPayUnsupportedToken(resolution.contract);
|
|
}
|
|
|
|
int targetChainId = requestedChainId;
|
|
if (resolution is TokenResolved && resolution.chainId != null) {
|
|
targetChainId = resolution.chainId!;
|
|
}
|
|
|
|
final token = resolution is TokenResolved ? resolution.token : null;
|
|
final amountOverride = resolution is TokenResolved ? resolution.amountOverride : null;
|
|
|
|
if (snapshot.currentWalletIsEvm && snapshot.currentChainId == targetChainId) {
|
|
return AnyPayApplyToCurrentWallet(
|
|
token: token,
|
|
amountOverride: amountOverride,
|
|
fallbackCurrency: _nativeRequestCurrency(request),
|
|
);
|
|
}
|
|
|
|
final targetWalletType = snapshot.supportedEvmChains[targetChainId]!;
|
|
|
|
return AnyPayCrossChainPayment(
|
|
targetWalletType: targetWalletType,
|
|
targetChainId: targetChainId,
|
|
wallets: snapshot.walletsOfType(targetWalletType),
|
|
token: token,
|
|
amountOverride: amountOverride,
|
|
);
|
|
}
|
|
|
|
static CryptoCurrency? _nativeRequestCurrency(AnyPayRequest request) {
|
|
if (request.paymentRequest.scheme.isEmpty || request.hasContract || request.amount.isEmpty) {
|
|
return null;
|
|
}
|
|
return request.detection.detectedCurrency;
|
|
}
|
|
|
|
static int? requestedEvmChainId(AnyPayRequest request, WalletSnapshot snapshot) {
|
|
final binding = request.chainBinding;
|
|
|
|
if (binding is ExplicitEvmChain) {
|
|
return binding.chainId;
|
|
}
|
|
|
|
if (binding is ChainlessEvm) {
|
|
// the eip 681 spec says we should use the current chain id if there is no chainId in the incoming request
|
|
return snapshot.currentWalletIsEvm ? snapshot.currentChainId ?? 1 : 1;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|