mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
94 files changed, -5926 net lines. No stubs, no generators, no dead enum
values, no orphan switch branches. Removed for principled reasons (we
do not ship these chains) and to make the codebase smaller and friendlier
to contribute to.
What's gone:
- lib/{solana,tron,zano,decred,zcash,haven}/ directories
- lib/exchange/provider/jupiter_exchange_provider.dart (Solana-only)
- lib/entities/haven_seed_store.dart
- WalletType.{solana,tron,zano,decred,zcash,haven} enum values
(HiveField IDs are pinned: Wownero stays at 12, all surviving types
keep their IDs, so existing user-saved wallets deserialize unchanged)
- All ~70 lib/ files that imported the removed chain stubs or branched
on the removed WalletType values
- backupSeeds(Box<HavenSeedStore>) abstract method + Wownero implementation
- Jupiter exchange branches in send/exchange view models
- TronGrid usage settings, Zano alias lookups, Zcash transparent address
handling, Decred default-spv-nodes branches, Haven seed migration
What stays (intentional):
- CryptoCurrency.{xhv,sol,trx,dcr,zano,zec} enum entries in cw_core —
they're still used as exchange/swap currency identifiers in
address_validator.dart and elsewhere. The wallet types are gone but
the *currencies* are still tradeable through Trocador.
- cw_core/spl_token.dart and cw_core/tron_token.dart Hive adapters in
main.dart — the tokens still register as Hive types for backwards-
compat with any old box data.
- lib/buy/{moonpay,robinhood,onramper,dfx,kryptonim,meld}/ provider
source files (just stripped of solana/tron/etc references; providers
themselves remain dormant per provider_types.dart).
Pinned-ID note: WalletType enum now has gaps in @HiveField IDs (4, 10,
11, 13, 14, 18 removed). This is deliberate — re-using a freed ID later
would mis-deserialize old hive boxes. Don't renumber.
Bundled config / CI hasn't changed; configure.dart was already free of
these chains in commit 4eb7db0b7.
400 lines
12 KiB
Dart
400 lines
12 KiB
Dart
import 'dart:math' show min;
|
|
import 'package:hash_wallet/bitcoin/bitcoin.dart';
|
|
import 'package:hash_wallet/di.dart';
|
|
import 'package:hash_wallet/entities/calculate_fiat_amount.dart';
|
|
import 'package:hash_wallet/entities/calculate_fiat_amount_raw.dart';
|
|
import 'package:hash_wallet/entities/contact_base.dart';
|
|
import 'package:hash_wallet/entities/parse_address_from_domain.dart';
|
|
import 'package:hash_wallet/entities/parsed_address.dart';
|
|
import 'package:hash_wallet/evm/evm.dart';
|
|
import 'package:hash_wallet/generated/i18n.dart';
|
|
import 'package:hash_wallet/monero/monero.dart';
|
|
import 'package:hash_wallet/reactions/wallet_connect.dart';
|
|
import 'package:hash_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
|
import 'package:hash_wallet/store/app_store.dart';
|
|
import 'package:hash_wallet/store/dashboard/fiat_conversion_store.dart';
|
|
import 'package:hash_wallet/store/settings_store.dart';
|
|
import 'package:hash_wallet/wownero/wownero.dart';
|
|
import 'package:cw_core/balance.dart';
|
|
import 'package:cw_core/crypto_amount_format.dart';
|
|
import 'package:cw_core/crypto_currency.dart';
|
|
import 'package:cw_core/currency_for_wallet_type.dart';
|
|
import 'package:cw_core/format_fixed.dart';
|
|
import 'package:cw_core/transaction_history.dart';
|
|
import 'package:cw_core/transaction_info.dart';
|
|
import 'package:cw_core/utils/print_verbose.dart';
|
|
import 'package:cw_core/wallet_base.dart';
|
|
import 'package:cw_core/wallet_type.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
part 'output.g.dart';
|
|
|
|
const String cryptoNumberPattern = '0.0';
|
|
|
|
class Output = OutputBase with _$Output;
|
|
|
|
abstract class OutputBase with Store {
|
|
OutputBase(this._wallet, this._appStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
|
|
: key = UniqueKey(),
|
|
sendAll = false,
|
|
cryptoAmount = '',
|
|
cryptoFullBalance = '',
|
|
fiatAmount = '',
|
|
address = '',
|
|
note = '',
|
|
memo = "",
|
|
extractedAddress = '',
|
|
estimatedFee = '0.0',
|
|
parsedAddress = ParsedAddress(addresses: []) {
|
|
autorun((_) {
|
|
final status = _wallet.syncStatus;
|
|
printV("Sync status changed to $status. Recalculating fees");
|
|
|
|
calculateEstimatedFee();
|
|
});
|
|
}
|
|
|
|
Key key;
|
|
|
|
bool get useSatoshi => _appStore.amountParsingProxy.useSatoshi(cryptoCurrencyHandler());
|
|
|
|
@observable
|
|
String fiatAmount;
|
|
|
|
@observable
|
|
String cryptoAmount;
|
|
|
|
@observable
|
|
String? displayName;
|
|
|
|
@computed
|
|
String get displayCryptoAmount =>
|
|
_appStore.amountParsingProxy.getDisplayCryptoAmount(cryptoAmount, cryptoCurrencyHandler());
|
|
|
|
@observable
|
|
String cryptoFullBalance;
|
|
|
|
@observable
|
|
String address;
|
|
|
|
@observable
|
|
String note;
|
|
|
|
@observable
|
|
String memo;
|
|
|
|
@observable
|
|
bool sendAll;
|
|
|
|
@observable
|
|
ParsedAddress parsedAddress;
|
|
|
|
@observable
|
|
String extractedAddress;
|
|
|
|
|
|
@computed
|
|
bool get isParsedAddress =>
|
|
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
|
|
|
|
String roundedCryptoAmount(int digits) => displayCryptoAmount.withMaxDecimals(digits);
|
|
|
|
String roundedFiatAmount(int digits) {
|
|
if (fiatAmount.split(".").last.length <= digits) {
|
|
return fiatAmount;
|
|
}
|
|
return double.tryParse(fiatAmount.replaceAll(",", ""))?.toStringAsPrecision(digits) ?? '0.0';
|
|
}
|
|
|
|
@observable
|
|
String? stealthAddress;
|
|
|
|
@computed
|
|
int get formattedCryptoAmount {
|
|
int amount = 0;
|
|
|
|
try {
|
|
if (cryptoAmount.isNotEmpty) {
|
|
final _cryptoAmount = cryptoAmount.replaceAll(',', '.');
|
|
int _amount = 0;
|
|
switch (walletType) {
|
|
case WalletType.monero:
|
|
_amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount);
|
|
break;
|
|
case WalletType.bitcoin:
|
|
case WalletType.litecoin:
|
|
case WalletType.bitcoinCash:
|
|
case WalletType.dogecoin:
|
|
_amount = cryptoCurrencyHandler().parseAmount(_cryptoAmount).toInt();
|
|
break;
|
|
case WalletType.ethereum:
|
|
case WalletType.polygon:
|
|
case WalletType.base:
|
|
case WalletType.arbitrum:
|
|
case WalletType.bsc:
|
|
_amount = evm!.formatterEVMParseAmount(_cryptoAmount);
|
|
break;
|
|
case WalletType.wownero:
|
|
_amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
|
|
break;
|
|
case WalletType.none:
|
|
case WalletType.nano:
|
|
case WalletType.banano:
|
|
break;
|
|
}
|
|
|
|
if (_amount > 0) {
|
|
amount = _amount;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
amount = 0;
|
|
}
|
|
|
|
return amount;
|
|
}
|
|
|
|
@observable
|
|
String estimatedFee;
|
|
|
|
@action
|
|
Future<void> calculateEstimatedFee() async {
|
|
try {
|
|
final priority = _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId);
|
|
if (isEVMCompatibleChain(_wallet.type)) {
|
|
await _wallet.updateEstimatedFeesParams(priority);
|
|
}
|
|
|
|
int fee = 0;
|
|
if (_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId) != null) {
|
|
fee = _wallet.calculateEstimatedFee(
|
|
_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId)!,
|
|
formattedCryptoAmount,
|
|
);
|
|
}
|
|
|
|
switch (_wallet.type) {
|
|
case WalletType.monero:
|
|
case WalletType.wownero:
|
|
case WalletType.litecoin:
|
|
case WalletType.bitcoinCash:
|
|
case WalletType.dogecoin:
|
|
estimatedFee = walletTypeToCryptoCurrency(_wallet.type).formatAmount(BigInt.from(fee));
|
|
break;
|
|
case WalletType.bitcoin:
|
|
if (cryptoCurrencyHandler() == CryptoCurrency.btcln) {
|
|
estimatedFee =
|
|
_appStore.amountParsingProxy.getDisplayCryptoString(10, cryptoCurrencyHandler());
|
|
break;
|
|
}
|
|
if (_settingsStore.getPriority(_wallet.type) ==
|
|
bitcoin!.getBitcoinTransactionPriorityCustom()) {
|
|
fee = bitcoin!.getEstimatedFeeWithFeeRate(
|
|
_wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
|
|
}
|
|
|
|
estimatedFee = _appStore.amountParsingProxy.getDisplayCryptoString(fee, cryptoCurrencyHandler());
|
|
break;
|
|
|
|
/// EVMs
|
|
case WalletType.ethereum:
|
|
case WalletType.polygon:
|
|
case WalletType.base:
|
|
case WalletType.arbitrum:
|
|
case WalletType.bsc:
|
|
final isNative = cryptoCurrencyHandler() == CryptoCurrency.eth ||
|
|
cryptoCurrencyHandler() == CryptoCurrency.maticpoly ||
|
|
cryptoCurrencyHandler() == CryptoCurrency.baseEth ||
|
|
cryptoCurrencyHandler() == CryptoCurrency.arbEth ||
|
|
cryptoCurrencyHandler() == CryptoCurrency.bnb;
|
|
String? fee = isNative
|
|
? evm!.getEVMNativeEstimatedFee(_wallet)
|
|
: evm!.getEVMERC20EstimatedFee(_wallet);
|
|
|
|
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
|
|
break;
|
|
|
|
/// end EVMs
|
|
|
|
case WalletType.nano:
|
|
case WalletType.banano:
|
|
case WalletType.none:
|
|
// will not reach here as it doesn't have priority and this function is triggered only when priority changes
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
printV(e.toString());
|
|
}
|
|
}
|
|
|
|
@computed
|
|
String get estimatedFeeFiatAmount {
|
|
// forces mobx to rebuild the computed value
|
|
final _ = _wallet.syncStatus;
|
|
|
|
try {
|
|
final currency = isEVMCompatibleChain(_wallet.type)
|
|
? _wallet.currency
|
|
: cryptoCurrencyHandler();
|
|
|
|
final cryptoAmount =
|
|
double.parse(_appStore.amountParsingProxy.getCanonicalCryptoAmount(estimatedFee, currency));
|
|
|
|
return calculateFiatAmountRaw(
|
|
price: _fiatConversationStore.prices[currency]!, cryptoAmount: cryptoAmount);
|
|
} catch (_) {
|
|
return '0.00';
|
|
}
|
|
}
|
|
|
|
@observable
|
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
|
|
|
|
WalletType get walletType => _wallet.type;
|
|
|
|
final CryptoCurrency Function() cryptoCurrencyHandler;
|
|
final FiatConversionStore _fiatConversationStore;
|
|
final AppStore _appStore;
|
|
|
|
SettingsStore get _settingsStore => _appStore.settingsStore;
|
|
|
|
@action
|
|
void setSendAll(String fullBalance) {
|
|
cryptoFullBalance =
|
|
_appStore.amountParsingProxy.getCanonicalCryptoAmount(fullBalance, cryptoCurrencyHandler());
|
|
sendAll = true;
|
|
_updateFiatAmount();
|
|
}
|
|
|
|
@action
|
|
void updateWallet(
|
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> newWallet) {
|
|
_wallet = newWallet;
|
|
}
|
|
|
|
@action
|
|
void reset() {
|
|
sendAll = false;
|
|
cryptoAmount = '';
|
|
fiatAmount = '';
|
|
address = '';
|
|
note = '';
|
|
memo = "";
|
|
resetParsedAddress();
|
|
}
|
|
|
|
void resetParsedAddress() {
|
|
displayName = null;
|
|
extractedAddress = '';
|
|
parsedAddress = ParsedAddress(addresses: []);
|
|
}
|
|
|
|
@action
|
|
/// [setCryptoAmount] always takes in the canonical representation eg. Bitcoin and not Sats
|
|
void setCryptoAmount(String amount) {
|
|
if (amount.toUpperCase() != S.current.all) sendAll = false;
|
|
|
|
cryptoAmount = amount;
|
|
_updateFiatAmount();
|
|
}
|
|
|
|
@action
|
|
void setFiatAmount(String amount) {
|
|
fiatAmount = amount;
|
|
_updateCryptoAmount();
|
|
}
|
|
|
|
@action
|
|
void _updateFiatAmount() {
|
|
try {
|
|
var cryptoAmount_ =
|
|
sendAll ? cryptoFullBalance.replaceAll(",", ".") : cryptoAmount.replaceAll(',', '.');
|
|
|
|
var cryptoCurrency = cryptoCurrencyHandler() == CryptoCurrency.btcln
|
|
? CryptoCurrency.btc
|
|
: cryptoCurrencyHandler();
|
|
|
|
final fiat = calculateFiatAmount(
|
|
price: _fiatConversationStore.prices[cryptoCurrency]!, cryptoAmount: cryptoAmount_);
|
|
if (fiatAmount != fiat) {
|
|
fiatAmount = fiat;
|
|
}
|
|
} catch (_) {
|
|
fiatAmount = '';
|
|
}
|
|
}
|
|
|
|
@action
|
|
void _updateCryptoAmount() {
|
|
try {
|
|
var cryptoCurrency = cryptoCurrencyHandler() == CryptoCurrency.btcln
|
|
? CryptoCurrency.btc
|
|
: cryptoCurrencyHandler();
|
|
|
|
final decimals = min(20, cryptoCurrencyHandler().decimals);
|
|
final crypto = (double.parse(fiatAmount.replaceAll(',', '.')) /
|
|
_fiatConversationStore.prices[cryptoCurrency]!)
|
|
.toStringAsFixed(decimals);
|
|
|
|
if (cryptoAmount != crypto) cryptoAmount = crypto;
|
|
} catch (e) {
|
|
printV(e);
|
|
cryptoAmount = '';
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic> get extra {
|
|
final fields = <String, dynamic>{};
|
|
if (parsedAddress.parseFrom == ParseFrom.bip353) {
|
|
fields['bip353_name'] = parsedAddress.name;
|
|
fields['bip353_proof'] = parsedAddress.bip353DnsProof;
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
Future<void> fetchParsedAddress(BuildContext context) async {
|
|
final domain = address;
|
|
final currency = cryptoCurrencyHandler();
|
|
parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
|
|
if(parsedAddress.name.isNotEmpty) {
|
|
displayName = parsedAddress.name;
|
|
}
|
|
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
|
|
note = parsedAddress.description;
|
|
}
|
|
|
|
void loadContact(ContactBase contact) {
|
|
address = contact.name;
|
|
parsedAddress = ParsedAddress.fetchContactAddress(address: contact.address, name: contact.name);
|
|
extractedAddress = parsedAddress.addresses.first;
|
|
note = parsedAddress.description;
|
|
}
|
|
}
|
|
|
|
extension OutputCopyWith on Output {
|
|
Output OutputCopyWithParsedAddress({
|
|
ParsedAddress? parsedAddress,
|
|
String? fiatAmount,
|
|
}) {
|
|
final clone = Output(
|
|
_wallet,
|
|
_appStore,
|
|
_fiatConversationStore,
|
|
cryptoCurrencyHandler,
|
|
);
|
|
|
|
clone
|
|
..cryptoAmount = cryptoAmount
|
|
..cryptoFullBalance = cryptoFullBalance
|
|
..note = note
|
|
..sendAll = sendAll
|
|
..memo = memo
|
|
..stealthAddress = stealthAddress
|
|
..parsedAddress = parsedAddress ?? this.parsedAddress
|
|
..fiatAmount = fiatAmount ?? this.fiatAmount;
|
|
|
|
return clone;
|
|
}
|
|
}
|