dev
dart 402 lines 11.6 KB
Raw
1 import 'dart:math' show min;
2 import 'package:cake_wallet/bitcoin/bitcoin.dart';
3 import 'package:cake_wallet/core/address_resolver/address_sources.dart';
4 import 'package:cake_wallet/core/address_resolver/parsed_address.dart';
5 import 'package:cake_wallet/decred/decred.dart';
6 import 'package:cake_wallet/di.dart';
7 import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
8 import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
9 import 'package:cake_wallet/entities/contact_base.dart';
10 import 'package:cake_wallet/evm/evm.dart';
11 import 'package:cake_wallet/generated/i18n.dart';
12 import 'package:cake_wallet/reactions/wallet_connect.dart';
13 import 'package:cake_wallet/solana/solana.dart';
14 import 'package:cake_wallet/store/app_store.dart';
15 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
16 import 'package:cake_wallet/store/settings_store.dart';
17 import 'package:cake_wallet/tron/tron.dart';
18 import 'package:cw_core/amount/amount_sanitizer.dart';
19 import 'package:cw_core/amount/money.dart';
20 import 'package:cw_core/balance.dart';
21 import 'package:cw_core/crypto_amount_format.dart';
22 import 'package:cw_core/crypto_currency.dart';
23 import 'package:cw_core/currency_for_wallet_type.dart';
24 import 'package:cw_core/sync_status.dart';
25 import 'package:cw_core/transaction_history.dart';
26 import 'package:cw_core/transaction_info.dart';
27 import 'package:cw_core/utils/print_verbose.dart';
28 import 'package:cw_core/wallet_base.dart';
29 import 'package:cw_core/wallet_type.dart';
30 import 'package:flutter/material.dart';
31 import 'package:mobx/mobx.dart';
32
33 part 'output.g.dart';
34
35 const String cryptoNumberPattern = '0.0';
36
37 class Output = OutputBase with _$Output;
38
39 abstract class OutputBase with Store {
40 OutputBase(this._wallet, this._appStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
41 : key = UniqueKey(),
42 sendAll = false,
43 cryptoAmount = '',
44 cryptoFullBalance = '',
45 fiatAmount = '',
46 address = '',
47 note = '',
48 memo = "",
49 extractedAddress = '',
50 estimatedFee = Money.zero(cryptoCurrencyHandler()),
51 parsedAddress = ParsedAddress(parsedAddressByCurrencyMap: {}) {
52 autorun((_) {
53 final status = _wallet.syncStatus;
54 if (status is! SyncedSyncStatus) {
55 return;
56 }
57 calculateEstimatedFee();
58 });
59 }
60
61 Key key;
62
63 bool get useSatoshi => _appStore.amountParsingProxy.useSatoshi(cryptoCurrencyHandler());
64
65 @observable
66 bool isFiatEntry = false;
67
68 @observable
69 String fiatAmount;
70
71 @observable
72 String cryptoAmount;
73
74 @observable
75 String? displayName;
76
77 @computed
78 String get displayCryptoAmount => _appStore.amountParsingProxy.asDisplayString(cryptoAmountMoney);
79
80 @observable
81 String cryptoFullBalance;
82
83 @observable
84 String address;
85
86 @observable
87 String note;
88
89 @observable
90 String memo;
91
92 @observable
93 bool sendAll;
94
95 @observable
96 ParsedAddress parsedAddress;
97
98 @observable
99 String extractedAddress;
100
101 @computed
102 bool get isParsedAddress =>
103 parsedAddress.addressSource != AddressSource.notParsed && parsedAddress.handle.isNotEmpty;
104
105 String roundedCryptoAmount(int digits) => displayCryptoAmount.withMaxDecimals(digits);
106
107 String roundedFiatAmount(int digits) {
108 if (fiatAmount.split(".").last.length <= digits) return fiatAmount;
109
110 return double.tryParse(fiatAmount.replaceAll(",", ""))?.toStringAsPrecision(digits) ?? '0.0';
111 }
112
113 @observable
114 String? stealthAddress;
115
116 @computed
117 Money get cryptoAmountMoney {
118 if (cryptoAmount.isEmpty) return Money.zero(cryptoCurrencyHandler());
119
120 try {
121 return cryptoCurrencyHandler().parseAmount(cryptoAmount.sanitized());
122 } catch (e) {
123 return Money.zero(cryptoCurrencyHandler());
124 }
125 }
126
127 @observable
128 Money estimatedFee;
129
130 @action
131 Future<void> calculateEstimatedFee() async {
132 try {
133 final priority = _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId);
134 if (isEVMCompatibleChain(_wallet.type)) {
135 await _wallet.updateEstimatedFeesParams(priority);
136 }
137
138 int fee = 0;
139 if (_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId) != null) {
140 fee = _wallet.calculateEstimatedFee(
141 _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId)!,
142 cryptoAmountMoney.amount.toInt(),
143 );
144 }
145
146 switch (_wallet.type) {
147 case WalletType.monero:
148 case WalletType.wownero:
149 case WalletType.litecoin:
150 case WalletType.bitcoinCash:
151 case WalletType.dogecoin:
152 case WalletType.decred:
153 case WalletType.zano:
154 estimatedFee = Money.fromInt(fee, walletTypeToCryptoCurrency(_wallet.type));
155 break;
156 case WalletType.bitcoin:
157 if (cryptoCurrencyHandler() == CryptoCurrency.btcln) {
158 estimatedFee = Money.fromInt(10, cryptoCurrencyHandler());
159 break;
160 }
161 if (_settingsStore.getPriority(_wallet.type) ==
162 bitcoin!.getBitcoinTransactionPriorityCustom()) {
163 fee = bitcoin!.getEstimatedFeeWithFeeRate(
164 _wallet, _settingsStore.customBitcoinFeeRate, cryptoAmountMoney.amount.toInt());
165 }
166
167 estimatedFee = Money.fromInt(fee, cryptoCurrencyHandler());
168 break;
169 case WalletType.solana:
170 estimatedFee = solana!.getEstimateFees(_wallet) ?? Money.zero(CryptoCurrency.sol);
171 break;
172 case WalletType.tron:
173 if (cryptoCurrencyHandler() == CryptoCurrency.trx) {
174 estimatedFee =
175 tron!.getTronNativeEstimatedFee(_wallet) ?? Money.zero(CryptoCurrency.trx);
176 } else {
177 estimatedFee =
178 tron!.getTronTRC20EstimatedFee(_wallet) ?? Money.zero(CryptoCurrency.trx);
179 }
180 break;
181
182 case WalletType.zcash:
183 estimatedFee = Money.fromInt(fee, cryptoCurrencyHandler());
184 break;
185
186 /// EVMs
187 case WalletType.ethereum:
188 case WalletType.polygon:
189 case WalletType.base:
190 case WalletType.arbitrum:
191 case WalletType.bsc:
192 final isNative = [
193 CryptoCurrency.eth,
194 CryptoCurrency.maticpoly,
195 CryptoCurrency.baseEth,
196 CryptoCurrency.arbEth,
197 CryptoCurrency.bnb
198 ].contains(cryptoCurrencyHandler());
199
200 final fee = isNative
201 ? evm!.getEVMNativeEstimatedFee(_wallet)
202 : evm!.getEVMERC20EstimatedFee(_wallet);
203
204 estimatedFee =
205 Money(BigInt.parse(fee ?? '0.0'), walletTypeToCryptoCurrency(_wallet.type));
206 break;
207
208 /// end EVMs
209
210 case WalletType.haven:
211 case WalletType.nano:
212 case WalletType.banano:
213 case WalletType.none:
214 // will not reach here as it doesn't have priority and this function is triggered only when priority changes
215 break;
216 }
217 } catch (e) {
218 printV(e.toString());
219 }
220 }
221
222 @computed
223 String get estimatedFeeFiatAmount {
224 // forces mobx to rebuild the computed value
225 final _ = _wallet.syncStatus;
226
227 try {
228 final currency = (isEVMCompatibleChain(_wallet.type) ||
229 [WalletType.solana, WalletType.tron].contains(_wallet.type)) ||
230 cryptoCurrencyHandler() == CryptoCurrency.btcln
231 ? _wallet.currency
232 : cryptoCurrencyHandler();
233
234 final cryptoAmount = double.parse(estimatedFee.toString());
235
236 return calculateFiatAmountRaw(
237 price: _fiatConversationStore.prices[currency]!, cryptoAmount: cryptoAmount);
238 } catch (_) {
239 return '0.00';
240 }
241 }
242
243 @observable
244 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
245
246 WalletType get walletType => _wallet.type;
247
248 final CryptoCurrency Function([CryptoCurrency?]) cryptoCurrencyHandler;
249 final FiatConversionStore _fiatConversationStore;
250 final AppStore _appStore;
251
252 SettingsStore get _settingsStore => _appStore.settingsStore;
253
254 @action
255 void setSendAll(String fullBalance) {
256 cryptoFullBalance =
257 _appStore.amountParsingProxy.getCanonicalCryptoAmount(fullBalance, cryptoCurrencyHandler());
258 sendAll = true;
259 _updateFiatAmount();
260 }
261
262 @action
263 void updateWallet(
264 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> newWallet) {
265 _wallet = newWallet;
266 estimatedFee = Money.zero(cryptoCurrencyHandler());
267 }
268
269 @action
270 void reset() {
271 sendAll = false;
272 cryptoAmount = '';
273 fiatAmount = '';
274 address = '';
275 note = '';
276 memo = "";
277 resetParsedAddress();
278 }
279
280 @action
281 void resetParsedAddress() {
282 displayName = null;
283 extractedAddress = '';
284 note = '';
285 parsedAddress = ParsedAddress(parsedAddressByCurrencyMap: {});
286 }
287
288 @action
289 void applyAddressLookupResult(ParsedAddress result) {
290 final currency = cryptoCurrencyHandler();
291
292 parsedAddress = result;
293 extractedAddress = result.parsedAddressByCurrencyMap[currency] ?? '';
294 note = result.description;
295 displayName = result.profileName.isNotEmpty ? result.profileName : result.handle;
296 }
297
298 @action
299
300 /// [setCryptoAmount] always takes in the canonical representation eg. Bitcoin and not Sats
301 void setCryptoAmount(String amount) {
302 if (amount.toUpperCase() != S.current.all) sendAll = false;
303
304 cryptoAmount = amount;
305 _updateFiatAmount();
306 }
307
308 @action
309 void setFiatAmount(String amount) {
310 fiatAmount = amount;
311 _updateCryptoAmount();
312 }
313
314 @action
315 void _updateFiatAmount() {
316 try {
317 var cryptoAmount_ =
318 sendAll ? cryptoFullBalance.replaceAll(",", ".") : cryptoAmount.replaceAll(',', '.');
319
320 var cryptoCurrency = cryptoCurrencyHandler() == CryptoCurrency.btcln
321 ? CryptoCurrency.btc
322 : cryptoCurrencyHandler();
323
324 final fiat = calculateFiatAmount(
325 price: _fiatConversationStore.prices[cryptoCurrency]!, cryptoAmount: cryptoAmount_);
326 if (fiatAmount != fiat) {
327 fiatAmount = fiat;
328 }
329 } catch (_) {
330 fiatAmount = '';
331 }
332 }
333
334 @action
335 void _updateCryptoAmount() {
336 try {
337 var cryptoCurrency = cryptoCurrencyHandler() == CryptoCurrency.btcln
338 ? CryptoCurrency.btc
339 : cryptoCurrencyHandler();
340
341 final decimals = min(20, cryptoCurrencyHandler().decimals);
342 final crypto = (double.parse(fiatAmount.replaceAll(',', '.')) /
343 _fiatConversationStore.prices[cryptoCurrency]!)
344 .toStringAsFixed(decimals);
345
346 if (cryptoAmount != crypto) cryptoAmount = crypto;
347 } catch (e) {
348 printV(e);
349 cryptoAmount = '';
350 }
351 }
352
353 Map<String, dynamic> get extra {
354 final fields = <String, dynamic>{};
355 if (parsedAddress.addressSource == AddressSource.bip353) {
356 fields['bip353_name'] = parsedAddress.handle;
357 fields['bip353_proof'] = parsedAddress.bip353DnsProof;
358 }
359 return fields;
360 }
361
362 @action
363 void loadContact(ContactBase contact) {
364 final currency = cryptoCurrencyHandler();
365
366 address = contact.address;
367 applyAddressLookupResult(
368 ParsedAddress(
369 parsedAddressByCurrencyMap: {currency: contact.address},
370 addressSource: AddressSource.contact,
371 handle: contact.name,
372 profileName: contact.name,
373 ),
374 );
375 }
376 }
377
378 extension OutputCopyWith on Output {
379 Output OutputCopyWithParsedAddress({
380 ParsedAddress? parsedAddress,
381 String? fiatAmount,
382 }) {
383 final clone = Output(
384 _wallet,
385 _appStore,
386 _fiatConversationStore,
387 cryptoCurrencyHandler,
388 );
389
390 clone
391 ..cryptoAmount = cryptoAmount
392 ..cryptoFullBalance = cryptoFullBalance
393 ..note = note
394 ..sendAll = sendAll
395 ..memo = memo
396 ..stealthAddress = stealthAddress
397 ..parsedAddress = parsedAddress ?? this.parsedAddress
398 ..fiatAmount = fiatAmount ?? this.fiatAmount;
399
400 return clone;
401 }
402 }