dev
dart 347 lines 11.3 KB
Raw
1 import 'dart:async';
2
3 import 'package:cake_wallet/core/universal_address_detector.dart';
4 import 'package:cake_wallet/evm/evm.dart';
5 import 'package:cake_wallet/reactions/wallet_connect.dart';
6 import 'package:cake_wallet/solana/solana.dart';
7 import 'package:cake_wallet/tron/tron.dart';
8 import 'package:cake_wallet/store/app_store.dart';
9 import 'package:cw_core/wallet_type.dart';
10 import 'package:cw_core/wallet_info.dart';
11 import 'package:cw_core/utils/print_verbose.dart';
12 import 'package:cw_core/currency_for_wallet_type.dart';
13 import 'package:cw_core/crypto_currency.dart';
14 import 'package:mobx/mobx.dart';
15
16 part 'payment_view_model.g.dart';
17
18 class PaymentViewModel = PaymentViewModelBase with _$PaymentViewModel;
19
20 abstract class PaymentViewModelBase with Store {
21 PaymentViewModelBase({required this.appStore});
22
23 final AppStore appStore;
24
25 @observable
26 WalletType? detectedWalletType;
27
28 @observable
29 AddressDetectionResult? _lastDetectionResult;
30
31 @computed
32 int? get detectedChainId {
33 if (detectedWalletType == null) return null;
34
35 if (!isEVMCompatibleChain(detectedWalletType!)) return null;
36
37 if (_lastDetectionResult?.chainId != null) {
38 return _lastDetectionResult!.chainId;
39 }
40
41 // If detected wallet type is EVM-compatible, get chainId from detected currency
42 if (_lastDetectionResult?.detectedCurrency != null) {
43 return getChainIdByCryptoCurrency(_lastDetectionResult!.detectedCurrency!);
44 }
45
46 return evm?.getChainIdByWalletType(detectedWalletType!);
47 }
48
49 @observable
50 bool isProcessing = false;
51
52 @computed
53 WalletType get currentWalletType => appStore.wallet!.type;
54
55 @computed
56 int? get currentChainId {
57 if (!isEVMCompatibleChain(currentWalletType) || evm == null) return null;
58
59 return evm!.getSelectedChainId(appStore.wallet!);
60 }
61
62 /// Main entry point - detect address type and check compatibility
63 @action
64 Future<PaymentFlowResult> processAddress(String addressData) async {
65 try {
66 detectedWalletType = null;
67 isProcessing = true;
68
69 // Detect address type
70 final detectionResult = UniversalAddressDetector.detectAddress(addressData);
71
72 _lastDetectionResult = detectionResult;
73 detectedWalletType = detectionResult.detectedWalletType;
74
75 if (!detectionResult.isValid || detectedWalletType == null) {
76 return PaymentFlowResult.incompatible('Unable to detect address type');
77 }
78
79 if (detectedWalletType == WalletType.solana && solana == null) {
80 return PaymentFlowResult.incompatible('Solana is not available in this app build.');
81 }
82 if (detectedWalletType == WalletType.tron && tron == null) {
83 return PaymentFlowResult.incompatible('Tron is not available in this app build.');
84 }
85 if (isEVMCompatibleChain(detectedWalletType!) && evm == null) {
86 return PaymentFlowResult.incompatible(
87 'Ethereum-compatible addresses are not supported in this app build.',
88 );
89 }
90
91 final currentWallet = appStore.wallet;
92
93 if (currentWallet != null &&
94 currentWallet.type == detectedWalletType &&
95 !isEVMCompatibleChain(detectedWalletType!)) {
96 return PaymentFlowResult.currentWalletCompatible(detectionResult);
97 }
98
99 if (detectedWalletType == WalletType.solana) {
100 final compatibleWallets = await getWalletsByType(WalletType.solana);
101 return PaymentFlowResult.solanaTokenSelection(
102 detectionResult,
103 compatibleWallets: compatibleWallets,
104 wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
105 );
106 }
107
108 if (detectedWalletType == WalletType.tron) {
109 final compatibleWallets = await getWalletsByType(WalletType.tron);
110 return PaymentFlowResult.tronTokenSelection(
111 detectionResult,
112 compatibleWallets: compatibleWallets,
113 wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
114 );
115 }
116
117 if (isEVMCompatibleChain(detectedWalletType!)) {
118 return PaymentFlowResult.evmNetworkSelection(detectionResult);
119 }
120
121 final compatibleWallets = await getWalletsByType(detectedWalletType!);
122
123 switch (compatibleWallets.length) {
124 case 0:
125 return PaymentFlowResult.noWallets(detectedWalletType!, detectionResult);
126 case 1:
127 return PaymentFlowResult.singleWallet(compatibleWallets.first, detectionResult);
128 default:
129 return PaymentFlowResult.multipleWallets(compatibleWallets, detectionResult);
130 }
131 } catch (e) {
132 printV('PaymentViewModel error: $e');
133 return PaymentFlowResult.error('Payment processing failed: $e');
134 } finally {
135 isProcessing = false;
136 }
137 }
138
139 @action
140 Future<void> selectChain() async {
141 if (detectedWalletType == null || evm == null || detectedChainId == null) return;
142
143 final node =
144 appStore.settingsStore.getCurrentNode(detectedWalletType!, chainId: detectedChainId);
145
146 await evm!.selectChain(appStore.wallet!, detectedChainId!, node: node);
147 }
148
149 void applyManualEvmSelection(AddressDetectionResult detection) {
150 runInAction(() {
151 _lastDetectionResult = detection;
152 detectedWalletType = detection.detectedWalletType;
153 });
154 }
155
156 Future<List<WalletInfo>> getWalletsByType(WalletType walletType) async {
157 return (await WalletInfo.getAll()).where((wallet) => wallet.type == walletType).toList();
158 }
159
160 Future<List<WalletInfo>> getEVMCompatibleWallets() async {
161 final allWallets = await WalletInfo.getAll();
162 return allWallets.where((wallet) => isEVMCompatibleChain(wallet.type)).toList();
163 }
164 }
165
166 class PaymentFlowResult {
167 final PaymentFlowType type;
168 final String? message;
169 final WalletInfo? wallet;
170 final int? chainId;
171 final List<WalletInfo> wallets;
172 final WalletType? walletType;
173 final AddressDetectionResult? addressDetectionResult;
174
175 PaymentFlowResult._({
176 required this.type,
177 this.message,
178 this.wallet,
179 this.chainId,
180 this.wallets = const [],
181 this.walletType,
182 this.addressDetectionResult,
183 });
184
185 /// EVM address detected - needs network selection
186 /// We'll also take note of the number of compatible wallets for EVM ecosystem
187 factory PaymentFlowResult.evmNetworkSelection(
188 AddressDetectionResult addressDetectionResult, {
189 List<WalletInfo>? compatibleWallets,
190 WalletInfo? wallet,
191 }) {
192 int? chainId = addressDetectionResult.chainId;
193 if (chainId == null && addressDetectionResult.detectedCurrency != null) {
194 chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
195 }
196 if (chainId == null && addressDetectionResult.detectedWalletType != null) {
197 chainId = evm?.getChainIdByWalletType(addressDetectionResult.detectedWalletType!);
198 }
199
200 return PaymentFlowResult._(
201 type: PaymentFlowType.evmNetworkSelection,
202 addressDetectionResult: addressDetectionResult,
203 walletType: addressDetectionResult.detectedWalletType,
204 chainId: chainId,
205 wallets: compatibleWallets ?? [],
206 wallet: wallet,
207 );
208 }
209
210 /// Solana address detected - needs token selection
211 factory PaymentFlowResult.solanaTokenSelection(
212 AddressDetectionResult addressDetectionResult, {
213 List<WalletInfo>? compatibleWallets,
214 WalletInfo? wallet,
215 }) =>
216 PaymentFlowResult._(
217 type: PaymentFlowType.solanaTokenSelection,
218 addressDetectionResult: addressDetectionResult,
219 walletType: WalletType.solana,
220 wallets: compatibleWallets ?? [],
221 wallet: wallet,
222 );
223
224 /// Tron address detected - needs token selection
225 factory PaymentFlowResult.tronTokenSelection(
226 AddressDetectionResult addressDetectionResult, {
227 List<WalletInfo>? compatibleWallets,
228 WalletInfo? wallet,
229 }) =>
230 PaymentFlowResult._(
231 type: PaymentFlowType.tronTokenSelection,
232 addressDetectionResult: addressDetectionResult,
233 walletType: WalletType.tron,
234 wallets: compatibleWallets ?? [],
235 wallet: wallet,
236 );
237
238 /// Current wallet is compatible
239 factory PaymentFlowResult.currentWalletCompatible(
240 AddressDetectionResult addressDetectionResult) =>
241 PaymentFlowResult._(
242 type: PaymentFlowType.currentWalletCompatible,
243 addressDetectionResult: addressDetectionResult,
244 );
245
246 /// Single compatible wallet available
247 factory PaymentFlowResult.singleWallet(
248 WalletInfo wallet,
249 AddressDetectionResult addressDetectionResult,
250 ) {
251 int? chainId = addressDetectionResult.chainId;
252 if (chainId == null && addressDetectionResult.detectedCurrency != null) {
253 chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
254 }
255
256 if (chainId == null && isEVMCompatibleChain(wallet.type)) {
257 chainId = evm?.getChainIdByWalletType(wallet.type);
258 }
259
260 return PaymentFlowResult._(
261 type: PaymentFlowType.singleWallet,
262 wallet: wallet,
263 walletType: wallet.type,
264 chainId: chainId,
265 addressDetectionResult: addressDetectionResult,
266 );
267 }
268
269 /// Multiple compatible wallets available
270 factory PaymentFlowResult.multipleWallets(
271 List<WalletInfo> wallets,
272 AddressDetectionResult addressDetectionResult,
273 ) {
274 int? chainId = addressDetectionResult.chainId;
275 if (chainId == null && addressDetectionResult.detectedCurrency != null) {
276 chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
277 }
278 if (chainId == null && isEVMCompatibleChain(wallets.first.type)) {
279 chainId = evm?.getChainIdByWalletType(wallets.first.type);
280 }
281
282 return PaymentFlowResult._(
283 type: PaymentFlowType.multipleWallets,
284 wallets: wallets,
285 walletType: wallets.first.type,
286 addressDetectionResult: addressDetectionResult,
287 chainId: chainId,
288 );
289 }
290
291 /// No compatible wallets available
292 factory PaymentFlowResult.noWallets(
293 WalletType walletType,
294 AddressDetectionResult addressDetectionResult,
295 ) {
296 int? chainId = addressDetectionResult.chainId;
297 if (chainId == null && addressDetectionResult.detectedCurrency != null) {
298 chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
299 }
300 if (chainId == null && isEVMCompatibleChain(walletType)) {
301 chainId = evm?.getChainIdByWalletType(walletType);
302 }
303
304 return PaymentFlowResult._(
305 type: PaymentFlowType.noWallets,
306 walletType: walletType,
307 addressDetectionResult: addressDetectionResult,
308 chainId: chainId,
309 );
310 }
311
312 /// Error occurred
313 factory PaymentFlowResult.error(String message) =>
314 PaymentFlowResult._(type: PaymentFlowType.error, message: message);
315
316 /// Incompatible address
317 factory PaymentFlowResult.incompatible(String message) =>
318 PaymentFlowResult._(type: PaymentFlowType.incompatible, message: message);
319
320 CryptoCurrency? get detectedCurrency {
321 if (type == PaymentFlowType.evmNetworkSelection ||
322 type == PaymentFlowType.solanaTokenSelection ||
323 type == PaymentFlowType.tronTokenSelection) {
324 return addressDetectionResult?.detectedCurrency;
325 }
326 if (walletType != null) {
327 if (isEVMCompatibleChain(walletType!)) {
328 return walletTypeToCryptoCurrency(walletType!, chainId: chainId);
329 }
330
331 return walletTypeToCryptoCurrency(walletType!);
332 }
333 return null;
334 }
335 }
336
337 enum PaymentFlowType {
338 currentWalletCompatible,
339 singleWallet,
340 multipleWallets,
341 noWallets,
342 evmNetworkSelection,
343 solanaTokenSelection,
344 tronTokenSelection,
345 error,
346 incompatible,
347 }