mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-11-01 07:04:08 +01:00
Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
This commit is contained in:
@@ -3,37 +3,37 @@ import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/keyable.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'anonpay_invoice_info.g.dart';
|
||||
part 'anonpay_invoice_info.part.dart';
|
||||
|
||||
@HiveType(typeId: AnonpayInvoiceInfo.typeId)
|
||||
// @HiveType(typeId: AnonpayInvoiceInfo.typeId)
|
||||
class AnonpayInvoiceInfo extends HiveObject with Keyable implements AnonpayInfoBase {
|
||||
@HiveField(0)
|
||||
// @HiveField(0)
|
||||
final String invoiceId;
|
||||
@HiveField(1)
|
||||
// @HiveField(1)
|
||||
String status;
|
||||
@HiveField(2)
|
||||
// @HiveField(2)
|
||||
final double? fiatAmount;
|
||||
@HiveField(3)
|
||||
// @HiveField(3)
|
||||
final String? fiatEquiv;
|
||||
@HiveField(4)
|
||||
// @HiveField(4)
|
||||
final double? amountTo;
|
||||
@HiveField(5)
|
||||
// @HiveField(5)
|
||||
final String coinTo;
|
||||
@HiveField(6)
|
||||
// @HiveField(6)
|
||||
final String address;
|
||||
@HiveField(7)
|
||||
// @HiveField(7)
|
||||
final String clearnetUrl;
|
||||
@HiveField(8)
|
||||
// @HiveField(8)
|
||||
final String onionUrl;
|
||||
@HiveField(9)
|
||||
// @HiveField(9)
|
||||
final String clearnetStatusUrl;
|
||||
@HiveField(10)
|
||||
// @HiveField(10)
|
||||
final String onionStatusUrl;
|
||||
@HiveField(11)
|
||||
// @HiveField(11)
|
||||
final DateTime createdAt;
|
||||
@HiveField(12)
|
||||
// @HiveField(12)
|
||||
final String walletId;
|
||||
@HiveField(13)
|
||||
// @HiveField(13)
|
||||
final String provider;
|
||||
|
||||
static const typeId = ANONPAY_INVOICE_INFO_TYPE_ID;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'anonpay_invoice_info.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class AnonpayInvoiceInfoAdapter extends TypeAdapter<AnonpayInvoiceInfo> {
|
||||
@override
|
||||
final int typeId = 10;
|
||||
|
||||
@override
|
||||
AnonpayInvoiceInfo read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return AnonpayInvoiceInfo(
|
||||
invoiceId: fields[0] as String,
|
||||
clearnetUrl: fields[7] as String,
|
||||
onionUrl: fields[8] as String,
|
||||
clearnetStatusUrl: fields[9] as String,
|
||||
onionStatusUrl: fields[10] as String,
|
||||
status: fields[1] as String,
|
||||
fiatAmount: fields[2] as double?,
|
||||
fiatEquiv: fields[3] as String?,
|
||||
amountTo: fields[4] as double?,
|
||||
coinTo: fields[5] as String,
|
||||
address: fields[6] as String,
|
||||
createdAt: fields[11] as DateTime,
|
||||
walletId: fields[12] as String,
|
||||
provider: fields[13] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, AnonpayInvoiceInfo obj) {
|
||||
writer
|
||||
..writeByte(14)
|
||||
..writeByte(0)
|
||||
..write(obj.invoiceId)
|
||||
..writeByte(1)
|
||||
..write(obj.status)
|
||||
..writeByte(2)
|
||||
..write(obj.fiatAmount)
|
||||
..writeByte(3)
|
||||
..write(obj.fiatEquiv)
|
||||
..writeByte(4)
|
||||
..write(obj.amountTo)
|
||||
..writeByte(5)
|
||||
..write(obj.coinTo)
|
||||
..writeByte(6)
|
||||
..write(obj.address)
|
||||
..writeByte(7)
|
||||
..write(obj.clearnetUrl)
|
||||
..writeByte(8)
|
||||
..write(obj.onionUrl)
|
||||
..writeByte(9)
|
||||
..write(obj.clearnetStatusUrl)
|
||||
..writeByte(10)
|
||||
..write(obj.onionStatusUrl)
|
||||
..writeByte(11)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(12)
|
||||
..write(obj.walletId)
|
||||
..writeByte(13)
|
||||
..write(obj.provider);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is AnonpayInvoiceInfoAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
+60
-31
@@ -156,13 +156,12 @@ class CWBitcoin extends Bitcoin {
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
cryptoAmount: out.cryptoAmountMoney,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo.isNotEmpty ? out.memo : null,
|
||||
extra: out.extra,
|
||||
))
|
||||
@@ -178,7 +177,7 @@ class CWBitcoin extends Bitcoin {
|
||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
return electrumWallet.walletAddresses.addressesByReceiveType
|
||||
.map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||
.map<ElectrumSubAddress>((addr) => ElectrumSubAddress(
|
||||
id: addr.index,
|
||||
name: addr.name,
|
||||
address: addr.address,
|
||||
@@ -191,7 +190,7 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority,
|
||||
Future<Money> estimateFakeSendAllTxAmount(WalletBase wallet, TransactionPriority priority,
|
||||
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) async {
|
||||
try {
|
||||
final sk = ECPrivate.random();
|
||||
@@ -231,7 +230,7 @@ class CWBitcoin extends Bitcoin {
|
||||
|
||||
return estimatedTx.amount;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
return Money.zero(wallet.currency);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,10 +244,6 @@ class CWBitcoin extends Bitcoin {
|
||||
String formatterBitcoinAmountToString({required int amount}) =>
|
||||
bitcoinAmountToString(amount: amount);
|
||||
|
||||
@override
|
||||
double formatterBitcoinAmountToDouble({required int amount}) =>
|
||||
bitcoinAmountToDouble(amount: amount);
|
||||
|
||||
@override
|
||||
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
|
||||
|
||||
@@ -261,7 +256,10 @@ class CWBitcoin extends Bitcoin {
|
||||
List<BitcoinUnspent> getUnspents(Object wallet,
|
||||
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.unspentCoins.where((element) {
|
||||
|
||||
final coins = bitcoinWallet.unspentCoinsForCurrentAccount;
|
||||
|
||||
return coins.where((element) {
|
||||
switch (coinTypeToSpendFrom) {
|
||||
case UnspentCoinType.mweb:
|
||||
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
|
||||
@@ -322,6 +320,12 @@ class CWBitcoin extends Bitcoin {
|
||||
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
|
||||
}
|
||||
|
||||
@override
|
||||
bool hasSelectedLightning(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.walletAddresses.addressPageType is LightningAddressType;
|
||||
}
|
||||
|
||||
@override
|
||||
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option) {
|
||||
switch (option) {
|
||||
@@ -557,9 +561,9 @@ class CWBitcoin extends Bitcoin {
|
||||
|
||||
@override
|
||||
HardwareWalletService getTrezorHardwareWalletService(
|
||||
trezor.TrezorConnect connect, bool isBitcoin) {
|
||||
if (isBitcoin) return BitcoinTrezorService(connect);
|
||||
return LitecoinTrezorService(connect);
|
||||
trezor.TrezorConnect? connect, trezor.TrezorClient? client, bool isBitcoin) {
|
||||
if (isBitcoin) return BitcoinTrezorService(client!);
|
||||
return LitecoinTrezorService(connect!);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -692,28 +696,20 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
|
||||
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs) {
|
||||
if (pendingTransaction is PendingLightningTransaction) return outputs;
|
||||
|
||||
final pendingTx = pendingTransaction as PendingBitcoinTransaction;
|
||||
|
||||
if (!pendingTx.hasSilentPayment) {
|
||||
if (pendingTransaction is! PendingBitcoinTransaction || !pendingTransaction.hasSilentPayment) {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
final updatedOutputs = outputs.map((output) {
|
||||
try {
|
||||
final pendingOut = pendingTx.outputs[outputs.indexOf(output)];
|
||||
final updatedOutput = output;
|
||||
final stealthAddresses = pendingTransaction.stealthAddresses;
|
||||
if (stealthAddresses.length != outputs.length) {
|
||||
printV("well, that shouldn't happen");
|
||||
return outputs;
|
||||
}
|
||||
|
||||
updatedOutput.stealthAddress = P2trAddress.fromScriptPubkey(script: pendingOut.scriptPubKey)
|
||||
.toAddress(BitcoinNetwork.mainnet);
|
||||
return updatedOutput;
|
||||
} catch (_) {}
|
||||
|
||||
return output;
|
||||
}).toList();
|
||||
|
||||
return updatedOutputs;
|
||||
for (var i = 0; i < outputs.length; i++) {
|
||||
outputs[i].stealthAddress = stealthAddresses[i];
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -926,4 +922,37 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Money balanceForAccount(Object wallet, int accountIndex) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
final balance = bitcoinWallet.balanceForAccount(accountIndex);
|
||||
return balance.confirmed + balance.unconfirmed;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<int, Object> accountBalancesSnapshot(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return Map<int, Object>.of(bitcoinWallet.accountBalances);
|
||||
}
|
||||
|
||||
@override
|
||||
List<TransactionInfo> getCurrentAccountBitcoinTransactions(Object wallet) =>
|
||||
(wallet as ElectrumWallet).currentAccountBitcoinTransactions;
|
||||
|
||||
@override
|
||||
Future<void> setCurrentAccount(Object wallet, int accountIndex) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.setCurrentAccount(accountIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Account> getCurrentAccount(Object wallet) async {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
final acc = await electrumWallet.loadCurrentAccount();
|
||||
return Account(
|
||||
id: acc.accountIndex,
|
||||
label: acc.label,
|
||||
balance: electrumWallet.balanceForAccount(acc.accountIndex).confirmed.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:hash_wallet/entities/calculate_fiat_amount.dart';
|
||||
import 'package:hash_wallet/entities/fiat_currency.dart';
|
||||
import 'package:hash_wallet/entities/provider_types.dart';
|
||||
import 'package:hash_wallet/exchange/limits.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
enum ProviderRecommendation { bestRate, lowKyc, successRate }
|
||||
@@ -13,11 +14,11 @@ extension RecommendationTitle on ProviderRecommendation {
|
||||
String get title {
|
||||
switch (this) {
|
||||
case ProviderRecommendation.bestRate:
|
||||
return 'BEST RATE';
|
||||
return S.current.best_rate;
|
||||
case ProviderRecommendation.lowKyc:
|
||||
return 'LOW KYC';
|
||||
return S.current.low_kyc;
|
||||
case ProviderRecommendation.successRate:
|
||||
return 'HIGHEST SUCCESS RATE';
|
||||
return S.current.highest_success_rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +124,8 @@ class Quote extends SelectableOption {
|
||||
set setLimits(Limits limits) => this.limits = limits;
|
||||
|
||||
factory Quote.fromMoonPayJson(
|
||||
Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
|
||||
Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType,
|
||||
{String? customPaymentMethodType}) {
|
||||
final rate = isBuyAction
|
||||
? json['quoteCurrencyPrice'] as double? ?? 0.0
|
||||
: json['baseCurrencyPrice'] as double? ?? 0.0;
|
||||
@@ -149,6 +151,7 @@ class Quote extends SelectableOption {
|
||||
transactionFee: transactionFee,
|
||||
payout: _toDouble(json['quoteCurrencyAmount']) ?? 0.0,
|
||||
paymentType: paymentType,
|
||||
customPaymentMethodType: customPaymentMethodType,
|
||||
recommendations: [],
|
||||
quoteId: json['signature'] as String? ?? '',
|
||||
provider: ProvidersHelper.getProviderByType(ProviderType.moonpay),
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
class MoonPayPaymentMethod {
|
||||
MoonPayPaymentMethod({
|
||||
required this.type,
|
||||
required this.displayName,
|
||||
required this.iconUrl,
|
||||
required this.active,
|
||||
this.limitAmount,
|
||||
this.limitCurrencyCode,
|
||||
});
|
||||
|
||||
factory MoonPayPaymentMethod.fromJson(Map<String, dynamic> json) {
|
||||
final availability = json["availability"] as Map<String, dynamic>? ?? {};
|
||||
final limits = json["limits"] as Map<String, dynamic>?;
|
||||
final perTransaction = limits?["perTransaction"] as Map<String, dynamic>?;
|
||||
|
||||
return MoonPayPaymentMethod(
|
||||
type: json["type"] as String? ?? "",
|
||||
displayName: json["displayName"] as String? ?? "",
|
||||
iconUrl: json["iconUrl"] as String? ?? "",
|
||||
active: availability["active"] as bool? ?? false,
|
||||
limitAmount: (perTransaction?["limit"] as num?)?.toDouble(),
|
||||
limitCurrencyCode: perTransaction?["currencyCode"] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
final String type;
|
||||
final String displayName;
|
||||
final String iconUrl;
|
||||
final bool active;
|
||||
final double? limitAmount;
|
||||
final String? limitCurrencyCode;
|
||||
}
|
||||
@@ -1,41 +1,38 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import "dart:convert";
|
||||
import "dart:developer";
|
||||
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:hash_wallet/buy/buy_exception.dart';
|
||||
import 'package:hash_wallet/buy/buy_provider.dart';
|
||||
import 'package:hash_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:hash_wallet/buy/buy_quote.dart';
|
||||
import 'package:hash_wallet/order/order.dart';
|
||||
import 'package:hash_wallet/order/order_source_description.dart';
|
||||
import 'package:hash_wallet/buy/pairs_utils.dart';
|
||||
import 'package:hash_wallet/buy/payment_method.dart';
|
||||
import 'package:hash_wallet/entities/fiat_currency.dart';
|
||||
import 'package:hash_wallet/exchange/trade_state.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/palette.dart';
|
||||
import 'package:hash_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import "package:hash_wallet/.secrets.g.dart" as secrets;
|
||||
import "package:hash_wallet/buy/buy_exception.dart";
|
||||
import "package:hash_wallet/buy/buy_provider.dart";
|
||||
import "package:hash_wallet/buy/buy_provider_description.dart";
|
||||
import "package:hash_wallet/buy/buy_quote.dart";
|
||||
import "package:hash_wallet/buy/moonpay/moonpay_payment_methods.dart";
|
||||
import "package:hash_wallet/buy/pairs_utils.dart";
|
||||
import "package:hash_wallet/buy/payment_method.dart";
|
||||
import "package:hash_wallet/entities/fiat_currency.dart";
|
||||
import "package:hash_wallet/exchange/trade_state.dart";
|
||||
import "package:hash_wallet/generated/i18n.dart";
|
||||
import "package:hash_wallet/order/order.dart";
|
||||
import "package:hash_wallet/order/order_source_description.dart";
|
||||
import "package:hash_wallet/palette.dart";
|
||||
import "package:hash_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/utils/print_verbose.dart";
|
||||
import "package:cw_core/utils/proxy_wrapper.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:url_launcher/url_launcher.dart";
|
||||
|
||||
class MoonPayProvider extends BuyProvider {
|
||||
MoonPayProvider({
|
||||
required AppStore appStore,
|
||||
required WalletBase wallet,
|
||||
bool isTestEnvironment = false,
|
||||
required super.wallet,
|
||||
super.isTestEnvironment = false,
|
||||
}) : baseSellUrl = isTestEnvironment ? _baseSellTestUrl : _baseSellProductUrl,
|
||||
baseBuyUrl = isTestEnvironment ? _baseBuyTestUrl : _baseBuyProductUrl,
|
||||
this._appStore = appStore,
|
||||
_appStore = appStore,
|
||||
super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
hardwareWalletVM: null,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
@@ -44,159 +41,207 @@ class MoonPayProvider extends BuyProvider {
|
||||
|
||||
final AppStore _appStore;
|
||||
|
||||
static const _baseSellTestUrl = 'sell-sandbox.moonpay.com';
|
||||
static const _baseSellProductUrl = 'sell.moonpay.com';
|
||||
static const _baseBuyTestUrl = 'buy-staging.moonpay.com';
|
||||
static const _baseBuyProductUrl = 'buy.moonpay.com';
|
||||
static const _cIdBaseUrl = 'exchange-helper.such.software';
|
||||
static const _apiUrl = 'https://api.moonpay.com';
|
||||
static const _baseUrl = 'api.moonpay.com';
|
||||
static const _currenciesPath = '/v3/currencies';
|
||||
static const _buyQuote = '/buy_quote';
|
||||
static const _sellQuote = '/sell_quote';
|
||||
static const _baseSellTestUrl = "sell-sandbox.moonpay.com";
|
||||
static const _baseSellProductUrl = "sell.moonpay.com";
|
||||
static const _baseBuyTestUrl = "buy-staging.moonpay.com";
|
||||
static const _baseBuyProductUrl = "buy.moonpay.com";
|
||||
static const _cIdBaseUrl = "exchange-helper.such.software";
|
||||
static const _apiUrl = "https://api.moonpay.com";
|
||||
static const _baseUrl = "api.moonpay.com";
|
||||
static const _currenciesPath = "/v3/currencies";
|
||||
static const _buyQuote = "/buy_quote";
|
||||
static const _sellQuote = "/sell_quote";
|
||||
static const _paymentMethodsPath = "/api/moonpay/payment-methods";
|
||||
|
||||
static const _transactionsSuffix = '/v1/transactions';
|
||||
static const _transactionsSuffix = "/v1/transactions";
|
||||
|
||||
static const List<CryptoCurrency> _notSupportedCrypto = [];
|
||||
static const List<FiatCurrency> _notSupportedFiat = [];
|
||||
|
||||
static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey;
|
||||
|
||||
static const Map<PaymentType, String> _moonPayPaymentMethodStrings = {
|
||||
PaymentType.creditCard: 'credit_debit_card',
|
||||
PaymentType.debitCard: 'credit_debit_card',
|
||||
PaymentType.ach: 'ach_bank_transfer',
|
||||
PaymentType.applePay: 'apple_pay',
|
||||
PaymentType.googlePay: 'google_pay',
|
||||
PaymentType.sepa: 'sepa_bank_transfer',
|
||||
PaymentType.paypal: 'paypal',
|
||||
PaymentType.sepaOpenBankingPayment: 'sepa_open_banking_payment',
|
||||
PaymentType.gbpOpenBankingPayment: 'gbp_open_banking_payment',
|
||||
PaymentType.lowCostAch: 'low_cost_ach',
|
||||
PaymentType.mobileWallet: 'mobile_wallet',
|
||||
PaymentType.pixInstantPayment: 'pix_instant_payment',
|
||||
PaymentType.yellowCardBankTransfer: 'yellow_card_bank_transfer',
|
||||
PaymentType.fiatBalance: 'fiat_balance',
|
||||
PaymentType.revolutPay: 'revolut_pay',
|
||||
PaymentType.moonpayCashApp: 'cash_app',
|
||||
PaymentType.moonpayBalance: 'moonpay_balance',
|
||||
};
|
||||
|
||||
static final Map<String, PaymentType> _paymentTypeByMoonPayString = {
|
||||
for (final entry in _moonPayPaymentMethodStrings.entries) entry.value: entry.key,
|
||||
};
|
||||
|
||||
String? normalizePaymentMethod(PaymentType paymentMethod) =>
|
||||
_moonPayPaymentMethodStrings[paymentMethod];
|
||||
|
||||
PaymentType _getPaymentTypeByString(String? paymentMethod) =>
|
||||
_paymentTypeByMoonPayString[paymentMethod] ?? PaymentType.unknown;
|
||||
|
||||
final String baseBuyUrl;
|
||||
final String baseSellUrl;
|
||||
|
||||
@override
|
||||
String get providerDescription =>
|
||||
'MoonPay offers a fast and simple way to buy and sell cryptocurrencies';
|
||||
"MoonPay offers a fast and simple way to buy and sell cryptocurrencies";
|
||||
|
||||
@override
|
||||
String get title => 'MoonPay';
|
||||
String get title => "MoonPay";
|
||||
|
||||
@override
|
||||
String get lightIcon => 'assets/images/moonpay_light.png';
|
||||
String get lightIcon => "assets/images/moonpay_light.png";
|
||||
|
||||
@override
|
||||
String get darkIcon => 'assets/images/moonpay_dark.png';
|
||||
String get darkIcon => "assets/images/moonpay_dark.png";
|
||||
|
||||
@override
|
||||
bool get isAggregator => false;
|
||||
|
||||
static String get _apiKey => secrets.moonPayApiKey;
|
||||
String get _apiKey => isTestEnvironment ? secrets.moonPaySandboxApiKey : secrets.moonPayApiKey;
|
||||
|
||||
String get currencyCode =>
|
||||
walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title.toLowerCase();
|
||||
|
||||
String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId=';
|
||||
String get trackUrl => "$baseBuyUrl/transaction_receipt?transactionId=";
|
||||
|
||||
static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey;
|
||||
|
||||
static String themeToMoonPayTheme(MaterialThemeBase theme) {
|
||||
switch (theme.type) {
|
||||
case ThemeType.light:
|
||||
return 'light';
|
||||
case ThemeType.dark:
|
||||
return 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getMoonpaySignature(String query) async {
|
||||
final uri = Uri.https(_cIdBaseUrl, "/api/moonpay");
|
||||
Future<String> getMoonpaySignedQuery(String query) async {
|
||||
final uri =
|
||||
Uri.https(_cIdBaseUrl, "/api/moonpay", isTestEnvironment ? {"useSandbox": "true"} : null);
|
||||
|
||||
final response = await ProxyWrapper().post(
|
||||
clearnetUri: uri,
|
||||
headers: {'Content-Type': 'application/json', 'x-api-key': _exchangeHelperApiKey},
|
||||
body: json.encode({'query': query}),
|
||||
headers: {"Content-Type": "application/json", "x-api-key": _exchangeHelperApiKey},
|
||||
body: json.encode({"query": query}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return (jsonDecode(response.body) as Map<String, dynamic>)['signature'] as String;
|
||||
printV(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
return (jsonDecode(response.body) as Map<String, dynamic>)["query"] as String;
|
||||
} else {
|
||||
throw Exception(
|
||||
'Provider currently unavailable. Status: ${response.statusCode} ${response.body}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> fetchFiatCredentials(
|
||||
String fiatCurrency, String cryptocurrency, String? paymentMethod) async {
|
||||
final params = {'baseCurrencyCode': fiatCurrency.toLowerCase(), 'apiKey': _apiKey};
|
||||
|
||||
if (paymentMethod != null) params['paymentMethod'] = paymentMethod;
|
||||
|
||||
final path = '$_currenciesPath/${cryptocurrency.toLowerCase()}/limits';
|
||||
final url = Uri.https(_baseUrl, path, params);
|
||||
|
||||
try {
|
||||
final response = await ProxyWrapper().get(
|
||||
clearnetUri: url,
|
||||
headers: {'accept': 'application/json'},
|
||||
"Provider currently unavailable. Status: ${response.statusCode} ${response.body}",
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
||||
} else {
|
||||
printV('MoonPay does not support fiat: $fiatCurrency');
|
||||
return {};
|
||||
}
|
||||
} catch (e) {
|
||||
printV('MoonPay Error fetching fiat currencies: $e');
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<MoonPayPaymentMethod>> fetchMoonPayPaymentMethods({
|
||||
required String currencyCode,
|
||||
String transactionType = "buy",
|
||||
}) async {
|
||||
final String? countryCode = FiatCurrency.tryDeserialize(raw: currencyCode)?.apiCountryCode;
|
||||
|
||||
if (countryCode == null) {
|
||||
throw Exception("failed to determine country code for currency: $currencyCode");
|
||||
}
|
||||
|
||||
final params = <String, String>{
|
||||
"currencyCode": currencyCode,
|
||||
"countryCode": countryCode,
|
||||
"transactionType": transactionType,
|
||||
if (countryCode == "USA") "stateOfResidence": "NY",
|
||||
};
|
||||
|
||||
final uri = Uri.https(_cIdBaseUrl, _paymentMethodsPath, params);
|
||||
|
||||
final response = await ProxyWrapper().get(
|
||||
clearnetUri: uri,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"x-api-key": _exchangeHelperApiKey,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
if (response.statusCode == 500) {
|
||||
throw Exception("failed to fetch payment methods. Status: 500 Internal Server Error");
|
||||
}
|
||||
throw Exception(
|
||||
"failed to fetch payment methods. Status: ${response.statusCode} ${response.body}");
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final list = decoded["payment-methods"] as List? ?? [];
|
||||
|
||||
return list.map((e) => MoonPayPaymentMethod.fromJson(e as Map<String, dynamic>)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PaymentMethod>> getAvailablePaymentTypes(
|
||||
String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
|
||||
final List<PaymentMethod> paymentMethods = [];
|
||||
|
||||
if (isBuyAction) {
|
||||
final fiatBuyCredentials =
|
||||
await fetchFiatCredentials(fiatCurrency, cryptoCurrency.title, null);
|
||||
if (fiatBuyCredentials.isNotEmpty) {
|
||||
final paymentMethod = fiatBuyCredentials['paymentMethod'] as String?;
|
||||
try {
|
||||
final moonPayMethods = await fetchMoonPayPaymentMethods(
|
||||
currencyCode: fiatCurrency,
|
||||
transactionType: isBuyAction ? "buy" : "sell",
|
||||
);
|
||||
|
||||
for (final method in moonPayMethods) {
|
||||
if (!method.active) {
|
||||
continue;
|
||||
}
|
||||
final paymentType = _getPaymentTypeByString(method.type);
|
||||
|
||||
paymentMethods.add(PaymentMethod.fromMoonPayJson(
|
||||
fiatBuyCredentials, _getPaymentTypeByString(paymentMethod)));
|
||||
return paymentMethods;
|
||||
method,
|
||||
paymentType,
|
||||
),);
|
||||
}
|
||||
} catch (e) {
|
||||
printV("Error fetching payment methods: $e");
|
||||
}
|
||||
|
||||
return paymentMethods;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Quote>?> fetchQuote(
|
||||
{required CryptoCurrency cryptoCurrency,
|
||||
required FiatCurrency fiatCurrency,
|
||||
required double amount,
|
||||
required bool isBuyAction,
|
||||
required String walletAddress,
|
||||
PaymentType? paymentType,
|
||||
String? customPaymentMethodType,
|
||||
String? countryCode}) async {
|
||||
Future<List<Quote>?> fetchQuote({
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
required FiatCurrency fiatCurrency,
|
||||
required double amount,
|
||||
required bool isBuyAction,
|
||||
required String walletAddress,
|
||||
PaymentType? paymentType,
|
||||
String? customPaymentMethodType,
|
||||
String? countryCode,
|
||||
}) async {
|
||||
String? paymentMethod;
|
||||
|
||||
if (paymentType != null && paymentType != PaymentType.all) {
|
||||
paymentMethod = normalizePaymentMethod(paymentType);
|
||||
if (paymentMethod == null) paymentMethod = paymentType.name;
|
||||
} else {
|
||||
paymentMethod = 'credit_debit_card';
|
||||
paymentMethod = normalizePaymentMethod(paymentType) ?? customPaymentMethodType;
|
||||
}
|
||||
|
||||
final action = isBuyAction ? 'buy' : 'sell';
|
||||
final action = isBuyAction ? "buy" : "sell";
|
||||
|
||||
final formattedCryptoCurrency = _normalizeCurrency(cryptoCurrency);
|
||||
final baseCurrencyCode =
|
||||
isBuyAction ? fiatCurrency.name.toLowerCase() : cryptoCurrency.title.toLowerCase();
|
||||
|
||||
final params = {
|
||||
'baseCurrencyCode': baseCurrencyCode,
|
||||
'baseCurrencyAmount': amount.toStringAsFixed(2),
|
||||
'paymentMethod': paymentMethod,
|
||||
'areFeesIncluded': 'false',
|
||||
"baseCurrencyCode": baseCurrencyCode,
|
||||
"baseCurrencyAmount": amount.toStringAsFixed(2),
|
||||
if (paymentMethod != null) "paymentMethod": paymentMethod,
|
||||
"areFeesIncluded": "false",
|
||||
'apiKey': _apiKey
|
||||
};
|
||||
|
||||
log('MoonPay: Fetching $action quote: ${isBuyAction ? formattedCryptoCurrency : fiatCurrency.name.toLowerCase()} -> ${isBuyAction ? baseCurrencyCode : formattedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod');
|
||||
log("MoonPay: Fetching $action quote: ${isBuyAction ? formattedCryptoCurrency : fiatCurrency.name.toLowerCase()} -> ${isBuyAction ? baseCurrencyCode : formattedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod");
|
||||
|
||||
final quotePath = isBuyAction ? _buyQuote : _sellQuote;
|
||||
|
||||
final path = '$_currenciesPath/$formattedCryptoCurrency$quotePath';
|
||||
final path = "$_currenciesPath/$formattedCryptoCurrency$quotePath";
|
||||
final url = Uri.https(_baseUrl, path, params);
|
||||
try {
|
||||
final response = await ProxyWrapper().get(clearnetUri: url);
|
||||
@@ -216,19 +261,30 @@ class MoonPayProvider extends BuyProvider {
|
||||
}
|
||||
|
||||
final paymentMethods = data['paymentMethod'] as String?;
|
||||
final quote =
|
||||
Quote.fromMoonPayJson(data, isBuyAction, _getPaymentTypeByString(paymentMethods));
|
||||
|
||||
final quote = Quote.fromMoonPayJson(
|
||||
data,
|
||||
isBuyAction,
|
||||
_getPaymentTypeByString(paymentMethods ?? paymentMethod),
|
||||
customPaymentMethodType: paymentMethod,
|
||||
);
|
||||
|
||||
quote.setFiatCurrency = fiatCurrency;
|
||||
quote.setCryptoCurrency = cryptoCurrency;
|
||||
|
||||
return [quote];
|
||||
} else {
|
||||
printV('Moon Pay: Error fetching buy quote: ');
|
||||
}
|
||||
try {
|
||||
final errorData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final errorMessage = errorData["message"] as String?;
|
||||
printV("Error fetching buy quote: $errorMessage");
|
||||
return null;
|
||||
} catch (e) {
|
||||
printV("Error parsing error response: $e");
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
printV('Moon Pay: Error fetching buy quote: $e');
|
||||
printV("Exception while fetching buy quote: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -242,20 +298,21 @@ class MoonPayProvider extends BuyProvider {
|
||||
required String cryptoCurrencyAddress,
|
||||
String? countryCode}) async {
|
||||
final Map<String, String> params = {
|
||||
'theme': themeToMoonPayTheme(_appStore.themeStore.currentTheme),
|
||||
'language': _appStore.settingsStore.languageCode,
|
||||
'colorCode': _appStore.themeStore.currentTheme.isDark
|
||||
"theme": _appStore.themeStore.currentTheme.type.name,
|
||||
"language": _appStore.settingsStore.languageCode,
|
||||
"colorCode": _appStore.themeStore.currentTheme.isDark
|
||||
? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
|
||||
: '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
|
||||
'baseCurrencyCode': isBuyAction ? quote.fiatCurrency.name : quote.cryptoCurrency.name,
|
||||
'baseCurrencyAmount': amount.toStringAsFixed(2),
|
||||
'walletAddress': cryptoCurrencyAddress,
|
||||
'lockAmount': 'false',
|
||||
'showAllCurrencies': 'false',
|
||||
'showWalletAddressForm': 'false',
|
||||
"baseCurrencyCode": isBuyAction ? quote.fiatCurrency.name : quote.cryptoCurrency.name,
|
||||
"baseCurrencyAmount": amount.toStringAsFixed(2),
|
||||
"walletAddress": cryptoCurrencyAddress,
|
||||
"lockAmount": "false",
|
||||
"showAllCurrencies": "false",
|
||||
"showWalletAddressForm": "false",
|
||||
if (isBuyAction)
|
||||
'enabledPaymentMethods': normalizePaymentMethod(quote.paymentType) ??
|
||||
'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment',
|
||||
"enabledPaymentMethods": normalizePaymentMethod(quote.paymentType) ??
|
||||
quote.customPaymentMethodType ??
|
||||
"credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment",
|
||||
if (!isBuyAction) 'refundWalletAddress': cryptoCurrencyAddress
|
||||
};
|
||||
|
||||
@@ -272,20 +329,18 @@ class MoonPayProvider extends BuyProvider {
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
throw Exception('Could not launch URL');
|
||||
throw Exception("Could not launch URL");
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: 'MoonPay',
|
||||
alertContent: 'The MoonPay service is currently unavailable: $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
alertTitle: 'MoonPay',
|
||||
alertContent: 'The MoonPay service is currently unavailable: $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -297,18 +352,13 @@ class MoonPayProvider extends BuyProvider {
|
||||
required Map<String, String> params,
|
||||
String? amount,
|
||||
}) async {
|
||||
if (_apiKey.isNotEmpty) params['apiKey'] = _apiKey;
|
||||
if (_apiKey.isNotEmpty) params["apiKey"] = _apiKey;
|
||||
|
||||
final baseUrl = isBuyAction ? baseBuyUrl : baseSellUrl;
|
||||
final originalUri = Uri.https(baseUrl, '', params);
|
||||
final originalUri = Uri.https(baseUrl, "", params);
|
||||
|
||||
if (isTestEnvironment) return originalUri;
|
||||
|
||||
final signature = await getMoonpaySignature('?${originalUri.query}');
|
||||
final query = Map<String, dynamic>.from(originalUri.queryParameters);
|
||||
query['signature'] = signature;
|
||||
final signedUri = originalUri.replace(queryParameters: query);
|
||||
return signedUri;
|
||||
final query = await getMoonpaySignedQuery("?${originalUri.query}");
|
||||
return Uri.parse(query);
|
||||
}
|
||||
|
||||
Future<Order> findOrderById(String id) async {
|
||||
@@ -350,76 +400,4 @@ class MoonPayProvider extends BuyProvider {
|
||||
|
||||
return currency.toString().toLowerCase();
|
||||
}
|
||||
|
||||
String? normalizePaymentMethod(PaymentType paymentMethod) {
|
||||
switch (paymentMethod) {
|
||||
case PaymentType.creditCard:
|
||||
return 'credit_debit_card';
|
||||
case PaymentType.debitCard:
|
||||
return 'credit_debit_card';
|
||||
case PaymentType.ach:
|
||||
return 'ach_bank_transfer';
|
||||
case PaymentType.applePay:
|
||||
return 'apple_pay';
|
||||
case PaymentType.googlePay:
|
||||
return 'google_pay';
|
||||
case PaymentType.sepa:
|
||||
return 'sepa_bank_transfer';
|
||||
case PaymentType.paypal:
|
||||
return 'paypal';
|
||||
case PaymentType.sepaOpenBankingPayment:
|
||||
return 'sepa_open_banking_payment';
|
||||
case PaymentType.gbpOpenBankingPayment:
|
||||
return 'gbp_open_banking_payment';
|
||||
case PaymentType.lowCostAch:
|
||||
return 'low_cost_ach';
|
||||
case PaymentType.mobileWallet:
|
||||
return 'mobile_wallet';
|
||||
case PaymentType.pixInstantPayment:
|
||||
return 'pix_instant_payment';
|
||||
case PaymentType.yellowCardBankTransfer:
|
||||
return 'yellow_card_bank_transfer';
|
||||
case PaymentType.fiatBalance:
|
||||
return 'fiat_balance';
|
||||
case PaymentType.revolutPay:
|
||||
return 'revolut_pay';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
PaymentType _getPaymentTypeByString(String? paymentMethod) {
|
||||
switch (paymentMethod) {
|
||||
case 'ach_bank_transfer':
|
||||
return PaymentType.ach;
|
||||
case 'apple_pay':
|
||||
return PaymentType.applePay;
|
||||
case 'credit_debit_card':
|
||||
return PaymentType.creditCard;
|
||||
case 'fiat_balance':
|
||||
return PaymentType.fiatBalance;
|
||||
case 'gbp_open_banking_payment':
|
||||
return PaymentType.gbpOpenBankingPayment;
|
||||
case 'google_pay':
|
||||
return PaymentType.googlePay;
|
||||
case 'low_cost_ach':
|
||||
return PaymentType.lowCostAch;
|
||||
case 'mobile_wallet':
|
||||
return PaymentType.mobileWallet;
|
||||
case 'paypal':
|
||||
return PaymentType.paypal;
|
||||
case 'pix_instant_payment':
|
||||
return PaymentType.pixInstantPayment;
|
||||
case 'sepa_bank_transfer':
|
||||
return PaymentType.sepa;
|
||||
case 'sepa_open_banking_payment':
|
||||
return PaymentType.sepaOpenBankingPayment;
|
||||
case 'yellow_card_bank_transfer':
|
||||
return PaymentType.yellowCardBankTransfer;
|
||||
case 'revolut_pay':
|
||||
return PaymentType.revolutPay;
|
||||
default:
|
||||
return PaymentType.unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import "package:hash_wallet/buy/moonpay/moonpay_payment_methods.dart";
|
||||
import 'package:hash_wallet/core/selectable_option.dart';
|
||||
import "package:hash_wallet/entities/calculate_fiat_amount.dart";
|
||||
|
||||
enum PaymentType {
|
||||
all,
|
||||
@@ -33,6 +35,8 @@ enum PaymentType {
|
||||
fiatBalance,
|
||||
bancontact,
|
||||
pixPay,
|
||||
moonpayCashApp,
|
||||
moonpayBalance,
|
||||
unknown,
|
||||
}
|
||||
|
||||
@@ -103,6 +107,10 @@ extension PaymentTypeTitle on PaymentType {
|
||||
return 'Bancontact';
|
||||
case PaymentType.pixPay:
|
||||
return 'PIX Pay';
|
||||
case PaymentType.moonpayCashApp:
|
||||
return 'Cash App';
|
||||
case PaymentType.moonpayBalance:
|
||||
return 'MoonPay Balance';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -115,7 +123,7 @@ extension PaymentTypeTitle on PaymentType {
|
||||
case PaymentType.creditCard:
|
||||
case PaymentType.debitCard:
|
||||
case PaymentType.yellowCardBankTransfer:
|
||||
return 'assets/images/card.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/debit_card.svg';
|
||||
case PaymentType.bankTransfer:
|
||||
return 'assets/images/bank_light.svg';
|
||||
case PaymentType.skrill:
|
||||
@@ -132,17 +140,21 @@ extension PaymentTypeTitle on PaymentType {
|
||||
String? get darkIconPath {
|
||||
switch (this) {
|
||||
case PaymentType.all:
|
||||
return 'assets/images/usd_round_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/all_methods.svg';
|
||||
case PaymentType.creditCard:
|
||||
case PaymentType.debitCard:
|
||||
case PaymentType.yellowCardBankTransfer:
|
||||
return 'assets/images/card_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/debit_card.svg';
|
||||
case PaymentType.bankTransfer:
|
||||
return 'assets/images/bank_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/bank_transfer.svg';
|
||||
case PaymentType.skrill:
|
||||
return 'assets/images/skrill.svg';
|
||||
case PaymentType.applePay:
|
||||
return 'assets/images/apple_pay_round_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/apple_pay.svg';
|
||||
case PaymentType.googlePay:
|
||||
return "assets/new-ui/buy_payment_methods/google_pay.svg";
|
||||
case PaymentType.paypal:
|
||||
return "assets/new-ui/buy_payment_methods/paypal.svg";
|
||||
case PaymentType.revolutPay:
|
||||
return 'assets/images/revolut_dark.svg';
|
||||
default:
|
||||
@@ -150,6 +162,12 @@ extension PaymentTypeTitle on PaymentType {
|
||||
}
|
||||
}
|
||||
|
||||
bool get isMonochromeIcon => [
|
||||
"assets/new-ui/buy_payment_methods/all_methods.svg",
|
||||
"assets/new-ui/buy_payment_methods/debit_card.svg",
|
||||
"assets/new-ui/buy_payment_methods/bank_transfer.svg"
|
||||
].contains(darkIconPath);
|
||||
|
||||
String? get description {
|
||||
switch (this) {
|
||||
default:
|
||||
@@ -165,6 +183,8 @@ class PaymentMethod extends SelectableOption {
|
||||
required this.customIconPath,
|
||||
this.customDescription,
|
||||
this.customPaymentMethodType,
|
||||
this.customBadges = const [],
|
||||
this.customBottomLeftSubTitle,
|
||||
}) : super(title: paymentMethodType.title ?? customTitle);
|
||||
|
||||
final PaymentType paymentMethodType;
|
||||
@@ -172,8 +192,16 @@ class PaymentMethod extends SelectableOption {
|
||||
final String customIconPath;
|
||||
final String? customDescription;
|
||||
final String? customPaymentMethodType;
|
||||
final List<String> customBadges;
|
||||
final String? customBottomLeftSubTitle;
|
||||
bool isSelected = false;
|
||||
|
||||
@override
|
||||
List<String> get badges => customBadges;
|
||||
|
||||
@override
|
||||
String? get bottomLeftSubTitle => customBottomLeftSubTitle;
|
||||
|
||||
@override
|
||||
String? get description => paymentMethodType.description ?? customDescription;
|
||||
|
||||
@@ -190,14 +218,64 @@ class PaymentMethod extends SelectableOption {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: PaymentType.all,
|
||||
customTitle: 'All Payment Methods',
|
||||
customIconPath: 'assets/images/dollar_coin.svg');
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/all_methods.svg');
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromMoonPayJson(Map<String, dynamic> json, PaymentType paymentType) {
|
||||
factory PaymentMethod.fromOnramperJson(Map<String, dynamic> json) {
|
||||
final type = PaymentMethod.getPaymentTypeId(json['paymentTypeId'] as String?);
|
||||
return PaymentMethod(
|
||||
paymentMethodType: type ?? PaymentType.unknown,
|
||||
customPaymentMethodType: json['paymentTypeId'] as String?,
|
||||
customTitle: json['name'] as String? ?? 'Unknown',
|
||||
customIconPath: json['icon'] as String? ?? 'assets/new-ui/buy_payment_methods/debit_card.svg',
|
||||
customDescription: json['description'] as String?);
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromDFX(String paymentMethod, PaymentType paymentType) {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: paymentType,
|
||||
customTitle: json['paymentMethod'] as String,
|
||||
customIconPath: 'assets/images/card.png');
|
||||
customTitle: paymentMethod,
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/debit_card.svg');
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromMoonPayJson(MoonPayPaymentMethod method, PaymentType paymentType) {
|
||||
final List<String> badges = [];
|
||||
String? limitSubtitle;
|
||||
if (paymentType == PaymentType.moonpayCashApp) {
|
||||
badges.add('New');
|
||||
}
|
||||
if (method.limitAmount != null && method.limitCurrencyCode != null) {
|
||||
limitSubtitle =
|
||||
'max: ${formatWithCommas(method.limitAmount.toString())} ${method.limitCurrencyCode!.toUpperCase()}';
|
||||
}
|
||||
|
||||
return PaymentMethod(
|
||||
paymentMethodType: paymentType,
|
||||
customTitle: method.displayName,
|
||||
customIconPath: method.iconUrl.isNotEmpty ? method.iconUrl : 'assets/images/card.png',
|
||||
customPaymentMethodType: method.type,
|
||||
customBadges: badges,
|
||||
customBottomLeftSubTitle: limitSubtitle,
|
||||
);
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromMeldJson(Map<String, dynamic> json) {
|
||||
final type = PaymentMethod.getPaymentTypeId(json['paymentMethod'] as String?);
|
||||
final logos = json['logos'] as Map<String, dynamic>;
|
||||
return PaymentMethod(
|
||||
paymentMethodType: type ?? PaymentType.unknown,
|
||||
customTitle: json['name'] as String? ?? 'Unknown',
|
||||
customIconPath: logos['dark'] as String? ?? 'assets/new-ui/buy_payment_methods/debit_card.svg',
|
||||
customDescription: json['description'] as String?);
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromKryptonimJson(Map<String, dynamic> json) {
|
||||
final type = PaymentMethod.getPaymentTypeId(json['payment_method'] as String?);
|
||||
return PaymentMethod(
|
||||
paymentMethodType: type ?? PaymentType.unknown,
|
||||
customTitle: json['payment_method'] as String? ?? 'Unknown',
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/debit_card.svg',
|
||||
);
|
||||
}
|
||||
|
||||
static PaymentType? getPaymentTypeId(String? type) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import "package:hash_wallet/store/settings_store.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
|
||||
class CiBuildOverlay extends StatelessWidget {
|
||||
const CiBuildOverlay({required this.child, this.settingsStore, super.key});
|
||||
|
||||
static const _commit = String.fromEnvironment("CI_COMMIT");
|
||||
static const _branch = String.fromEnvironment("CI_BRANCH");
|
||||
static const isCiBuild = _commit != "" || _branch != "";
|
||||
|
||||
final Widget child;
|
||||
final SettingsStore? settingsStore;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!isCiBuild) {
|
||||
return child;
|
||||
}
|
||||
|
||||
final shortCommit = _commit.length > 7 ? _commit.substring(0, 7) : _commit;
|
||||
final label = [shortCommit, _branch].where((part) => part.isNotEmpty).join(" · ");
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.passthrough,
|
||||
textDirection: TextDirection.ltr,
|
||||
children: [
|
||||
child,
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Observer(
|
||||
builder: (_) => settingsStore?.showCiBuildOverlay ?? true
|
||||
? IgnorePointer(
|
||||
child: ExcludeSemantics(
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: ColoredBox(
|
||||
color: Colors.black54,
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
textDirection: TextDirection.ltr,
|
||||
textScaler: TextScaler.noScaling,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 10),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
abstract class AddressLookupProvider {
|
||||
AddressSource get source;
|
||||
|
||||
List<CryptoCurrency> get supportedCurrencies;
|
||||
|
||||
bool isEnabled(SettingsStore settingsStore);
|
||||
|
||||
bool canHandle(String query);
|
||||
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/bip_353/bip_353_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/ens/ens_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/fio/fio_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/lnurl_pay/lnurl_pay_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/mastodon/mastodon_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/nostr/nostr_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/openalias/openalias_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/twitter/twitter_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/unstoppable/unstoppable_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/wellknown/wellknown_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/zano/zano_alias_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/zcash/zcash_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/zcash/zcash_me_address_provider.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class AddressResolverService {
|
||||
AddressResolverService({required this.settingsStore});
|
||||
|
||||
final SettingsStore settingsStore;
|
||||
|
||||
// Sources available for address resolution and lookup settings.
|
||||
// Hash Bags privacy rule: only domain-anchored resolvers, where the lookup goes to the domain
|
||||
// the user typed (DNS for OpenAlias/BIP-353, the domain itself for .well-known/LNURL) or to a
|
||||
// public chain RPC (ENS). Never add a third-party lookup (X, Mastodon, Nostr, Yat, Unstoppable,
|
||||
// FIO, ThorChain, Zcash/Zano names) without an off-by-default setting and a PRIVACY.md update.
|
||||
static const supportedSources = [
|
||||
AddressSource.lnurlPay,
|
||||
AddressSource.openAlias,
|
||||
AddressSource.ens,
|
||||
AddressSource.wellKnown,
|
||||
AddressSource.bip353,
|
||||
];
|
||||
|
||||
List<AddressLookupProvider> _buildProviders() {
|
||||
final allProviders = <AddressLookupProvider>[
|
||||
LNUrlPayAddressProvider(),
|
||||
TwitterAddressProvider(),
|
||||
MastodonAddressProvider(),
|
||||
UnstoppableAddressProvider(),
|
||||
OpenaliasAddressProvider(),
|
||||
EnsAddressProvider(),
|
||||
ZcashNameAddressProvider(),
|
||||
ZcashMeAddressProvider(),
|
||||
WellKnownAddressProvider(),
|
||||
ZanoAliasAddressProvider(),
|
||||
Bip353AddressProvider(),
|
||||
FioAddressProvider(),
|
||||
NostrAddressProvider(),
|
||||
];
|
||||
|
||||
return allProviders.where((provider) => supportedSources.contains(provider.source)).toList();
|
||||
}
|
||||
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required WalletBase wallet,
|
||||
required CryptoCurrency currency,
|
||||
}) async {
|
||||
try {
|
||||
final providers = _buildProviders();
|
||||
// return first entry if currency is specified and provider supports it
|
||||
if (currency != null) {
|
||||
for (final provider in providers) {
|
||||
final cur = currency.title;
|
||||
final src = provider.source.label;
|
||||
|
||||
printV('[address resolver service] checking $src - $cur - $query');
|
||||
if (!provider.isEnabled(settingsStore)) {
|
||||
printV('[address resolver service] ...skipping $src (disabled)');
|
||||
continue;
|
||||
}
|
||||
if (!provider.canHandle(query)) {
|
||||
printV('[address resolver service] ...skipping $src (cannot handle)');
|
||||
continue;
|
||||
}
|
||||
if (!provider.supportedCurrencies.contains(currency)) {
|
||||
printV('[address resolver service] ...skipping $src (unsupported currency)');
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
printV('[address resolver service] ...resolving $src - $cur - $query');
|
||||
final results = await provider.resolve(
|
||||
query: query,
|
||||
currencies: [currency],
|
||||
wallet: wallet,
|
||||
);
|
||||
|
||||
if (results.isNotEmpty) {
|
||||
final parsedAddress = results.first.parsedAddressByCurrencyMap[currency];
|
||||
final parsedAddressStr =
|
||||
parsedAddress == null || parsedAddress.isEmpty ? 'N/A' : parsedAddress;
|
||||
printV('[address resolver service] resolved $src - $cur - $parsedAddressStr');
|
||||
return [results.first];
|
||||
}
|
||||
} catch (e) {
|
||||
printV('[address resolver service] Error resolving $src: $e');
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return []; // TODO: Implement multi-currency resolution if no specific currency is provided.
|
||||
|
||||
final tasks = <Future<List<ParsedAddress>>>[];
|
||||
|
||||
for (final provider in providers) {
|
||||
if (!provider.isEnabled(settingsStore)) continue;
|
||||
if (!provider.canHandle(query)) continue;
|
||||
|
||||
tasks.add(
|
||||
provider.resolve(
|
||||
query: query,
|
||||
currencies: provider.supportedCurrencies,
|
||||
wallet: wallet,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final results = await Future.wait(tasks);
|
||||
return results.expand((items) => items).toList();
|
||||
} catch (e) {
|
||||
printV('Error resolving address: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import 'package:hash_wallet/core/address_resolver/unstoppable/unstoppable_address_provider.dart';
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
|
||||
class AddressResolverUtils {
|
||||
static Future<Map<CryptoCurrency, String>> resolveUnstoppableDomainFromText({
|
||||
required String raw,
|
||||
required List<CryptoCurrency> currencies,
|
||||
}) async {
|
||||
final domain = extractUnstoppableDomain(raw);
|
||||
if (domain.isEmpty) return {};
|
||||
|
||||
final result = <CryptoCurrency, String>{};
|
||||
|
||||
for (final currency in currencies) {
|
||||
final address =
|
||||
await UnstoppableAddressProvider.fetchUnstoppableDomainAddress(domain, currency.title);
|
||||
if (address.isNotEmpty) {
|
||||
result[currency] = address;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static String extractUnstoppableDomain(String raw) {
|
||||
// sort by length to avoid matching shorter tld instead of longer
|
||||
final domains = List<String>.from(UnstoppableAddressProvider.unstoppableDomains)
|
||||
..sort((a, b) => b.length.compareTo(a.length));
|
||||
for (final tld in domains) {
|
||||
final pattern = RegExp(r'([A-Za-z0-9-]+)\.' + RegExp.escape(tld), caseSensitive: false);
|
||||
final match = pattern.firstMatch(raw);
|
||||
if (match != null) return match.group(0)!;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
static Map<CryptoCurrency, String> extractAddressesFromText({
|
||||
required String raw,
|
||||
required List<CryptoCurrency> currencies,
|
||||
bool requireSurroundingWhitespaces = true,
|
||||
}) {
|
||||
final result = <CryptoCurrency, String>{};
|
||||
var text = raw;
|
||||
|
||||
for (final currency in currencies) {
|
||||
final address = extractAddressByType(
|
||||
raw: text,
|
||||
type: currency,
|
||||
requireSurroundingWhitespaces: requireSurroundingWhitespaces,
|
||||
);
|
||||
|
||||
if (address != null && address.isNotEmpty) {
|
||||
result[currency] = address;
|
||||
text = text.replaceFirst(address, '');
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static String? extractAddressByType({
|
||||
required String raw,
|
||||
required CryptoCurrency type,
|
||||
bool requireSurroundingWhitespaces = true,
|
||||
}) {
|
||||
var addressPattern = AddressValidator.getAddressFromStringPattern(type);
|
||||
if (addressPattern == null) {
|
||||
printV('Unknown pattern for $type');
|
||||
return null;
|
||||
}
|
||||
if (requireSurroundingWhitespaces) addressPattern = "$BEFORE_REGEX$addressPattern$AFTER_REGEX";
|
||||
final text = stripHtmlTags(raw);
|
||||
final match = RegExp(addressPattern, multiLine: true, caseSensitive: false).firstMatch(text);
|
||||
if (match == null) return null;
|
||||
return match.group(0)?.replaceAllMapped(RegExp('[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'),
|
||||
(Match match) {
|
||||
String group = match.group(0)!;
|
||||
if (group.startsWith('bitcoincash:') ||
|
||||
group.startsWith('nano_') ||
|
||||
group.startsWith('ban_')) {
|
||||
return group;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
static bool isEmailFormat(String address) {
|
||||
final RegExp emailRegex = RegExp(
|
||||
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
|
||||
caseSensitive: false,
|
||||
);
|
||||
return emailRegex.hasMatch(address);
|
||||
}
|
||||
|
||||
static String stripHtmlTags(String value) {
|
||||
return value
|
||||
.replaceAll(RegExp(r'[\u2028\u2029]'), '\n')
|
||||
.replaceAll(RegExp(r'<br\s*/?>', caseSensitive: false), '\n')
|
||||
.replaceAll(RegExp(r'</p\s*>', caseSensitive: false), '\n')
|
||||
.replaceAll(RegExp(r'<[^>]*>'), ' ')
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
enum AddressSource {
|
||||
twitter(label: 'X', iconPath: 'assets/new-ui/address_sources/x.svg', alias: '@username'),
|
||||
unstoppableDomains(
|
||||
label: 'Unstoppable Domains',
|
||||
iconPath: 'assets/new-ui/address_sources/unstoppable.svg',
|
||||
alias: 'domain.tld'),
|
||||
openAlias(
|
||||
label: 'OpenAlias',
|
||||
iconPath: 'assets/new-ui/address_sources/openalias.svg',
|
||||
alias: 'name.domain.tld'),
|
||||
yatRecord(label: 'Yat', iconPath: 'assets/new-ui/address_sources/yat.svg', alias: '🎂🎂🎂'),
|
||||
fio(label: 'FIO', iconPath: 'assets/new-ui/address_sources/fio.svg', alias: 'user@domain'),
|
||||
ens(
|
||||
label: 'Ethereum Name Service',
|
||||
iconPath: 'assets/new-ui/address_sources/ens.svg',
|
||||
alias: 'domain.eth'),
|
||||
mastodon(
|
||||
label: 'Mastodon',
|
||||
iconPath: 'assets/new-ui/address_sources/mastodon.svg',
|
||||
alias: '@username@domain.tld'),
|
||||
nostr(
|
||||
label: 'Nostr',
|
||||
iconPath: 'assets/new-ui/address_sources/nostr.svg',
|
||||
alias: 'user@domain.tld'),
|
||||
thorChain(
|
||||
label: 'ThorChain',
|
||||
iconPath: 'assets/new-ui/address_sources/icon-thorchain.svg',
|
||||
alias: 'name'),
|
||||
wellKnown(
|
||||
label: '.wellknown',
|
||||
iconPath: 'assets/new-ui/address_sources/wellknown.svg',
|
||||
alias: 'domain.tld'),
|
||||
zanoAlias(
|
||||
label: 'Zano Alias', iconPath: 'assets/new-ui/address_sources/zano.svg', alias: '@alias'),
|
||||
bip353(
|
||||
label: 'BIP353',
|
||||
iconPath: 'assets/new-ui/address_sources/bip353.svg',
|
||||
alias: 'user@domain.com'),
|
||||
zcashAddress(
|
||||
label: 'Zcash.me',
|
||||
iconPath: 'assets/new-ui/address_sources/icon-zcash-me.svg',
|
||||
alias: 'zcash.me/username'),
|
||||
zcashName(
|
||||
label: 'Zcash Names',
|
||||
iconPath: 'assets/new-ui/address_sources/icon-zcashnames.svg',
|
||||
alias: 'name.zec'),
|
||||
lnurlPay(
|
||||
label: 'LNURL Pay',
|
||||
iconPath: 'assets/new-ui/address_sources/icon-lnurl-pay.svg',
|
||||
alias: 'user@domain.com'),
|
||||
contact(label: 'Contact', iconPath: ''),
|
||||
notParsed(label: 'Unknown', iconPath: ''),
|
||||
;
|
||||
|
||||
const AddressSource({
|
||||
required this.label,
|
||||
required this.iconPath,
|
||||
this.alias = '',
|
||||
});
|
||||
|
||||
final String label;
|
||||
final String iconPath;
|
||||
final String alias;
|
||||
}
|
||||
|
||||
extension AddressSourceIndex on AddressSource {
|
||||
int get raw => index;
|
||||
|
||||
static AddressSource fromRaw(int raw) =>
|
||||
AddressSource.values[raw.clamp(0, AddressSource.values.length - 1)];
|
||||
}
|
||||
|
||||
extension AddressSourceNameParser on AddressSource {
|
||||
static AddressSource fromLabel(String? text) {
|
||||
if (text == null || text.trim().isEmpty) {
|
||||
return AddressSource.notParsed;
|
||||
}
|
||||
final needle = text.trim().toLowerCase();
|
||||
return AddressSource.values.firstWhere(
|
||||
(src) => src.label.toLowerCase() == needle,
|
||||
orElse: () => AddressSource.notParsed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/bip_353/bip_353_record.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class Bip353AddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.bip353;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.btc];
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsBip353;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) {
|
||||
final value = query.trim();
|
||||
return value.contains('@') && value.contains('.') && !value.startsWith('@');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final result = <CryptoCurrency, String>{};
|
||||
String? dnsProof;
|
||||
|
||||
for (final cur in currencies) {
|
||||
final bip353AddressMap = await Bip353Record.fetchUriByCryptoCurrency(query, cur.title);
|
||||
if (bip353AddressMap == null || bip353AddressMap.isEmpty) continue;
|
||||
|
||||
final spAddress = bip353AddressMap['sp'];
|
||||
final address = bip353AddressMap['address'];
|
||||
final chosenAddress = spAddress?.isNotEmpty == true ? spAddress : address;
|
||||
|
||||
if (chosenAddress != null && chosenAddress.isNotEmpty) {
|
||||
result[cur] = chosenAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isEmpty) return [];
|
||||
|
||||
try {
|
||||
dnsProof = await Bip353Record.fetchDnsProof(query);
|
||||
} catch (e) {
|
||||
printV('Bip353Record.fetchDnsProof error: $e');
|
||||
}
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
addressSource: AddressSource.bip353,
|
||||
handle: query,
|
||||
bip353DnsProof: dnsProof ?? '',
|
||||
)
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving BIP353 address: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
-69
@@ -2,13 +2,9 @@ import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/widgets/alert_with_picker_option.dart';
|
||||
import 'package:hash_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:dnssec_proof/dnssec_proof.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Bip353Record {
|
||||
Bip353Record({
|
||||
@@ -103,69 +99,4 @@ class Bip353Record {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> pickBip353AddressChoice(
|
||||
BuildContext context,
|
||||
String bip353Name,
|
||||
Map<String, String> addressMap,
|
||||
) async {
|
||||
if (addressMap.length == 1) {
|
||||
return addressMap.values.first;
|
||||
}
|
||||
|
||||
final chosenAddress = await _showAddressChoiceDialog(context, bip353Name, addressMap);
|
||||
|
||||
return chosenAddress;
|
||||
}
|
||||
|
||||
static Future<String?> _showAddressChoiceDialog(
|
||||
BuildContext context,
|
||||
String bip353Name,
|
||||
Map<String, String> addressMap,
|
||||
) async {
|
||||
final entriesList = addressMap.entries.toList();
|
||||
final List<Map<String, String>> displayItems = entriesList.map((entry) {
|
||||
final originalKey = entry.key;
|
||||
final originalValue = entry.value;
|
||||
|
||||
final extendedKeyName = keyDisplayMap[originalKey] ?? originalKey;
|
||||
final truncatedValue = _truncate(originalValue, front: 6, back: 6);
|
||||
|
||||
return {
|
||||
'displayKey': extendedKeyName,
|
||||
'displayValue': truncatedValue,
|
||||
'originalKey': originalKey,
|
||||
'originalValue': originalValue,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
String? selectedOriginalValue;
|
||||
|
||||
if (context.mounted) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithPickerOption(
|
||||
alertTitle: S.of(context).multiple_addresses_detected + '\n$bip353Name',
|
||||
alertTitleTextSize: 14,
|
||||
alertSubtitle: S.of(context).please_choose_one + ':',
|
||||
options: displayItems,
|
||||
onOptionSelected: (Map<String, String> chosenItem) {
|
||||
selectedOriginalValue = chosenItem['originalValue'];
|
||||
},
|
||||
alertBarrierDismissible: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return selectedOriginalValue;
|
||||
}
|
||||
|
||||
static String _truncate(String value, {int front = 6, int back = 6}) {
|
||||
if (value.length <= front + back) return value;
|
||||
|
||||
final start = value.substring(0, front);
|
||||
final end = value.substring(value.length - back);
|
||||
return '$start...$end';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/ens/ens_record.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class EnsAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.ens;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.eth];
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsENS;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) => query.endsWith('.eth'); // ENS handle example: name.eth
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
final Map<CryptoCurrency, String> result = {};
|
||||
|
||||
for (final cur in currencies) {
|
||||
final address = await EnsRecord.fetchEnsAddress(query, cur, wallet: wallet);
|
||||
if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") {
|
||||
result[cur] = address;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isNotEmpty) {
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
addressSource: AddressSource.ens,
|
||||
handle: query,
|
||||
)
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import "package:hash_wallet/evm/evm.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/utils/print_verbose.dart";
|
||||
import "package:cw_core/utils/proxy_wrapper.dart";
|
||||
import "package:cw_core/wallet_base.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:ens_dart/ens_dart.dart";
|
||||
import "package:web3dart/web3dart.dart";
|
||||
|
||||
class EnsRecord {
|
||||
static Future<String> fetchEnsAddress(
|
||||
String name,
|
||||
CryptoCurrency cur, {
|
||||
WalletBase? wallet,
|
||||
}) async {
|
||||
Web3Client? _client;
|
||||
|
||||
if (wallet?.type == WalletType.ethereum || wallet?.type == WalletType.polygon) {
|
||||
_client = evm!.getWeb3Client(wallet!);
|
||||
}
|
||||
|
||||
_client ??= Web3Client(
|
||||
"https://ethereum-rpc.publicnode.com",
|
||||
ProxyWrapper().getHttpIOClient(),
|
||||
);
|
||||
|
||||
final registryEns = Ens(client: _client).withName(name);
|
||||
final coinType = getEnsCoinType(cur);
|
||||
|
||||
if (coinType == null) {
|
||||
printV("Unsupported currency for ENS: $cur");
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if the ENS name has its resolver set. If not, return an empty string.
|
||||
final resolverAddress = await registryEns.getResolverAddress(registryEns.nodeHash);
|
||||
|
||||
if (isZeroAddress(resolverAddress)) {
|
||||
printV("No resolver set for ENS name: $name");
|
||||
return "";
|
||||
}
|
||||
|
||||
final resolverEns = Ens(client: _client,address: resolverAddress).withName(name);
|
||||
|
||||
if (coinType == CoinType.ETH) {
|
||||
return (await resolverEns.getAddress()).hex;
|
||||
} else {
|
||||
return await resolverEns.getCoinAddress(coinType);
|
||||
}
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static bool isZeroAddress(EthereumAddress address) =>
|
||||
address.addressBytes.every((byte) => byte == 0);
|
||||
|
||||
static CoinType? getEnsCoinType(CryptoCurrency cur) => switch (cur) {
|
||||
CryptoCurrency.xmr => CoinType.XMR,
|
||||
CryptoCurrency.btc => CoinType.BTC,
|
||||
CryptoCurrency.ltc => CoinType.LTC,
|
||||
CryptoCurrency.eth => CoinType.ETH,
|
||||
CryptoCurrency.maticpoly => CoinType.MATIC,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class FioAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.fio;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => AddressValidator.reliableValidateCurrencies;
|
||||
|
||||
@override
|
||||
bool canHandle(String q) =>
|
||||
!q.startsWith('@') &&
|
||||
q.contains('@') &&
|
||||
!q.contains('.'); // FIO handle example: username@domain
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsFio;
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
final Map<CryptoCurrency, String> result = {};
|
||||
try {
|
||||
final bool isFioRegistered = await FioAddressProvider.checkAvail(query);
|
||||
if (!isFioRegistered) return [];
|
||||
|
||||
for (final cur in currencies) {
|
||||
final address = await FioAddressProvider.getPubAddress(query, cur.title);
|
||||
if (address != null && address.isNotEmpty) {
|
||||
result[cur] = address;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isNotEmpty) {
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result, addressSource: AddressSource.fio, handle: query)
|
||||
];
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
printV('FioAddressProvider.resolve error: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static const _apiAuthority = 'fio.blockpane.com';
|
||||
static const _availCheck = '/v1/chain/avail_check';
|
||||
static const _getAddress = '/v1/chain/get_pub_address';
|
||||
|
||||
static Future<bool> checkAvail(String fioAddress) async {
|
||||
bool isFioRegistered = false;
|
||||
final headers = {'Content-Type': 'application/json'};
|
||||
final body = <String, String>{"fio_name": fioAddress};
|
||||
|
||||
final uri = Uri.https(_apiAuthority, _availCheck);
|
||||
final response = await ProxyWrapper().post(
|
||||
clearnetUri: uri,
|
||||
headers: headers,
|
||||
body: json.encode(body),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return isFioRegistered;
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
isFioRegistered = responseJSON['is_registered'] as int == 1;
|
||||
|
||||
return isFioRegistered;
|
||||
}
|
||||
|
||||
static Future<String?> getPubAddress(String fioAddress, String token) async {
|
||||
final headers = {'Content-Type': 'application/json'};
|
||||
final body = <String, String>{
|
||||
"fio_address": fioAddress,
|
||||
"chain_code": token.toUpperCase(),
|
||||
"token_code": token.toUpperCase(),
|
||||
};
|
||||
|
||||
final uri = Uri.https(_apiAuthority, _getAddress);
|
||||
final response = await ProxyWrapper().post(
|
||||
clearnetUri: uri,
|
||||
headers: headers,
|
||||
body: json.encode(body),
|
||||
);
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode == 400) {
|
||||
final error = responseJSON['error'] as String;
|
||||
final message = responseJSON['message'] as String;
|
||||
printV('${error}\n$message');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
final String message = responseJSON['message'] as String? ?? 'Unknown error';
|
||||
|
||||
printV('Error fetching public address for token $token: $message');
|
||||
return null;
|
||||
}
|
||||
|
||||
final String pubAddress = responseJSON['public_address'] as String? ?? '';
|
||||
|
||||
if (pubAddress.isNotEmpty) {
|
||||
return pubAddress;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/lnurl_pay/lnurlpay_record.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class LNUrlPayAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.lnurlPay;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.btcln];
|
||||
|
||||
@override
|
||||
bool canHandle(String q) =>
|
||||
q.contains('.') && q.contains('@'); // LNURL-pay handle example: user@domain.com
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsLNUrl;
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
if (wallet.type != WalletType.bitcoin) return [];
|
||||
|
||||
final formattedName = query.trim();
|
||||
if (formattedName.isEmpty) return [];
|
||||
|
||||
final result = <CryptoCurrency, String>{};
|
||||
|
||||
for (final currency in currencies) {
|
||||
if (!supportedCurrencies.contains(currency)) continue;
|
||||
|
||||
final record = await LNUrlPayRecord.fetchAddressAndName(
|
||||
formattedName: formattedName,
|
||||
currency: currency,
|
||||
);
|
||||
|
||||
if (record != null && record.address.isNotEmpty) {
|
||||
result[currency] = record.address;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
addressSource: AddressSource.lnurlPay,
|
||||
handle: formattedName,
|
||||
profileName: formattedName,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving LNURL-pay address: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_resolver_utils.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/mastodon/mastodon_api.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class MastodonAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.mastodon;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => AddressValidator.reliableValidateCurrencies;
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsMastodon;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) =>
|
||||
query.startsWith('@') &&
|
||||
query.contains('@', 1) &&
|
||||
query.contains('.', 1); // Mastodon handle example: @username@hostname.xxx
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final subText = query.substring(1); // Remove '@' from the beginning of the handle
|
||||
final hostNameIndex = subText.indexOf('@');
|
||||
if (hostNameIndex <= 0 || hostNameIndex == subText.length - 1) return [];
|
||||
|
||||
final userName = subText.substring(0, hostNameIndex);
|
||||
final hostName = subText.substring(hostNameIndex + 1);
|
||||
|
||||
final result = <CryptoCurrency, String>{};
|
||||
|
||||
final mastodonUser = await MastodonAPI.lookupUserByUserName(
|
||||
userName: userName,
|
||||
apiHost: hostName,
|
||||
);
|
||||
if (mastodonUser == null) return [];
|
||||
|
||||
final bio = AddressResolverUtils.stripHtmlTags(mastodonUser.note);
|
||||
result.addAll(
|
||||
AddressResolverUtils.extractAddressesFromText(
|
||||
raw: bio,
|
||||
currencies: currencies,
|
||||
),
|
||||
);
|
||||
|
||||
final pinnedPosts = await MastodonAPI.getPinnedPosts(
|
||||
userId: mastodonUser.id,
|
||||
apiHost: hostName,
|
||||
);
|
||||
|
||||
if (pinnedPosts.isNotEmpty) {
|
||||
final pinnedPostsText = pinnedPosts
|
||||
.map((item) => AddressResolverUtils.stripHtmlTags(item.content))
|
||||
.where((text) => text.isNotEmpty)
|
||||
.join('\n');
|
||||
|
||||
result.addAll(
|
||||
AddressResolverUtils.extractAddressesFromText(
|
||||
raw: pinnedPostsText,
|
||||
currencies: currencies,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (result.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
addressSource: AddressSource.mastodon,
|
||||
handle: query,
|
||||
profileImageUrl: mastodonUser.profileImageUrl,
|
||||
profileName: mastodonUser.username,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving Mastodon address: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'dart:convert';
|
||||
import 'package:hash_wallet/core/address_resolver/mastodon/mastodon_user.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
|
||||
class MastodonAPI {
|
||||
static const httpsScheme = 'https';
|
||||
static const userPath = '/api/v1/accounts/lookup';
|
||||
static const statusesPath = '/api/v1/accounts/:id/statuses';
|
||||
|
||||
static Future<MastodonUser?> lookupUserByUserName(
|
||||
{required String userName, required String apiHost}) async {
|
||||
try {
|
||||
final queryParams = {'acct': userName};
|
||||
|
||||
final uri = Uri(
|
||||
scheme: httpsScheme,
|
||||
host: apiHost,
|
||||
path: userPath,
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
|
||||
final response = await ProxyWrapper().get(clearnetUri: uri);
|
||||
|
||||
if (response.statusCode != 200) return null;
|
||||
|
||||
final Map<String, dynamic> responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
return MastodonUser.fromJson(responseJSON);
|
||||
} catch (e) {
|
||||
printV('Error in lookupUserByUserName: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<PinnedPost>> getPinnedPosts({
|
||||
required String userId,
|
||||
required String apiHost,
|
||||
}) async {
|
||||
try {
|
||||
final queryParams = {'pinned': 'true'};
|
||||
|
||||
final uri = Uri(
|
||||
scheme: httpsScheme,
|
||||
host: apiHost,
|
||||
path: statusesPath.replaceAll(':id', userId),
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
|
||||
final response = await ProxyWrapper().get(clearnetUri: uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected HTTP status: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final List<dynamic> responseJSON = json.decode(response.body) as List<dynamic>;
|
||||
|
||||
return responseJSON.map((json) => PinnedPost.fromJson(json as Map<String, dynamic>)).toList();
|
||||
} catch (e) {
|
||||
printV('Error in getPinnedPosts: $e');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
class MastodonUser {
|
||||
String id;
|
||||
String username;
|
||||
String profileImageUrl;
|
||||
String acct;
|
||||
String note;
|
||||
|
||||
MastodonUser({
|
||||
required this.id,
|
||||
required this.username,
|
||||
required this.profileImageUrl,
|
||||
required this.acct,
|
||||
required this.note,
|
||||
});
|
||||
|
||||
factory MastodonUser.fromJson(Map<String, dynamic> json) {
|
||||
return MastodonUser(
|
||||
id: json['id'] as String,
|
||||
username: json['username'] as String? ?? '',
|
||||
acct: json['acct'] as String,
|
||||
note: json['note'] as String,
|
||||
profileImageUrl: json['avatar'] as String? ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
class PinnedPost {
|
||||
final String id;
|
||||
final String content;
|
||||
|
||||
PinnedPost({required this.id, required this.content});
|
||||
|
||||
factory PinnedPost.fromJson(Map<String, dynamic> json) {
|
||||
return PinnedPost(
|
||||
id: json['id'] as String,
|
||||
content: json['content'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_resolver_utils.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/nostr/nostr_api.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class NostrAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.nostr;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => AddressValidator.reliableValidateCurrencies;
|
||||
|
||||
@override
|
||||
bool canHandle(String q) =>
|
||||
AddressResolverUtils.isEmailFormat(q); // Nostr handle example: username@domain
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsNostr;
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final profile = await NostrProfileHandler.queryProfile(query);
|
||||
if (profile == null) return [];
|
||||
|
||||
final data = await NostrProfileHandler.processRelays(profile, query);
|
||||
if (data == null) return [];
|
||||
|
||||
final result = <CryptoCurrency, String>{};
|
||||
|
||||
String queryTxt = data.about;
|
||||
|
||||
for (final cur in currencies) {
|
||||
final addr = AddressResolverUtils.extractAddressByType(raw: queryTxt, type: cur);
|
||||
if (addr != null && addr.isNotEmpty) {
|
||||
result[cur] = addr;
|
||||
queryTxt = queryTxt.replaceFirst(addr, '');
|
||||
}
|
||||
}
|
||||
if (result.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
addressSource: AddressSource.nostr,
|
||||
handle: query,
|
||||
profileImageUrl: data.picture,
|
||||
profileName: data.name,
|
||||
)
|
||||
];
|
||||
} catch (e) {
|
||||
printV('Error looking up Nostr profile: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hash_wallet/core/address_resolver/nostr/nostr_user.dart';
|
||||
import 'package:nostr_tools/nostr_tools.dart';
|
||||
import 'dart:async' show Completer, TimeoutException, runZonedGuarded;
|
||||
|
||||
class NostrProfileHandler {
|
||||
static final relayToDomainMap = {
|
||||
'relay.snort.social': 'snort.social',
|
||||
};
|
||||
|
||||
static final Nip05 _nip05 = Nip05();
|
||||
|
||||
static Future<ProfilePointer?> queryProfile(String nip05Address) async {
|
||||
final profile = await _nip05.queryProfile(nip05Address);
|
||||
if (profile?.pubkey != null && profile?.relays?.isNotEmpty == true) {
|
||||
return profile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<UserMetadata?> processRelays(
|
||||
ProfilePointer profile,
|
||||
String nip05Address,
|
||||
) async {
|
||||
final userDomain = _extractDomain(nip05Address);
|
||||
const int metaKind = 0;
|
||||
|
||||
// Domain-matched relays first
|
||||
for (final String relayUrl in profile.relays ?? []) {
|
||||
final relayDomain =
|
||||
relayToDomainMap[_getDomainFromRelayUrl(relayUrl)] ?? _getDomainFromRelayUrl(relayUrl);
|
||||
|
||||
if (relayDomain == userDomain) {
|
||||
final data = await _fetchInfoFromRelay(relayUrl, profile.pubkey, [metaKind]);
|
||||
if (data != null) return data;
|
||||
}
|
||||
}
|
||||
|
||||
// Then try every remaining relay
|
||||
for (final String relayUrl in profile.relays ?? []) {
|
||||
final data = await _fetchInfoFromRelay(relayUrl, profile.pubkey, [metaKind]);
|
||||
if (data != null) return data;
|
||||
}
|
||||
|
||||
// Nothing found
|
||||
return null;
|
||||
}
|
||||
|
||||
static const Duration _relayTimeout = Duration(seconds: 3);
|
||||
|
||||
static Future<UserMetadata?> _fetchInfoFromRelay(
|
||||
String relayUrl, String userPubKey, List<int> kinds) async {
|
||||
// sanitize so obvious junk (like '#') doesn't reach connect()
|
||||
final clean = _sanitizeRelay(relayUrl);
|
||||
if (clean.isEmpty) return null;
|
||||
|
||||
final result = Completer<UserMetadata?>();
|
||||
|
||||
runZonedGuarded(() async {
|
||||
try {
|
||||
final relay = RelayApi(relayUrl: clean);
|
||||
|
||||
final stream = await relay.connect().timeout(
|
||||
_relayTimeout,
|
||||
onTimeout: () {
|
||||
relay.close();
|
||||
throw TimeoutException('Relay connect timeout');
|
||||
},
|
||||
);
|
||||
|
||||
relay.sub([
|
||||
Filter(kinds: kinds, authors: [userPubKey])
|
||||
]);
|
||||
|
||||
final sub = stream.listen((msg) {
|
||||
if (msg.type == 'EVENT' && !result.isCompleted) {
|
||||
try {
|
||||
final event = msg.message as Event;
|
||||
final jsonMap = json.decode(event.content) as Map<String, dynamic>;
|
||||
result.complete(UserMetadata.fromJson(jsonMap));
|
||||
} catch (_) {
|
||||
if (!result.isCompleted) result.complete(null);
|
||||
}
|
||||
}
|
||||
}, onError: (_) {
|
||||
if (!result.isCompleted) result.complete(null);
|
||||
}, onDone: () {
|
||||
if (!result.isCompleted) result.complete(null);
|
||||
});
|
||||
|
||||
final value = await result.future.timeout(_relayTimeout, onTimeout: () => null);
|
||||
await sub.cancel();
|
||||
relay.close();
|
||||
if (!result.isCompleted) result.complete(value);
|
||||
} catch (_) {
|
||||
if (!result.isCompleted) result.complete(null);
|
||||
}
|
||||
}, (error, stack) {
|
||||
// swallow ALL async errors from the websocket layer (including "was not upgraded to websocket")
|
||||
if (!result.isCompleted) result.complete(null);
|
||||
});
|
||||
|
||||
return result.future;
|
||||
}
|
||||
|
||||
static String _sanitizeRelay(String url) {
|
||||
url = url.replaceFirst(RegExp(r'^https?://'), 'wss://');
|
||||
final uri = Uri.parse(url);
|
||||
return Uri(
|
||||
scheme: uri.scheme.isEmpty ? 'wss' : uri.scheme,
|
||||
host: uri.host,
|
||||
port: uri.hasPort ? uri.port : 443,
|
||||
).toString();
|
||||
}
|
||||
|
||||
static String _extractDomain(String nip05) => nip05.split('@').last;
|
||||
|
||||
static String _getDomainFromRelayUrl(String url) {
|
||||
try {
|
||||
return Uri.parse(url).host;
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
class UserMetadata {
|
||||
final String name;
|
||||
final String lnurl;
|
||||
final String email;
|
||||
final String picture;
|
||||
final String about;
|
||||
final String nip05;
|
||||
final String banner;
|
||||
final String website;
|
||||
|
||||
UserMetadata({
|
||||
required this.name,
|
||||
required this.lnurl,
|
||||
required this.email,
|
||||
required this.picture,
|
||||
required this.about,
|
||||
required this.nip05,
|
||||
required this.banner,
|
||||
required this.website,
|
||||
});
|
||||
|
||||
factory UserMetadata.fromJson(Map<String, dynamic> json) {
|
||||
return UserMetadata(
|
||||
name: json['name'] as String? ?? '',
|
||||
lnurl: json['lud06'] as String? ?? '',
|
||||
email: json['lud16'] as String? ?? '',
|
||||
picture: json['picture'] as String? ?? '',
|
||||
about: json['about'] as String? ?? '',
|
||||
nip05: json['nip05'] as String? ?? '',
|
||||
banner: json['banner'] as String? ?? '',
|
||||
website: json['website'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/openalias/openalias_record.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class OpenaliasAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.openAlias;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.xmr, CryptoCurrency.btc];
|
||||
|
||||
@override
|
||||
bool canHandle(String q) {
|
||||
final formattedName = OpenaliasRecord.formatDomainName(q);
|
||||
return formattedName.contains(".");
|
||||
}
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsOpenAlias;
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final formatted = OpenaliasRecord.formatDomainName(query);
|
||||
|
||||
final txtRecords = await OpenaliasRecord.lookupOpenAliasRecord(formatted);
|
||||
if (txtRecords == null) return [];
|
||||
|
||||
final result = <CryptoCurrency, String>{};
|
||||
|
||||
for (final cur in currencies) {
|
||||
final rec = OpenaliasRecord.fetchAddressAndName(
|
||||
formattedName: formatted,
|
||||
ticker: cur.title.toLowerCase(),
|
||||
txtRecord: txtRecords,
|
||||
);
|
||||
|
||||
if (rec.address.isNotEmpty) result[cur] = rec.address;
|
||||
}
|
||||
|
||||
if (result.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
addressSource: AddressSource.openAlias,
|
||||
handle: query,
|
||||
)
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving OpenAlias address: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
|
||||
class OpenaliasRecord {
|
||||
OpenaliasRecord({
|
||||
required this.address,
|
||||
required this.name,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String address;
|
||||
final String description;
|
||||
|
||||
static String formatDomainName(String name) {
|
||||
String formattedName = name;
|
||||
|
||||
if (name.contains("@")) {
|
||||
formattedName = name.replaceAll("@", ".");
|
||||
}
|
||||
|
||||
return formattedName;
|
||||
}
|
||||
|
||||
static Future<List<RRecord>?> lookupOpenAliasRecord(String name) async {
|
||||
try {
|
||||
final txtRecord = await DnsUtils.lookupRecord(name, RRecordType.TXT, dnssec: true);
|
||||
|
||||
return txtRecord;
|
||||
} catch (e) {
|
||||
printV("${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static OpenaliasRecord fetchAddressAndName({
|
||||
required String formattedName,
|
||||
required String ticker,
|
||||
required List<RRecord> txtRecord,
|
||||
}) {
|
||||
var address = '';
|
||||
var name = formattedName;
|
||||
var note = '';
|
||||
|
||||
final addrRe = RegExp(r'recipient_address=([^;\s]+)', caseSensitive: false);
|
||||
final nameRe = RegExp(r'recipient_name=([^;]+)', caseSensitive: false);
|
||||
final noteRe = RegExp(r'tx_description=([^;]+)', caseSensitive: false);
|
||||
final tag = 'oa1:$ticker';
|
||||
|
||||
for (final rr in txtRecord) {
|
||||
final txt = rr.data.replaceAll('"', '');
|
||||
|
||||
if (!txt.toLowerCase().contains(tag)) continue;
|
||||
|
||||
final addrM = addrRe.firstMatch(txt);
|
||||
if (addrM != null) address = addrM.group(1)!;
|
||||
|
||||
final nameM = nameRe.firstMatch(txt);
|
||||
if (nameM != null) name = nameM.group(1)!.trim();
|
||||
|
||||
final noteM = noteRe.firstMatch(txt);
|
||||
if (noteM != null) note = noteM.group(1)!.trim();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return OpenaliasRecord(address: address, name: name, description: note);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class ParsedAddress {
|
||||
const ParsedAddress({
|
||||
required this.parsedAddressByCurrencyMap,
|
||||
this.addressSource = AddressSource.notParsed,
|
||||
this.handle = '',
|
||||
this.profileImageUrl = '',
|
||||
this.profileName = '',
|
||||
this.description = '',
|
||||
this.bip353DnsProof,
|
||||
});
|
||||
|
||||
final Map<CryptoCurrency, String> parsedAddressByCurrencyMap;
|
||||
final AddressSource addressSource;
|
||||
final String handle;
|
||||
final String profileImageUrl;
|
||||
final String profileName;
|
||||
final String description;
|
||||
final String? bip353DnsProof;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_resolver_utils.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/twitter/twitter_api.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/twitter/twitter_user.dart';
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class TwitterAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.twitter;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => AddressValidator.reliableValidateCurrencies;
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsTwitter;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) =>
|
||||
query.startsWith('@') && !query.substring(1).contains('@'); // x handle example: @username
|
||||
|
||||
/// Resolves a Twitter handle to a list of ParsedAddress objects by extracting potential cryptocurrency addresses
|
||||
/// from the user's, profile.
|
||||
/// fallback to resolving Unstoppable Domains if no addresses are found in each of the Twitter user's bio, location, and pinned tweet.
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
final formattedName = query.replaceFirst("@", "");
|
||||
try {
|
||||
final result = <CryptoCurrency, String>{};
|
||||
|
||||
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
|
||||
|
||||
final bio = (text: twitterUser.description, whitespaces: true);
|
||||
final location = (text: twitterUser.location, whitespaces: false);
|
||||
final pinnedTweet = (text: twitterUser.pinnedTweet?.text ?? '', whitespaces: true);
|
||||
|
||||
final twitterTexts = [
|
||||
bio,
|
||||
location,
|
||||
pinnedTweet,
|
||||
].where((item) => item.text.isNotEmpty);
|
||||
|
||||
for (final item in twitterTexts) {
|
||||
result.addAll(
|
||||
AddressResolverUtils.extractAddressesFromText(
|
||||
raw: item.text,
|
||||
currencies: currencies,
|
||||
requireSurroundingWhitespaces: item.whitespaces,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.isEmpty) {
|
||||
final domainResult = await AddressResolverUtils.resolveUnstoppableDomainFromText(
|
||||
raw: item.text,
|
||||
currencies: currencies,
|
||||
);
|
||||
|
||||
if (domainResult.isNotEmpty) {
|
||||
return [_buildTwitterParsedAddress(query, twitterUser, domainResult)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isEmpty) return [];
|
||||
|
||||
return [_buildTwitterParsedAddress(query, twitterUser, result)];
|
||||
} catch (e) {
|
||||
printV('[address resolver service] Twitter error: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
ParsedAddress _buildTwitterParsedAddress(
|
||||
String query,
|
||||
TwitterUser twitterUser,
|
||||
Map<CryptoCurrency, String> result,
|
||||
) {
|
||||
return ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
addressSource: AddressSource.twitter,
|
||||
handle: query,
|
||||
profileImageUrl: twitterUser.profileImageUrl,
|
||||
profileName: twitterUser.name,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:hash_wallet/core/address_resolver/twitter/twitter_user.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
class TwitterApi {
|
||||
static const twitterBearerToken = secrets.twitterBearerToken;
|
||||
static const httpsScheme = 'https';
|
||||
static const apiHost = 'api.twitter.com';
|
||||
static const userPath = '/2/users/by/username/';
|
||||
|
||||
static Future<TwitterUser> lookupUserByName({required String userName}) async {
|
||||
final queryParams = {
|
||||
'user.fields': 'description,profile_image_url,location',
|
||||
'expansions': 'pinned_tweet_id',
|
||||
'tweet.fields': 'note_tweet'
|
||||
};
|
||||
final headers = {'authorization': 'Bearer $twitterBearerToken'};
|
||||
final uri = Uri(
|
||||
scheme: httpsScheme,
|
||||
host: apiHost,
|
||||
path: userPath + userName,
|
||||
queryParameters: queryParams);
|
||||
|
||||
final response =
|
||||
await ProxyWrapper().get(clearnetUri: uri, headers: headers).catchError((error) {
|
||||
throw Exception('HTTP request failed: $error');
|
||||
});
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final Map<String, dynamic> responseJSON = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
if (responseJSON['errors'] != null &&
|
||||
!responseJSON['errors'][0]['detail']
|
||||
.toString()
|
||||
.contains("Could not find tweet with pinned_tweet_id")) {
|
||||
throw Exception(responseJSON['errors'][0]['detail']);
|
||||
}
|
||||
|
||||
return TwitterUser.fromJson(responseJSON, _getPinnedTweet(responseJSON));
|
||||
}
|
||||
|
||||
static Tweet? _getPinnedTweet(Map<String, dynamic> responseJSON) {
|
||||
try {
|
||||
final tweetId = responseJSON['data']['pinned_tweet_id'] as String?;
|
||||
if (tweetId == null || responseJSON['includes'] == null) return null;
|
||||
|
||||
final tweetIncludes = List.from(responseJSON['includes']['tweets'] as List);
|
||||
final pinnedTweetData = tweetIncludes.firstWhere(
|
||||
(tweet) => tweet['id'] == tweetId,
|
||||
orElse: () => null,
|
||||
) as Map<String, dynamic>?;
|
||||
|
||||
if (pinnedTweetData == null) return null;
|
||||
|
||||
final pinnedTweetText =
|
||||
(pinnedTweetData['note_tweet']?['text'] ?? pinnedTweetData['text']) as String;
|
||||
|
||||
return Tweet(id: tweetId, text: pinnedTweetText);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
class TwitterUser {
|
||||
TwitterUser(
|
||||
{required this.id,
|
||||
required this.username,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.location,
|
||||
required this.profileImageUrl,
|
||||
this.pinnedTweet});
|
||||
|
||||
final String id;
|
||||
final String username;
|
||||
final String name;
|
||||
final String description;
|
||||
final String location;
|
||||
final String profileImageUrl;
|
||||
final Tweet? pinnedTweet;
|
||||
|
||||
factory TwitterUser.fromJson(Map<String, dynamic> json, [Tweet? pinnedTweet]) {
|
||||
final profileImageUrl = json['data']['profile_image_url'] as String? ?? '';
|
||||
final scaledProfileImageUrl = profileImageUrl.replaceFirst('normal', '200x200');
|
||||
return TwitterUser(
|
||||
id: json['data']['id'] as String,
|
||||
username: json['data']['username'] as String? ?? '',
|
||||
name: json['data']['name'] as String,
|
||||
description: json['data']['description'] as String? ?? '',
|
||||
location: json['data']['location'] as String? ?? '',
|
||||
profileImageUrl: scaledProfileImageUrl,
|
||||
pinnedTweet: pinnedTweet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Tweet {
|
||||
Tweet({
|
||||
required this.id,
|
||||
required this.text,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String text;
|
||||
|
||||
factory Tweet.fromJson(Map<String, dynamic> json) {
|
||||
return Tweet(
|
||||
id: json['id'] as String,
|
||||
text: json['text'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/openalias/openalias_record.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class UnstoppableAddressProvider extends AddressLookupProvider {
|
||||
static const unstoppableDomains = [
|
||||
"888",
|
||||
"academy",
|
||||
"agency",
|
||||
"altimist",
|
||||
"anime",
|
||||
"austin",
|
||||
"bald",
|
||||
"bay",
|
||||
"benji",
|
||||
"bet",
|
||||
"binanceus",
|
||||
"bitcoin",
|
||||
"bitget",
|
||||
"bitscrunch",
|
||||
"blockchain",
|
||||
"boomer",
|
||||
"boston",
|
||||
"ca",
|
||||
"caw",
|
||||
"cc",
|
||||
"chat",
|
||||
"chomp",
|
||||
"clay",
|
||||
"club",
|
||||
"co",
|
||||
"com",
|
||||
"company",
|
||||
"crypto",
|
||||
"dao",
|
||||
"design",
|
||||
"dfz",
|
||||
"digital",
|
||||
"doga",
|
||||
"donut",
|
||||
"dream",
|
||||
"email",
|
||||
"emir",
|
||||
"eth",
|
||||
"ethermail",
|
||||
"family",
|
||||
"farms",
|
||||
"finance",
|
||||
"fun",
|
||||
"fyi",
|
||||
"games",
|
||||
"global",
|
||||
"go",
|
||||
"group",
|
||||
"guru",
|
||||
"hi",
|
||||
"hockey",
|
||||
"host",
|
||||
"info",
|
||||
"io",
|
||||
"klever",
|
||||
"kresus",
|
||||
"kryptic",
|
||||
"lfg",
|
||||
"life",
|
||||
"live",
|
||||
"llc",
|
||||
"ltc",
|
||||
"ltd",
|
||||
"manga",
|
||||
"me",
|
||||
"media",
|
||||
"metropolis",
|
||||
"miami",
|
||||
"miku",
|
||||
"money",
|
||||
"moon",
|
||||
"mumu",
|
||||
"net",
|
||||
"network",
|
||||
"news",
|
||||
"nft",
|
||||
"npc",
|
||||
"onchain",
|
||||
"online",
|
||||
"org",
|
||||
"podcast",
|
||||
"pog",
|
||||
"polygon",
|
||||
"base",
|
||||
"arbitrum",
|
||||
"press",
|
||||
"privacy",
|
||||
"pro",
|
||||
"propykeys",
|
||||
"pudgy",
|
||||
"pw",
|
||||
"quantum",
|
||||
"rad",
|
||||
"raiin",
|
||||
"retardio",
|
||||
"rip",
|
||||
"rocks",
|
||||
"secret",
|
||||
"services",
|
||||
"site",
|
||||
"smobler",
|
||||
"social",
|
||||
"solutions",
|
||||
"space",
|
||||
"stepn",
|
||||
"store",
|
||||
"studio",
|
||||
"systems",
|
||||
"tball",
|
||||
"tea",
|
||||
"team",
|
||||
"tech",
|
||||
"technology",
|
||||
"today",
|
||||
"tribe",
|
||||
"u",
|
||||
"ubu",
|
||||
"uno",
|
||||
"unstoppable",
|
||||
"vip",
|
||||
"wallet",
|
||||
"website",
|
||||
"wif",
|
||||
"wifi",
|
||||
"witg",
|
||||
"work",
|
||||
"world",
|
||||
"wrkx",
|
||||
"wtf",
|
||||
"x",
|
||||
"xmr",
|
||||
"xyz",
|
||||
"zil",
|
||||
"zone",
|
||||
"pizza"
|
||||
];
|
||||
|
||||
@override
|
||||
AddressSource get source => AddressSource.unstoppableDomains;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.xmr, CryptoCurrency.btc];
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsUnstoppableDomains;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) {
|
||||
// Unstoppable Domains handle example: name.crypto
|
||||
final formattedName = OpenaliasRecord.formatDomainName(query);
|
||||
final domainParts = formattedName.split('.');
|
||||
final name = domainParts.last;
|
||||
return domainParts.length > 1 &&
|
||||
domainParts.first.isNotEmpty &&
|
||||
name.isNotEmpty &&
|
||||
unstoppableDomains.any((domain) => name.trim() == domain);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final formattedName = OpenaliasRecord.formatDomainName(query);
|
||||
final result = <CryptoCurrency, String>{};
|
||||
|
||||
for (final currency in currencies) {
|
||||
final address = await fetchUnstoppableDomainAddress(
|
||||
formattedName,
|
||||
currency.title,
|
||||
);
|
||||
|
||||
if (address.isNotEmpty) {
|
||||
result[currency] = address;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: result,
|
||||
profileName: formattedName,
|
||||
addressSource: AddressSource.unstoppableDomains,
|
||||
handle: formattedName,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving Unstoppable Domain: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> fetchUnstoppableDomainAddress(String domain, String ticker) async {
|
||||
var address = '';
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
"https://api.unstoppabledomains.com/profile/public/${Uri.encodeQueryComponent(domain)}?fields=records");
|
||||
final response = await ProxyWrapper().get(clearnetUri: uri);
|
||||
|
||||
final jsonParsed = json.decode(response.body) as Map<String, dynamic>;
|
||||
if (jsonParsed["records"] == null) {
|
||||
throw Exception(".records response from $uri is empty");
|
||||
}
|
||||
;
|
||||
final records = jsonParsed["records"] as Map<String, dynamic>;
|
||||
final key = "crypto.${ticker.toUpperCase()}.address";
|
||||
if (records[key] == null) {
|
||||
throw Exception(".records.${key} response from $uri is empty");
|
||||
}
|
||||
|
||||
return records[key] as String? ?? '';
|
||||
} catch (e) {
|
||||
printV('Unstoppable domain error: ${e.toString()}');
|
||||
address = '';
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/wellknown/wellknown_record.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class WellKnownAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.wellKnown;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.nano];
|
||||
|
||||
@override
|
||||
bool canHandle(String q) => q.contains('.') && q.contains('@');
|
||||
|
||||
// .well-known handle example:
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsWellKnown;
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final rec = await WellKnownRecord.fetch(query, CryptoCurrency.nano);
|
||||
if (rec == null || rec.address.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: {CryptoCurrency.nano: rec.address},
|
||||
addressSource: AddressSource.wellKnown,
|
||||
handle: query,
|
||||
profileName: rec.title ?? '',
|
||||
profileImageUrl: rec.imageUrl ?? '',
|
||||
)
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving Well-Known address: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
class WellKnownRecord {
|
||||
WellKnownRecord({
|
||||
required this.address,
|
||||
required this.alias,
|
||||
required this.imageUrl,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
final String address;
|
||||
final String alias;
|
||||
final String? imageUrl;
|
||||
final String? title;
|
||||
|
||||
static const _jsonLocation = {CryptoCurrency.nano: 'nano-currency'};
|
||||
|
||||
static Future<WellKnownRecord?> fetch(
|
||||
String username,
|
||||
CryptoCurrency currency,
|
||||
) async {
|
||||
final location = _jsonLocation[currency];
|
||||
if (location == null) {
|
||||
printV('well-known: unsupported coin $currency');
|
||||
return null;
|
||||
}
|
||||
|
||||
final parts = username.split('@');
|
||||
if (parts.length < 2) {
|
||||
printV('well-known: missing @ in "$username"');
|
||||
return null;
|
||||
}
|
||||
final alias = (parts.length == 3 ? parts[1] : parts[0]).trim();
|
||||
final domain = parts.last.trim();
|
||||
final encoded = Uri.encodeComponent(alias.isEmpty ? '_' : alias);
|
||||
|
||||
final uri = Uri.https(
|
||||
domain,
|
||||
'/.well-known/$location.json',
|
||||
{'names': encoded},
|
||||
);
|
||||
|
||||
try {
|
||||
final res = await ProxyWrapper().get(
|
||||
clearnetUri: uri,
|
||||
headers: {'Accept': 'application/json'},
|
||||
);
|
||||
|
||||
final cType = res.headers['content-type']?.toLowerCase() ?? '';
|
||||
if (res.statusCode != 200 || !cType.contains('application/json')) {
|
||||
printV('well-known: $uri → ${res.statusCode} $cType');
|
||||
return null;
|
||||
}
|
||||
|
||||
final body = json.decode(res.body) as Map<String, dynamic>;
|
||||
final items = body['names'] as List<dynamic>? ?? const [];
|
||||
|
||||
for (final raw in items.whereType<Map>()) {
|
||||
if (raw['name'].toString().toLowerCase() == alias.toLowerCase()) {
|
||||
return WellKnownRecord(
|
||||
address: raw['address']?.toString() ?? '',
|
||||
alias: alias,
|
||||
imageUrl: raw['image']?.toString(),
|
||||
title: raw['name']?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
printV('well-known: alias "$alias" not found in list');
|
||||
} catch (e) {
|
||||
printV('well-known: network / JSON error → $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class ZanoAliasAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.zanoAlias;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.zano];
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsZanoAlias;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) => query.startsWith('@'); // zano handle example: @username
|
||||
|
||||
static const _zanoUrl = 'http://37.27.100.59:10500/json_rpc';
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
if (!currencies.contains(CryptoCurrency.zano)) return [];
|
||||
|
||||
final formattedName = query.replaceFirst("@", "");
|
||||
if (formattedName.isEmpty) return [];
|
||||
|
||||
final zanoAddress = await fetchZanoAliasAddress(formattedName);
|
||||
if (zanoAddress == null || zanoAddress.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: {CryptoCurrency.zano: zanoAddress},
|
||||
addressSource: AddressSource.zanoAlias,
|
||||
handle: query,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving Zano Alias: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> fetchZanoAliasAddress(String alias) async {
|
||||
try {
|
||||
final uri = Uri.parse(_zanoUrl);
|
||||
final response = await ProxyWrapper().post(
|
||||
clearnetUri: uri,
|
||||
body: json.encode({
|
||||
'id': 0,
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'get_alias_details',
|
||||
'params': {'alias': alias},
|
||||
}),
|
||||
);
|
||||
|
||||
final jsonParsed = json.decode(response.body) as Map<String, dynamic>;
|
||||
return jsonParsed['result']?['alias_details']?['address'] as String?;
|
||||
} catch (e) {
|
||||
printV('[address resolver] Zano Alias error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/zcash/zcash_names_record.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class ZcashNameAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.zcashName;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.zec];
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsZcashNames;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) {
|
||||
final lowerText = query.toLowerCase();
|
||||
return lowerText.endsWith(".zec") || lowerText.endsWith(".zcash");
|
||||
} // Zcash handle example: name.zec
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final address = await ZcashNamesRecord.fetchZcashNamesAddress(query);
|
||||
if (address == null || address.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: {CryptoCurrency.zec: address},
|
||||
profileName: query,
|
||||
addressSource: AddressSource.zcashName,
|
||||
handle: query,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving Zcash Name: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:hash_wallet/core/address_resolver/address_lookup_provider.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
class ZcashMeAddressProvider extends AddressLookupProvider {
|
||||
@override
|
||||
AddressSource get source => AddressSource.zcashAddress;
|
||||
|
||||
@override
|
||||
List<CryptoCurrency> get supportedCurrencies => [CryptoCurrency.zec];
|
||||
|
||||
@override
|
||||
bool isEnabled(SettingsStore settingsStore) => settingsStore.lookupsZcashAddress;
|
||||
|
||||
@override
|
||||
bool canHandle(String query) {
|
||||
final value = query.trim().toLowerCase();
|
||||
return value.startsWith('zcash.me/') && value.split('/').last.isNotEmpty;
|
||||
} // zcash.me profile example: zcash.me/username
|
||||
|
||||
@override
|
||||
Future<List<ParsedAddress>> resolve({
|
||||
required String query,
|
||||
required List<CryptoCurrency> currencies,
|
||||
required WalletBase wallet,
|
||||
}) async {
|
||||
try {
|
||||
final handle = query.trim().split('/').last;
|
||||
if (handle.isEmpty) return [];
|
||||
|
||||
final address = await _fetchZcashAddress(handle);
|
||||
if (address == null || address.isEmpty) return [];
|
||||
|
||||
return [
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: {CryptoCurrency.zec: address},
|
||||
profileName: handle,
|
||||
addressSource: AddressSource.zcashAddress,
|
||||
handle: query,
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error resolving zcash.me address: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _fetchZcashAddress(String handle) async {
|
||||
final url = Uri.parse('https://zcash.me/$handle');
|
||||
|
||||
try {
|
||||
final response = await ProxyWrapper().get(clearnetUri: url);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final addressRegex = RegExp(
|
||||
r'(t1[0-9A-Za-z]{33}|t3[0-9A-Za-z]{33}|zs[a-z0-9]{76}|u1[a-z0-9]{1,300})',
|
||||
caseSensitive: true,
|
||||
);
|
||||
|
||||
final match = addressRegex.firstMatch(response.body);
|
||||
return match?.group(0);
|
||||
}
|
||||
} catch (e) {
|
||||
printV('[address resolver] Error fetching zcash.me profile: $e');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
class ZcashNamesRecord {
|
||||
static final Uri _mainnetEndpoint = Uri.parse('https://main.zcashnames.com');
|
||||
|
||||
static final RegExp _nameRegExp = RegExp(r'^[A-Za-z0-9_-]{1,63}$');
|
||||
|
||||
static Future<String?> fetchZcashNamesAddress(String input) async {
|
||||
final name = _extractName(input);
|
||||
if (name == null) return null;
|
||||
|
||||
try {
|
||||
final response = await ProxyWrapper().post(
|
||||
clearnetUri: _mainnetEndpoint,
|
||||
headers: const {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'jsonrpc': '2.0',
|
||||
'id': 1,
|
||||
'method': 'resolve',
|
||||
'params': {'query': name},
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
printV('ZcashNames: non-200 status ${response.statusCode} for $name');
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(response.body);
|
||||
if (decoded is! Map<String, dynamic>) return null;
|
||||
if (decoded['error'] != null) return null;
|
||||
|
||||
final result = decoded['result'];
|
||||
if (result is! Map<String, dynamic>) return null;
|
||||
|
||||
final address = result['address'];
|
||||
if (address is! String || address.isEmpty) return null;
|
||||
|
||||
if (!_isValidZcashAddress(address)) {
|
||||
printV('ZcashNames: returned address failed validation for $name');
|
||||
return null;
|
||||
}
|
||||
return address;
|
||||
} catch (e) {
|
||||
printV('ZcashNames: lookup failed for $name: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String? _extractName(String input) {
|
||||
final lower = input.toLowerCase().trim();
|
||||
String? raw;
|
||||
if (lower.endsWith('.zcash')) {
|
||||
raw = lower.substring(0, lower.length - '.zcash'.length);
|
||||
} else if (lower.endsWith('.zec')) {
|
||||
raw = lower.substring(0, lower.length - '.zec'.length);
|
||||
}
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
if (!_nameRegExp.hasMatch(raw)) return null;
|
||||
return raw;
|
||||
}
|
||||
|
||||
static bool _isValidZcashAddress(String address) {
|
||||
final pattern = AddressValidator.getAddressFromStringPattern(CryptoCurrency.zec);
|
||||
if (pattern == null) return false;
|
||||
return RegExp('^(?:$pattern)\$').hasMatch(address);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,11 @@ const BEFORE_REGEX = '(^|\\s)';
|
||||
const AFTER_REGEX = '(\$|\\s)';
|
||||
|
||||
class AddressValidator extends TextValidator {
|
||||
AddressValidator({required CryptoCurrency type, bool isTestnet = false})
|
||||
: super(
|
||||
AddressValidator({
|
||||
required CryptoCurrency type,
|
||||
bool isTestnet = false,
|
||||
String? network,
|
||||
}) : super(
|
||||
errorMessage: S.current.error_text_address,
|
||||
useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type)
|
||||
? (String txt) {
|
||||
@@ -28,11 +31,36 @@ class AddressValidator extends TextValidator {
|
||||
);
|
||||
}
|
||||
: null,
|
||||
pattern: getPattern(type, isTestnet: isTestnet),
|
||||
pattern: getPattern(type, isTestnet: isTestnet, network: network),
|
||||
length: getLength(type),
|
||||
);
|
||||
|
||||
static String getPattern(CryptoCurrency type, {bool isTestnet = false}) {
|
||||
static const List<CryptoCurrency> reliableValidateCurrencies = [
|
||||
CryptoCurrency.xmr,
|
||||
CryptoCurrency.btc,
|
||||
CryptoCurrency.ltc,
|
||||
CryptoCurrency.bch,
|
||||
CryptoCurrency.trx,
|
||||
CryptoCurrency.nano,
|
||||
CryptoCurrency.sol,
|
||||
CryptoCurrency.zano,
|
||||
CryptoCurrency.ada,
|
||||
CryptoCurrency.xrp,
|
||||
CryptoCurrency.xhv,
|
||||
CryptoCurrency.zaddr,
|
||||
CryptoCurrency.zec,
|
||||
CryptoCurrency.dcr,
|
||||
CryptoCurrency.rvn,
|
||||
CryptoCurrency.near,
|
||||
CryptoCurrency.rune,
|
||||
CryptoCurrency.scrt,
|
||||
CryptoCurrency.stx,
|
||||
CryptoCurrency.kmd,
|
||||
CryptoCurrency.doge,
|
||||
CryptoCurrency.btcln,
|
||||
];
|
||||
|
||||
static String? getPattern(CryptoCurrency type, {bool isTestnet = false, String? network}) {
|
||||
var pattern = "";
|
||||
if (type is Erc20Token) {
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
@@ -80,6 +108,7 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.eth:
|
||||
case CryptoCurrency.baseEth:
|
||||
case CryptoCurrency.arbEth:
|
||||
case CryptoCurrency.robEth:
|
||||
case CryptoCurrency.mana:
|
||||
case CryptoCurrency.matic:
|
||||
case CryptoCurrency.maticpoly:
|
||||
@@ -141,13 +170,7 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.hbar:
|
||||
pattern = '[0-9a-zA-Z.]+';
|
||||
case CryptoCurrency.zec:
|
||||
pattern = '(?:'
|
||||
't1[0-9A-Za-z]{33}'
|
||||
'|t3[0-9A-Za-z]{33}'
|
||||
'|zs[a-z0-9]{76}'
|
||||
'|zxviews[a-z0-9]{278}'
|
||||
'|u1[a-z0-9]{1,300}'
|
||||
')';
|
||||
pattern = "(?:$zcashAddressPattern|zxviews[a-z0-9]{278})";
|
||||
case CryptoCurrency.dcr:
|
||||
pattern = '(D|T|S)[ksecS]([0-9a-zA-Z])+';
|
||||
case CryptoCurrency.rvn:
|
||||
@@ -236,6 +259,7 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.avaxc:
|
||||
case CryptoCurrency.baseEth:
|
||||
case CryptoCurrency.arbEth:
|
||||
case CryptoCurrency.robEth:
|
||||
return [42];
|
||||
case CryptoCurrency.bch:
|
||||
return [42, 54, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35];
|
||||
@@ -314,6 +338,8 @@ class AddressValidator extends TextValidator {
|
||||
static String get silentPaymentAddressPatternMainnet => 'sp1[0-9a-zA-Z]{113}';
|
||||
static String get silentPaymentAddressPatternTestnet => '(tsp|sprt)1[0-9a-zA-Z]{113}';
|
||||
static String get mWebAddressPattern => MwebAddress.regex.pattern;
|
||||
static String get zcashAddressPattern =>
|
||||
"t1[0-9A-Za-z]{33}|t3[0-9A-Za-z]{33}|zs[a-z0-9]{76}|u1[a-z0-9]{1,300}";
|
||||
|
||||
static const String bolt11InvoiceMatcher = r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt)[a-z0-9]+$';
|
||||
static const String bolt12OfferMatcher = r'^(lightning:)?(lno1)[a-z0-9]+$';
|
||||
@@ -345,6 +371,7 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.maticpoly:
|
||||
case CryptoCurrency.baseEth:
|
||||
case CryptoCurrency.arbEth:
|
||||
case CryptoCurrency.robEth:
|
||||
case CryptoCurrency.arb:
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.nano:
|
||||
@@ -360,13 +387,7 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.zano:
|
||||
pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
|
||||
case CryptoCurrency.zec:
|
||||
pattern = '(?:'
|
||||
't1[0-9A-Za-z]{33}'
|
||||
'|t3[0-9A-Za-z]{33}'
|
||||
'|zs[a-z0-9]{76}'
|
||||
'|zxviews[a-z0-9]{278}'
|
||||
'|u1[a-z0-9]{1,300}'
|
||||
')';
|
||||
pattern = "(?:$zcashAddressPattern|zxviews[a-z0-9]{278})";
|
||||
default:
|
||||
if (type.tag == CryptoCurrency.eth.title) {
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:hash_wallet/entities/bitcoin_amount_display_mode.dart';
|
||||
import 'package:hash_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
|
||||
class AmountParsingProxy {
|
||||
@@ -24,7 +26,7 @@ class AmountParsingProxy {
|
||||
if (useSatoshi(cryptoCurrency) && amount.isNotEmpty) {
|
||||
return cryptoCurrency
|
||||
.parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals))
|
||||
.toString();
|
||||
.toStringWithPrecision(useBaseUnit: true);
|
||||
}
|
||||
|
||||
return amount.withMaxDecimals(cryptoCurrency.decimals);
|
||||
@@ -35,55 +37,39 @@ class AmountParsingProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/// [getCryptoStringRepresentation] turns the input [amount] into the preferred representation of [cryptoCurrency]
|
||||
String getDisplayCryptoString(int amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return "$amount";
|
||||
}
|
||||
String getDisplayCryptoString(int amount, CryptoCurrency cryptoCurrency) =>
|
||||
asDisplayString(Money.fromInt(amount, cryptoCurrency));
|
||||
|
||||
return cryptoCurrency.formatAmount(BigInt.from(amount));
|
||||
}
|
||||
/// [getDisplayCryptoString] turns the input [amount] into the preferred representation of [cryptoCurrency]
|
||||
///
|
||||
/// if [displayMode] is [BitcoinAmountDisplayMode.satoshi] it returns 1 BTC as 100000000
|
||||
/// if [displayMode] is [BitcoinAmountDisplayMode.bitcoin] it returns 1 BTC as 1
|
||||
/// hardcoding fractionalDigits to 8 as it's a display string, so no need to show more than that
|
||||
String asDisplayString(Money amount) =>
|
||||
amount.toStringWithPrecision(useBaseUnit: useSatoshi(amount.currency), fractionalDigits: 8);
|
||||
|
||||
String getDisplayCryptoStringFromBigInt(BigInt amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return "$amount";
|
||||
}
|
||||
/// [asDisplayStringWithSymbol] turns the input [amount] into the preferred representation of
|
||||
/// [cryptoCurrency] following the symbol of the unit
|
||||
///
|
||||
/// if [displayMode] is [BitcoinAmountDisplayMode.satoshi] it returns 1 BTC as 100000000 sats
|
||||
/// if [displayMode] is [BitcoinAmountDisplayMode.bitcoin] it returns 1 BTC as 1 BTC
|
||||
/// hardcoding fractionalDigits to 8 as it's a display string, so no need to show more than that
|
||||
String asDisplayStringWithSymbol(Money amount) =>
|
||||
amount.toStringWithSymbol(useBaseUnit: useSatoshi(amount.currency), fractionalDigits: 8);
|
||||
|
||||
return cryptoCurrency.formatAmount(amount);
|
||||
}
|
||||
/// [parseCryptoString] turns the the display representation [string] into `Money` with [cryptoCurrency]
|
||||
Money parseCryptoString(String amount, CryptoCurrency cryptoCurrency) =>
|
||||
Money.parse(amount, cryptoCurrency, isBaseUnit: useSatoshi(cryptoCurrency));
|
||||
|
||||
/// [getCryptoStringFromDouble] turns the input [amount] into the preferred representation of [cryptoCurrency] and
|
||||
String getDisplayCryptoStringFromDouble(double amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return "$amount";
|
||||
}
|
||||
|
||||
return cryptoCurrency.formatAmount(BigInt.from(amount));
|
||||
}
|
||||
|
||||
/// [parseCryptoString] turns the input [string] into a `BigInt` presentation of the [cryptoCurrency]
|
||||
BigInt parseCryptoString(String amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return BigInt.parse(amount);
|
||||
}
|
||||
|
||||
return cryptoCurrency.parseAmount(amount);
|
||||
}
|
||||
|
||||
/// [tryParseCryptoString] tries to turn the display representation [string] into a `BigInt` presentation of the [cryptoCurrency]
|
||||
BigInt? tryParseCryptoString(String amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency)) {
|
||||
return BigInt.tryParse(amount);
|
||||
}
|
||||
|
||||
return cryptoCurrency.tryParseAmount(amount);
|
||||
}
|
||||
/// [tryParseCryptoString] tries to turn the display representation [string] into `Money` with [cryptoCurrency]
|
||||
Money? tryParseCryptoString(String amount, CryptoCurrency cryptoCurrency) =>
|
||||
Money.tryParse(amount, cryptoCurrency, isBaseUnit: useSatoshi(cryptoCurrency));
|
||||
|
||||
/// [getCryptoSymbol] returns the correct Symbol related to the presentation
|
||||
String getCryptoSymbol(CryptoCurrency cryptoCurrency) =>
|
||||
useSatoshi(cryptoCurrency) ? "sats" : cryptoCurrency.title.safeSubString(0, 8);
|
||||
|
||||
bool useSatoshi(CryptoCurrency cryptoCurrency) =>
|
||||
bool useSatoshi(Currency cryptoCurrency) =>
|
||||
([CryptoCurrency.btc, CryptoCurrency.btcln].contains(cryptoCurrency) &&
|
||||
displayMode == BitcoinAmountDisplayMode.satoshi) ||
|
||||
(CryptoCurrency.btcln == cryptoCurrency &&
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import "package:hash_wallet/core/universal_address_detector.dart";
|
||||
import "package:hash_wallet/utils/payment_request.dart";
|
||||
import "package:cw_core/amount/money.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
|
||||
sealed class ChainBinding {
|
||||
const ChainBinding();
|
||||
}
|
||||
|
||||
class ExplicitEvmChain extends ChainBinding {
|
||||
const ExplicitEvmChain(this.chainId);
|
||||
|
||||
final int chainId;
|
||||
}
|
||||
|
||||
class ChainlessEvm extends ChainBinding {
|
||||
const ChainlessEvm();
|
||||
}
|
||||
|
||||
class NoEvmBinding extends ChainBinding {
|
||||
const NoEvmBinding();
|
||||
}
|
||||
|
||||
class AnyPayRequest {
|
||||
AnyPayRequest({
|
||||
required this.rawInput,
|
||||
required this.paymentRequest,
|
||||
required this.detection,
|
||||
required this.chainBinding,
|
||||
});
|
||||
|
||||
final String rawInput;
|
||||
final PaymentRequest paymentRequest;
|
||||
final AddressDetectionResult detection;
|
||||
final ChainBinding chainBinding;
|
||||
|
||||
String get address => paymentRequest.address;
|
||||
|
||||
String get amount => paymentRequest.amount;
|
||||
|
||||
String? get contractAddress => paymentRequest.contractAddress;
|
||||
|
||||
bool get hasContract => contractAddress != null && contractAddress!.isNotEmpty;
|
||||
|
||||
bool get isLightning => detection.detectedCurrency == CryptoCurrency.btcln;
|
||||
}
|
||||
|
||||
class WalletSnapshot {
|
||||
WalletSnapshot({
|
||||
required this.type,
|
||||
required this.currentWalletIsEvm,
|
||||
required this.currentChainId,
|
||||
required this.wallets,
|
||||
required this.supportedEvmChains,
|
||||
required this.hasEvmProxy,
|
||||
required this.hasSolanaProxy,
|
||||
required this.hasTronProxy,
|
||||
});
|
||||
|
||||
final WalletType type;
|
||||
final bool currentWalletIsEvm;
|
||||
final int? currentChainId;
|
||||
final List<WalletInfo> wallets;
|
||||
final Map<int, WalletType> supportedEvmChains;
|
||||
final bool hasEvmProxy;
|
||||
final bool hasSolanaProxy;
|
||||
final bool hasTronProxy;
|
||||
|
||||
List<WalletInfo> walletsOfType(WalletType walletType) =>
|
||||
wallets.where((wallet) => wallet.type == walletType).toList();
|
||||
}
|
||||
|
||||
sealed class AnyPayResolution {
|
||||
const AnyPayResolution();
|
||||
}
|
||||
|
||||
class NoTokenRequested extends AnyPayResolution {
|
||||
const NoTokenRequested();
|
||||
}
|
||||
|
||||
class TokenResolved extends AnyPayResolution {
|
||||
const TokenResolved({required this.token, this.chainId, this.amountOverride});
|
||||
|
||||
final CryptoCurrency token;
|
||||
final int? chainId;
|
||||
final String? amountOverride;
|
||||
}
|
||||
|
||||
class TokenUnknown extends AnyPayResolution {
|
||||
const TokenUnknown(this.contract);
|
||||
|
||||
final String contract;
|
||||
}
|
||||
|
||||
sealed class AnyPayDecision {
|
||||
const AnyPayDecision();
|
||||
}
|
||||
|
||||
class AnyPayApplyToCurrentWallet extends AnyPayDecision {
|
||||
const AnyPayApplyToCurrentWallet({
|
||||
this.token,
|
||||
this.amountOverride,
|
||||
this.fallbackCurrency,
|
||||
});
|
||||
|
||||
final CryptoCurrency? token;
|
||||
final String? amountOverride;
|
||||
final CryptoCurrency? fallbackCurrency;
|
||||
}
|
||||
|
||||
class AnyPayEvmNetworkChoice extends AnyPayDecision {
|
||||
const AnyPayEvmNetworkChoice();
|
||||
}
|
||||
|
||||
class AnyPayCrossChainPayment extends AnyPayDecision {
|
||||
const AnyPayCrossChainPayment({
|
||||
required this.targetWalletType,
|
||||
required this.wallets,
|
||||
this.targetChainId,
|
||||
this.token,
|
||||
this.amountOverride,
|
||||
this.canSwap = true,
|
||||
});
|
||||
|
||||
final WalletType targetWalletType;
|
||||
final int? targetChainId;
|
||||
final List<WalletInfo> wallets;
|
||||
final CryptoCurrency? token;
|
||||
final String? amountOverride;
|
||||
final bool canSwap;
|
||||
|
||||
bool get hasCompatibleWallet => wallets.isNotEmpty;
|
||||
}
|
||||
|
||||
class AnyPayUnsupportedNetwork extends AnyPayDecision {
|
||||
const AnyPayUnsupportedNetwork(this.chainId);
|
||||
|
||||
final int chainId;
|
||||
}
|
||||
|
||||
class AnyPayUnsupportedToken extends AnyPayDecision {
|
||||
const AnyPayUnsupportedToken(this.contract);
|
||||
|
||||
final String contract;
|
||||
}
|
||||
|
||||
class AnyPayEmptyInput extends AnyPayDecision {
|
||||
const AnyPayEmptyInput();
|
||||
}
|
||||
|
||||
class AnyPayEvaluation {
|
||||
AnyPayEvaluation({required this.request, required this.decision});
|
||||
|
||||
final AnyPayRequest request;
|
||||
final AnyPayDecision decision;
|
||||
}
|
||||
|
||||
class AnyPaySwapIntent {
|
||||
AnyPaySwapIntent({
|
||||
required this.request,
|
||||
required this.receiveCurrency,
|
||||
required this.targetWalletType,
|
||||
this.receiveAmount,
|
||||
});
|
||||
|
||||
final AnyPayRequest request;
|
||||
final CryptoCurrency receiveCurrency;
|
||||
final WalletType targetWalletType;
|
||||
final Money? receiveAmount;
|
||||
|
||||
String get recipientAddress => request.address;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import "package:hash_wallet/core/anypay/anypay_models.dart";
|
||||
import "package:hash_wallet/core/universal_address_detector.dart";
|
||||
import "package:hash_wallet/utils/payment_request.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/payment_uris.dart";
|
||||
|
||||
class AnyPayParser {
|
||||
static AnyPayRequest fromRaw(String input) {
|
||||
final paymentRequest = PaymentRequest.fromString(input);
|
||||
|
||||
return AnyPayRequest(
|
||||
rawInput: input,
|
||||
paymentRequest: paymentRequest,
|
||||
detection: UniversalAddressDetector.detectAddress(input),
|
||||
chainBinding: _bindingFor(paymentRequest),
|
||||
);
|
||||
}
|
||||
|
||||
static AnyPayRequest fromPaymentRequest(PaymentRequest request) {
|
||||
final raw = _reconstructRawInput(request);
|
||||
|
||||
return AnyPayRequest(
|
||||
rawInput: raw,
|
||||
paymentRequest: request,
|
||||
detection: UniversalAddressDetector.detectAddress(raw),
|
||||
chainBinding: _bindingFor(request),
|
||||
);
|
||||
}
|
||||
|
||||
static String _reconstructRawInput(PaymentRequest request) {
|
||||
final scheme = request.scheme.toLowerCase();
|
||||
|
||||
if (scheme.isEmpty || scheme == "lightning") {
|
||||
return request.address;
|
||||
}
|
||||
|
||||
if (scheme == "ethereum") {
|
||||
return ERC681URI(
|
||||
address: request.address,
|
||||
amount: request.amount,
|
||||
contractAddress: request.contractAddress,
|
||||
chainId: request.chainId ?? 1,
|
||||
rawTokenAmount: request.rawTokenAmount,
|
||||
).toString();
|
||||
}
|
||||
|
||||
final amount = request.amount.isNotEmpty ? "?amount=${request.amount}" : "";
|
||||
return "${request.scheme}:${request.address}$amount";
|
||||
}
|
||||
|
||||
static ChainBinding _bindingFor(PaymentRequest request) {
|
||||
final scheme = request.scheme.toLowerCase();
|
||||
|
||||
if (scheme == "ethereum") {
|
||||
final chainId = request.chainId;
|
||||
return chainId != null ? ExplicitEvmChain(chainId) : const ChainlessEvm();
|
||||
}
|
||||
|
||||
if (scheme.isNotEmpty) {
|
||||
try {
|
||||
final chainId = getChainIdByCryptoCurrency(CryptoCurrency.fromString(scheme));
|
||||
if (chainId != null) {
|
||||
return ExplicitEvmChain(chainId);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return const NoEvmBinding();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import "package:hash_wallet/core/anypay/anypay_models.dart";
|
||||
import "package:hash_wallet/core/anypay/anypay_router.dart";
|
||||
import "package:hash_wallet/reactions/wallet_connect.dart";
|
||||
import "package:hash_wallet/utils/token_utilities.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
|
||||
class AnyPayResolver {
|
||||
AnyPayResolver({AnyPayTokenLookup? tokenLookup})
|
||||
: _tokenLookup = tokenLookup ?? const TokenUtilitiesLookup();
|
||||
|
||||
final AnyPayTokenLookup _tokenLookup;
|
||||
|
||||
Future<AnyPayResolution> resolve(AnyPayRequest request, WalletSnapshot snapshot) async {
|
||||
if (!request.hasContract) {
|
||||
return const NoTokenRequested();
|
||||
}
|
||||
|
||||
final contract = request.contractAddress!;
|
||||
final detectedType = request.detection.detectedWalletType;
|
||||
|
||||
if (detectedType != null && isEVMCompatibleChain(detectedType)) {
|
||||
return _resolveEvm(request, snapshot, contract);
|
||||
}
|
||||
|
||||
final token = await _tokenLookup.findTokenByAddress(
|
||||
walletType: detectedType ?? snapshot.type,
|
||||
address: contract,
|
||||
);
|
||||
|
||||
if (token == null) {
|
||||
return TokenUnknown(contract);
|
||||
}
|
||||
|
||||
return TokenResolved(
|
||||
token: token,
|
||||
amountOverride: request.paymentRequest.resolveTokenAmount(token),
|
||||
);
|
||||
}
|
||||
|
||||
Future<AnyPayResolution> _resolveEvm(
|
||||
AnyPayRequest request,
|
||||
WalletSnapshot snapshot,
|
||||
String contract,
|
||||
) async {
|
||||
final requestedChainId = AnyPayRouter.requestedEvmChainId(request, snapshot) ?? 1;
|
||||
|
||||
final lookupType = snapshot.supportedEvmChains[requestedChainId];
|
||||
if (lookupType != null) {
|
||||
final token =
|
||||
await _tokenLookup.findTokenByAddress(walletType: lookupType, address: contract);
|
||||
if (token != null) {
|
||||
return TokenResolved(
|
||||
token: token,
|
||||
chainId: requestedChainId,
|
||||
amountOverride: request.paymentRequest.resolveTokenAmount(token),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The QRs from our old app versions omit the chainId on mainnet, so a contract the target
|
||||
// network does not know may still belong to another EVM network.
|
||||
if (request.chainBinding is ChainlessEvm) {
|
||||
final reboundChainId = await _tokenLookup.findEvmChainIdForContract(
|
||||
contract,
|
||||
excludingChainId: requestedChainId,
|
||||
);
|
||||
final reboundType =
|
||||
reboundChainId != null ? snapshot.supportedEvmChains[reboundChainId] : null;
|
||||
if (reboundType != null) {
|
||||
final token =
|
||||
await _tokenLookup.findTokenByAddress(walletType: reboundType, address: contract);
|
||||
if (token != null) {
|
||||
return TokenResolved(
|
||||
token: token,
|
||||
chainId: reboundChainId,
|
||||
amountOverride: request.paymentRequest.resolveTokenAmount(token),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TokenUnknown(contract);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AnyPayTokenLookup {
|
||||
Future<CryptoCurrency?> findTokenByAddress({
|
||||
required WalletType walletType,
|
||||
required String address,
|
||||
});
|
||||
|
||||
Future<int?> findEvmChainIdForContract(String contractAddress, {int? excludingChainId});
|
||||
}
|
||||
|
||||
class TokenUtilitiesLookup implements AnyPayTokenLookup {
|
||||
const TokenUtilitiesLookup();
|
||||
|
||||
@override
|
||||
Future<CryptoCurrency?> findTokenByAddress({
|
||||
required WalletType walletType,
|
||||
required String address,
|
||||
}) =>
|
||||
TokenUtilities.findTokenByAddress(walletType: walletType, address: address);
|
||||
|
||||
@override
|
||||
Future<int?> findEvmChainIdForContract(String contractAddress, {int? excludingChainId}) =>
|
||||
TokenUtilities.findEvmChainIdForContract(contractAddress, excludingChainId: excludingChainId);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import "package:hash_wallet/core/address_validator.dart";
|
||||
import "package:hash_wallet/core/anypay/anypay_models.dart";
|
||||
import "package:hash_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 (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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import "package:hash_wallet/core/anypay/anypay_models.dart";
|
||||
import "package:hash_wallet/core/anypay/anypay_parser.dart";
|
||||
import "package:hash_wallet/core/anypay/anypay_resolver.dart";
|
||||
import "package:hash_wallet/core/anypay/anypay_router.dart";
|
||||
import "package:hash_wallet/evm/evm.dart";
|
||||
import "package:hash_wallet/new-ui/services/wallet_switch_service.dart";
|
||||
import "package:hash_wallet/reactions/wallet_connect.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:hash_wallet/utils/payment_request.dart";
|
||||
import "package:cw_core/amount/money.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/utils/print_verbose.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
|
||||
class AnyPayService {
|
||||
AnyPayService({
|
||||
required this.appStore,
|
||||
required this.walletSwitchService,
|
||||
AnyPayResolver? resolver,
|
||||
}) : resolver = resolver ?? AnyPayResolver();
|
||||
|
||||
final AppStore appStore;
|
||||
final WalletSwitchService walletSwitchService;
|
||||
final AnyPayResolver resolver;
|
||||
|
||||
Future<AnyPayEvaluation> evaluateRawInput(String input) => _evaluate(AnyPayParser.fromRaw(input));
|
||||
|
||||
Future<AnyPayEvaluation> evaluatePaymentRequest(PaymentRequest request) =>
|
||||
_evaluate(AnyPayParser.fromPaymentRequest(request));
|
||||
|
||||
Future<AnyPayEvaluation> evaluateForEvmChain(AnyPayRequest request, int chainId) => _evaluate(
|
||||
AnyPayRequest(
|
||||
rawInput: request.rawInput,
|
||||
paymentRequest: request.paymentRequest,
|
||||
detection: request.detection,
|
||||
chainBinding: ExplicitEvmChain(chainId),
|
||||
),
|
||||
);
|
||||
|
||||
Future<AnyPayEvaluation> _evaluate(AnyPayRequest request) async {
|
||||
try {
|
||||
final snapshot = await _buildWalletSnapshot();
|
||||
final resolution = await resolver.resolve(request, snapshot);
|
||||
|
||||
return AnyPayEvaluation(
|
||||
request: request,
|
||||
decision: AnyPayRouter.route(request, snapshot, resolution),
|
||||
);
|
||||
} catch (e) {
|
||||
printV("anypay evaluation failed: $e");
|
||||
return AnyPayEvaluation(
|
||||
request: request,
|
||||
decision: const AnyPayApplyToCurrentWallet(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<WalletSnapshot> _buildWalletSnapshot() async {
|
||||
final wallet = appStore.wallet!;
|
||||
final currentWalletIsEvm = isEVMCompatibleChain(wallet.type);
|
||||
|
||||
final supportedEvmChains = <int, WalletType>{};
|
||||
if (evm != null) {
|
||||
for (final chain in evm!.getAllChains()) {
|
||||
final walletType = evm!.getWalletTypeByChainId(chain.chainId);
|
||||
if (walletType != null) {
|
||||
supportedEvmChains[chain.chainId] = walletType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WalletSnapshot(
|
||||
type: wallet.type,
|
||||
currentWalletIsEvm: currentWalletIsEvm,
|
||||
currentChainId: currentWalletIsEvm && evm != null
|
||||
? evm!.getSelectedChainId(wallet) ?? evm!.getChainIdByWalletType(wallet.type)
|
||||
: null,
|
||||
wallets: await WalletInfo.getAll(),
|
||||
supportedEvmChains: supportedEvmChains,
|
||||
hasEvmProxy: evm != null,
|
||||
hasSolanaProxy: false,
|
||||
hasTronProxy: false,
|
||||
);
|
||||
}
|
||||
|
||||
AnyPaySwapIntent buildSwapIntent(
|
||||
AnyPayRequest request, {
|
||||
required WalletType targetWalletType,
|
||||
int? targetChainId,
|
||||
CryptoCurrency? token,
|
||||
}) {
|
||||
final receiveCurrency = token ??
|
||||
(targetChainId != null
|
||||
? walletTypeToCryptoCurrency(targetWalletType, chainId: targetChainId)
|
||||
: request.detection.detectedCurrency ?? walletTypeToCryptoCurrency(targetWalletType));
|
||||
|
||||
final amountValue = token != null
|
||||
? request.paymentRequest.resolveTokenAmount(token)
|
||||
: (request.amount.isNotEmpty ? request.amount : null);
|
||||
|
||||
return AnyPaySwapIntent(
|
||||
request: request,
|
||||
receiveCurrency: receiveCurrency,
|
||||
targetWalletType: targetWalletType,
|
||||
receiveAmount: amountValue != null ? Money.tryParse(amountValue, receiveCurrency) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> switchWalletForPayment(WalletInfo walletInfo, {int? chainId}) async {
|
||||
try {
|
||||
await walletSwitchService.switchToWallet(walletInfo);
|
||||
} catch (e) {
|
||||
printV("wallet switch failed: $e");
|
||||
return false;
|
||||
}
|
||||
|
||||
final wallet = appStore.wallet!;
|
||||
if (chainId != null && evm != null && isEVMCompatibleChain(wallet.type)) {
|
||||
try {
|
||||
final node = appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId);
|
||||
await evm!.selectChain(wallet, chainId, node: node);
|
||||
} catch (e, s) {
|
||||
printV("switchWalletForPayment chain select failed: $e\n$s");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await wallet.updateBalance();
|
||||
} catch (e) {
|
||||
printV("balance refresh after wallet switch failed: $e");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ class AuthService with Store {
|
||||
|
||||
static const List<String> _alwaysAuthenticateRoutes = [
|
||||
Routes.showKeys,
|
||||
Routes.seed,
|
||||
Routes.backup,
|
||||
Routes.setupPin,
|
||||
Routes.setup_2faPage,
|
||||
|
||||
@@ -208,10 +208,14 @@ class BackgroundSync {
|
||||
}
|
||||
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name),
|
||||
tx.date.add(Duration(minutes: 1)).toIso8601String());
|
||||
final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent";
|
||||
final action = tx.additionalInfo['isAutoShield'] == true
|
||||
? "Autoshield"
|
||||
: tx.direction == TransactionDirection.incoming
|
||||
? "Received"
|
||||
: "Sent";
|
||||
if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) {
|
||||
await showNotification(
|
||||
"$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}");
|
||||
"$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amount.toString()}");
|
||||
}
|
||||
printV(
|
||||
"${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}");
|
||||
|
||||
@@ -18,6 +18,9 @@ import 'package:hash_wallet/core/key_service.dart';
|
||||
import 'package:hash_wallet/entities/encrypt.dart';
|
||||
import 'package:hash_wallet/entities/preferences_key.dart';
|
||||
import 'package:hash_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cw_core/erc20_token_legacy.dart' show performErc20TokenHiveMigration;
|
||||
import 'package:cw_core/spl_token_legacy.dart' show performSplTokenHiveMigration;
|
||||
import 'package:cw_core/tron_token_legacy.dart' show performTronTokenHiveMigration;
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:hash_wallet/exchange/trade_legacy.dart';
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
@@ -113,6 +116,10 @@ class $BackupService {
|
||||
Future<void> verifyWallets() async {
|
||||
await performHiveMigration(); // for backups made before sqlite migration
|
||||
await performTradeHiveMigration(_secureStorage);
|
||||
await performErc20TokenHiveMigration();
|
||||
await performSplTokenHiveMigration();
|
||||
await performTronTokenHiveMigration();
|
||||
|
||||
correctWallets = (await WalletInfo.getAll())
|
||||
.where((info) => availableWalletTypes.contains(info.type))
|
||||
.toList();
|
||||
|
||||
@@ -147,6 +147,21 @@ class BackupMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
class IncompatibleBackupAppException implements Exception {
|
||||
IncompatibleBackupAppException({
|
||||
required this.sourceAppName,
|
||||
required this.currentAppName,
|
||||
});
|
||||
|
||||
final String sourceAppName;
|
||||
final String currentAppName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'This backup was created in $sourceAppName and cannot be restored in $currentAppName.';
|
||||
}
|
||||
}
|
||||
|
||||
class BackupServiceV3 extends $BackupService {
|
||||
BackupServiceV3(super.secureStorage, super.transactionDescriptionBox, super.keyService,
|
||||
super.sharedPreferences);
|
||||
@@ -201,7 +216,8 @@ class BackupServiceV3 extends $BackupService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> importBackupFile(File file, String password, {String nonce = secrets.backupSalt}) {
|
||||
Future<void> importBackupFile(File file, String password,
|
||||
{String nonce = secrets.backupSalt, bool checkBackupApp = true}) {
|
||||
final version = getVersionFile(file);
|
||||
switch (version) {
|
||||
case BackupVersion.unknown:
|
||||
@@ -214,12 +230,12 @@ class BackupServiceV3 extends $BackupService {
|
||||
case BackupVersion.v2:
|
||||
return super.importBackupV2(file.readAsBytesSync(), password);
|
||||
case BackupVersion.v3:
|
||||
return importBackupFileV3(file, password, nonce: nonce);
|
||||
return importBackupFileV3(file, password, nonce: nonce, checkBackupApp: checkBackupApp);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> importBackupFileV3(File file, String password,
|
||||
{String nonce = secrets.backupSalt}) async {
|
||||
{String nonce = secrets.backupSalt, bool checkBackupApp = true}) async {
|
||||
// Overall design of v3 backup is the following:
|
||||
// 1. backup.zip - plaintext zip file that user can open with any archive manager
|
||||
// 2. backup.zip/README.txt - text file to let user know what is inside of this file
|
||||
@@ -228,6 +244,11 @@ class BackupServiceV3 extends $BackupService {
|
||||
|
||||
final inputStream = InputFileStream(file.path);
|
||||
final archive = ZipDecoder().decodeStream(inputStream);
|
||||
|
||||
if (checkBackupApp) {
|
||||
await _throwIfBackupWasCreatedInAnotherApp(archive);
|
||||
}
|
||||
|
||||
final metadataFile = archive.findFile('metadata.json');
|
||||
if (metadataFile == null) {
|
||||
throw Exception('Invalid v3 backup: missing metadata.json');
|
||||
@@ -497,6 +518,31 @@ This backup was created on ${DateTime.now().toIso8601String()}
|
||||
return file;
|
||||
}
|
||||
|
||||
Future<void> _throwIfBackupWasCreatedInAnotherApp(Archive archive) async {
|
||||
final readmeFile = archive.findFile('README.txt');
|
||||
if (readmeFile == null) return;
|
||||
|
||||
final readmeBytes = readmeFile.rawContent?.readBytes();
|
||||
if (readmeBytes == null) return;
|
||||
|
||||
final readmeString = utf8.decode(readmeBytes, allowMalformed: true);
|
||||
final sourceAppName = _extractBackupAppName(readmeString);
|
||||
if (sourceAppName == null) return;
|
||||
|
||||
final currentAppName = (await PackageInfo.fromPlatform()).appName;
|
||||
if (sourceAppName == currentAppName) return;
|
||||
|
||||
throw IncompatibleBackupAppException(
|
||||
sourceAppName: sourceAppName,
|
||||
currentAppName: currentAppName,
|
||||
);
|
||||
}
|
||||
|
||||
String? _extractBackupAppName(String readme) {
|
||||
final match = RegExp(r'^This is a (.+) backup\.', multiLine: true).firstMatch(readme);
|
||||
return match?.group(1)?.trim();
|
||||
}
|
||||
|
||||
static const chunkSize = 24 * 1024 * 1024; // 24MiB
|
||||
|
||||
File setVersionFile(File file, BackupVersion version) {
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hash_wallet/entities/transaction_description.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:hash_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:hash_wallet/utils/share_util.dart';
|
||||
import 'package:hash_wallet/utils/show_bar.dart';
|
||||
import 'package:hash_wallet/utils/show_pop_up.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/action_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/date_section_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/trade_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class CsvExportService {
|
||||
CsvExportService({required this.transactionDescriptionBox});
|
||||
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
|
||||
static const _columns = [
|
||||
'record_type',
|
||||
'date_time',
|
||||
'type',
|
||||
'amount',
|
||||
'currency',
|
||||
'fee',
|
||||
'tx_id',
|
||||
'address',
|
||||
'status',
|
||||
'note',
|
||||
'from_amount',
|
||||
'from_currency',
|
||||
'to_amount',
|
||||
'to_currency',
|
||||
'trade_id',
|
||||
'provider',
|
||||
'confirmations',
|
||||
];
|
||||
|
||||
static const _utf8Bom = '';
|
||||
|
||||
static const _formulaTriggers = {'=', '+', '-', '@', '\t', '\r'};
|
||||
|
||||
static final _plainNumber = RegExp(r'^-?\d+(\.\d+)?$');
|
||||
|
||||
String buildCsvContent(List<ActionListItem> items) {
|
||||
final buf = StringBuffer();
|
||||
buf.write(_utf8Bom);
|
||||
buf.writeln(_columns.join(','));
|
||||
|
||||
final primaryAddress = _primaryAddressOf(items);
|
||||
|
||||
for (final item in items) {
|
||||
if (item is DateSectionItem) continue;
|
||||
|
||||
final row = _buildRow(item, primaryAddress);
|
||||
if (row != null) buf.writeln(row);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
String _primaryAddressOf(List<ActionListItem> items) {
|
||||
for (final item in items) {
|
||||
if (item is TransactionListItem) {
|
||||
return item.balanceViewModel.wallet.walletAddresses.primaryAddress;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
String _noteFor(TransactionListItem item, String primaryAddress) {
|
||||
final txHash = item.transaction.txHash;
|
||||
final descriptionKey = '${txHash}_$primaryAddress';
|
||||
final description = transactionDescriptionBox.values
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == txHash);
|
||||
return description?.note ?? '';
|
||||
}
|
||||
|
||||
String? _buildRow(ActionListItem item, String primaryAddress) {
|
||||
if (item is TransactionListItem) return _transactionRow(item, primaryAddress);
|
||||
if (item is TradeListItem) return _tradeRow(item);
|
||||
if (item is OrderListItem) return _orderRow(item);
|
||||
if (item is AnonpayTransactionListItem) return _anonpayRow(item);
|
||||
if (item is PayjoinTransactionListItem) return _payjoinRow(item);
|
||||
return null;
|
||||
}
|
||||
|
||||
String _transactionRow(TransactionListItem item, String primaryAddress) {
|
||||
final tx = item.transaction;
|
||||
final type = tx.direction == TransactionDirection.incoming ? 'incoming' : 'outgoing';
|
||||
final status = tx.isPending ? 'pending' : 'confirmed';
|
||||
|
||||
// Prefer tx.to/tx.from; fall back to address lists for chains that don't populate them.
|
||||
final address = tx.direction == TransactionDirection.incoming
|
||||
? (tx.from?.isNotEmpty == true ? tx.from! : (tx.inputAddresses?.firstOrNull ?? ''))
|
||||
: (tx.to?.isNotEmpty == true ? tx.to! : (tx.outputAddresses?.firstOrNull ?? ''));
|
||||
|
||||
final fee = tx.fee != null && !tx.fee!.isZero ? tx.fee.toString() : '';
|
||||
|
||||
return _row([
|
||||
'transaction',
|
||||
_isoDate(tx.date),
|
||||
type,
|
||||
tx.amount.toString(),
|
||||
tx.amount.currency.symbol,
|
||||
fee,
|
||||
tx.id,
|
||||
address,
|
||||
status,
|
||||
_noteFor(item, primaryAddress),
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
tx.confirmations.toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
String _tradeRow(TradeListItem item) {
|
||||
final trade = item.trade;
|
||||
return _row([
|
||||
'trade',
|
||||
_isoDate(trade.createdAt ?? DateTime.fromMillisecondsSinceEpoch(0, isUtc: true)),
|
||||
'swap',
|
||||
'',
|
||||
'',
|
||||
trade.fee?.toString() ?? '',
|
||||
trade.outputTransaction ?? '',
|
||||
trade.payoutAddress ?? '',
|
||||
trade.state.title,
|
||||
trade.memo ?? '',
|
||||
trade.amount,
|
||||
trade.from?.name ?? '',
|
||||
trade.receiveAmount ?? '',
|
||||
trade.to?.name ?? '',
|
||||
trade.id,
|
||||
trade.provider.title,
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
String _orderRow(OrderListItem item) {
|
||||
final order = item.order;
|
||||
return _row([
|
||||
'order',
|
||||
_isoDate(order.createdAt),
|
||||
order.source.title,
|
||||
order.amountFormatted(),
|
||||
order.from ?? '',
|
||||
'',
|
||||
order.transferId,
|
||||
'',
|
||||
order.state.title,
|
||||
'',
|
||||
order.amountFormatted(),
|
||||
order.from ?? '',
|
||||
order.receiveAmount ?? '',
|
||||
order.to ?? '',
|
||||
order.id,
|
||||
order.providerTitle,
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
String _anonpayRow(AnonpayTransactionListItem item) {
|
||||
final tx = item.transaction;
|
||||
final amount = tx.fiatAmount?.toString() ?? tx.amountTo?.toString() ?? '';
|
||||
final currency = tx.fiatEquiv ?? tx.coinTo;
|
||||
|
||||
return _row([
|
||||
'anonpay',
|
||||
_isoDate(tx.createdAt),
|
||||
'anonymous_payment',
|
||||
amount,
|
||||
currency,
|
||||
'',
|
||||
tx.invoiceId,
|
||||
tx.address,
|
||||
tx.status,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
tx.provider,
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
String _payjoinRow(PayjoinTransactionListItem item) {
|
||||
final session = item.session;
|
||||
final type = session.isSenderSession ? 'send' : 'receive';
|
||||
final amount = session.rawAmount ?? '0';
|
||||
|
||||
return _row([
|
||||
'payjoin',
|
||||
_isoDate(session.inProgressSince ?? DateTime.fromMillisecondsSinceEpoch(0, isUtc: true)),
|
||||
type,
|
||||
amount,
|
||||
'BTC',
|
||||
'',
|
||||
session.txId ?? '',
|
||||
'',
|
||||
item.status,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
String _row(List<String> fields) => fields.map(escapeField).join(',');
|
||||
|
||||
String escapeField(String field) {
|
||||
final value = _neutralizeFormula(field);
|
||||
if (value.contains(',') ||
|
||||
value.contains('"') ||
|
||||
value.contains('\n') ||
|
||||
value.contains('\r')) {
|
||||
return '"${value.replaceAll('"', '""')}"';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
String _neutralizeFormula(String field) {
|
||||
if (field.isEmpty || !_formulaTriggers.contains(field[0])) return field;
|
||||
if (_plainNumber.hasMatch(field)) return field;
|
||||
return "'$field";
|
||||
}
|
||||
|
||||
String _isoDate(DateTime dt) => dt.toUtc().toIso8601String();
|
||||
|
||||
Future<void> exportToCsv(List<ActionListItem> items, BuildContext context) async {
|
||||
final dataItems =
|
||||
items.whereType<ActionListItem>().where((e) => e is! DateSectionItem).toList();
|
||||
|
||||
if (dataItems.isEmpty) {
|
||||
await showBar<void>(context, S.current.csv_nothing_to_export);
|
||||
return;
|
||||
}
|
||||
|
||||
final now = DateTime.now();
|
||||
final fileName = 'cake_wallet_export_${DateFormat('yyyyMMdd_HHmmss').format(now)}.csv';
|
||||
|
||||
late File csvFile;
|
||||
|
||||
final exportFuture = Future(() async {
|
||||
final content = buildCsvContent(items);
|
||||
csvFile = await _writeTempFile(fileName, content);
|
||||
});
|
||||
|
||||
showPersistentActionOverlay(context, exportFuture, text: S.current.generating_csv);
|
||||
await exportFuture;
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
_showAndroidExportDialog(context, csvFile, fileName);
|
||||
} else if (Platform.isIOS) {
|
||||
await _shareFile(csvFile, fileName, context);
|
||||
} else {
|
||||
await _saveFileDesktop(csvFile, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
void _showAndroidExportDialog(BuildContext context, File csvFile, String fileName) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.current.export_csv,
|
||||
alertContent: S.current.select_destination,
|
||||
rightButtonText: S.current.save_to_downloads,
|
||||
leftButtonText: S.current.share,
|
||||
actionRightButton: () async {
|
||||
await _saveToDownloads(fileName, csvFile);
|
||||
Navigator.of(dialogContext).pop();
|
||||
await showBar<void>(context, S.current.file_saved);
|
||||
await csvFile.delete();
|
||||
},
|
||||
actionLeftButton: () async {
|
||||
Navigator.of(dialogContext).pop();
|
||||
await _shareFile(csvFile, fileName, context);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _shareFile(File file, String fileName, BuildContext context) async {
|
||||
await ShareUtil.shareFile(filePath: file.path, fileName: fileName, context: context);
|
||||
if (await file.exists()) await file.delete();
|
||||
}
|
||||
|
||||
Future<void> _saveFileDesktop(File csvFile, String fileName) async {
|
||||
final outputPath = await FilePicker.platform.saveFile(
|
||||
dialogTitle: 'Save CSV export',
|
||||
fileName: fileName,
|
||||
lockParentWindow: true,
|
||||
);
|
||||
if (outputPath == null) return;
|
||||
await csvFile.copy(outputPath);
|
||||
await csvFile.delete();
|
||||
}
|
||||
|
||||
Future<File> _writeTempFile(String fileName, String content) async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final file = File('${dir.path}/$fileName');
|
||||
if (file.existsSync()) file.deleteSync();
|
||||
await file.writeAsString(content, flush: true);
|
||||
return file;
|
||||
}
|
||||
|
||||
Future<void> _saveToDownloads(String fileName, File file) async {
|
||||
if (!Platform.isAndroid) return;
|
||||
const downloadsPath = '/storage/emulated/0/Download';
|
||||
final dest = File('$downloadsPath/$fileName');
|
||||
if (dest.existsSync()) dest.deleteSync();
|
||||
await file.copy(dest.path);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import "dart:convert";
|
||||
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import "package:cw_core/utils/proxy_wrapper.dart";
|
||||
|
||||
Future<String?> getBolt11FromLightingAddress(String lightningAddress, {int amount = 0}) async {
|
||||
try {
|
||||
@@ -31,28 +31,34 @@ Uri getURlOfLightningAddress(String lightningAddress) {
|
||||
}
|
||||
|
||||
class _LNURLPResponseDTO {
|
||||
const _LNURLPResponseDTO(
|
||||
this.callback,
|
||||
this.maxSendable,
|
||||
this.minSendable,
|
||||
this.tag,
|
||||
this.metadata,
|
||||
);
|
||||
|
||||
factory _LNURLPResponseDTO.fromJson(Map<String, dynamic> map) => _LNURLPResponseDTO(
|
||||
map["callback"] as String,
|
||||
map["maxSendable"] as int,
|
||||
map["minSendable"] as int,
|
||||
map["tag"] as String,
|
||||
map["metadata"] as String,
|
||||
);
|
||||
|
||||
final String callback;
|
||||
final int maxSendable;
|
||||
final int minSendable;
|
||||
final String tag;
|
||||
final String metadata;
|
||||
|
||||
const _LNURLPResponseDTO(
|
||||
this.callback, this.maxSendable, this.minSendable, this.tag, this.metadata);
|
||||
|
||||
static _LNURLPResponseDTO fromJson(Map<String, dynamic> map) => _LNURLPResponseDTO(
|
||||
map["callback"] as String,
|
||||
map["maxSendable"] as int,
|
||||
map["minSendable"] as int,
|
||||
map["tag"] as String,
|
||||
map["metadata"] as String);
|
||||
}
|
||||
|
||||
class _LNURLPCallbackResponseDTO {
|
||||
final String pr;
|
||||
|
||||
const _LNURLPCallbackResponseDTO(this.pr);
|
||||
|
||||
static _LNURLPCallbackResponseDTO fromJson(Map<String, dynamic> map) =>
|
||||
factory _LNURLPCallbackResponseDTO.fromJson(Map<String, dynamic> map) =>
|
||||
_LNURLPCallbackResponseDTO(map["pr"] as String);
|
||||
|
||||
final String pr;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ class NodeSwitchingService {
|
||||
NodeSwitchingService({
|
||||
required this.appStore,
|
||||
required this.settingsStore,
|
||||
required this.nodeSource,
|
||||
});
|
||||
|
||||
static const int _healthCheckIntervalSeconds = 30;
|
||||
@@ -38,7 +37,6 @@ class NodeSwitchingService {
|
||||
|
||||
final AppStore appStore;
|
||||
final SettingsStore settingsStore;
|
||||
final Box<Node> nodeSource;
|
||||
|
||||
final Map<WalletType, List<dynamic>> _usedNodeKeys = {};
|
||||
|
||||
@@ -118,13 +116,13 @@ class NodeSwitchingService {
|
||||
WalletType walletType,
|
||||
) async {
|
||||
for (final node in nodes) {
|
||||
if (!_usedNodeKeys[walletType]!.contains(node.key)) {
|
||||
if (!_usedNodeKeys[walletType]!.contains(node.id)) {
|
||||
final isActive = await node.requestNode();
|
||||
if (isActive) {
|
||||
return node;
|
||||
} else {
|
||||
printV('Node ${node.uriRaw} is not active. Marking as used.');
|
||||
_usedNodeKeys[walletType]!.add(node.key);
|
||||
_usedNodeKeys[walletType]!.add(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +155,7 @@ class NodeSwitchingService {
|
||||
final currentNode = settingsStore.getCurrentNode(nodeWalletType, chainId: chainId);
|
||||
|
||||
// Get all trusted nodes for this wallet type
|
||||
final trustedNodes = nodeSource.values
|
||||
final trustedNodes = (await Node.getAll())
|
||||
.where((node) => node.type == nodeWalletType && node.isEnabledForAutoSwitching)
|
||||
.toList();
|
||||
|
||||
@@ -171,8 +169,8 @@ class NodeSwitchingService {
|
||||
_usedNodeKeys.putIfAbsent(nodeWalletType, () => []);
|
||||
|
||||
// Add current node to used list if not already there
|
||||
if (!_usedNodeKeys[nodeWalletType]!.contains(currentNode.key)) {
|
||||
_usedNodeKeys[nodeWalletType]!.add(currentNode.key);
|
||||
if (!_usedNodeKeys[nodeWalletType]!.contains(currentNode.id)) {
|
||||
_usedNodeKeys[nodeWalletType]!.add(currentNode.id);
|
||||
}
|
||||
|
||||
// Try to find an active unused node
|
||||
@@ -199,8 +197,8 @@ class NodeSwitchingService {
|
||||
}
|
||||
|
||||
// Ensure the selected node is marked as used
|
||||
if (!_usedNodeKeys[nodeWalletType]!.contains(nextNode.key)) {
|
||||
_usedNodeKeys[nodeWalletType]!.add(nextNode.key);
|
||||
if (!_usedNodeKeys[nodeWalletType]!.contains(nextNode.id)) {
|
||||
_usedNodeKeys[nodeWalletType]!.add(nextNode.id);
|
||||
}
|
||||
|
||||
printV(
|
||||
|
||||
@@ -42,7 +42,7 @@ String syncStatusTitle(SyncStatus syncStatus, SyncStatusDisplayMode syncStatusDi
|
||||
}
|
||||
|
||||
if (syncStatus is SyncedSyncStatus) {
|
||||
return "";
|
||||
return S.current.sync_synced;
|
||||
}
|
||||
|
||||
if (syncStatus is FailedSyncStatus) {
|
||||
|
||||
@@ -238,6 +238,12 @@ class UniversalAddressDetector {
|
||||
currency: CryptoCurrency.doge,
|
||||
),
|
||||
|
||||
// Zcash (transparent, Sapling shielded, unified)
|
||||
_DetectionPattern(
|
||||
pattern: RegExp("^(?:${AddressValidator.zcashAddressPattern})\$"),
|
||||
currency: CryptoCurrency.zec,
|
||||
),
|
||||
|
||||
// Solana (Base58 format)
|
||||
_DetectionPattern(
|
||||
pattern: RegExp(r'^[1-9A-HJ-NP-Za-km-z]{43,44}$'),
|
||||
|
||||
+196
-304
@@ -6,11 +6,14 @@ import 'package:hash_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:hash_wallet/anypay/anypay_api.dart';
|
||||
import 'package:hash_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:hash_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_resolver_service.dart';
|
||||
import 'package:hash_wallet/core/anypay/anypay_service.dart';
|
||||
import 'package:hash_wallet/entities/bitcoin_amount_display_mode.dart';
|
||||
import 'package:hash_wallet/evm/evm.dart';
|
||||
import 'package:hash_wallet/buy/moonpay/moonpay_provider.dart';
|
||||
import 'package:hash_wallet/core/auth_service.dart';
|
||||
import 'package:hash_wallet/core/backup_service_v3.dart';
|
||||
import 'package:hash_wallet/core/csv_export_service.dart';
|
||||
import 'package:hash_wallet/core/key_service.dart';
|
||||
import 'package:hash_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:hash_wallet/core/new_wallet_type_arguments.dart';
|
||||
@@ -28,7 +31,6 @@ import 'package:hash_wallet/entities/contact.dart';
|
||||
import 'package:hash_wallet/entities/contact_record.dart';
|
||||
import 'package:hash_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:hash_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
|
||||
import 'package:hash_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:hash_wallet/entities/preferences_key.dart';
|
||||
import 'package:hash_wallet/entities/qr_view_data.dart';
|
||||
import 'package:hash_wallet/entities/template.dart';
|
||||
@@ -40,20 +42,29 @@ import 'package:hash_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:hash_wallet/exchange/trade.dart';
|
||||
import 'package:hash_wallet/monero/monero.dart';
|
||||
import 'package:hash_wallet/nano/nano.dart';
|
||||
import 'package:hash_wallet/new-ui/model/charts/price_store.dart';
|
||||
import 'package:hash_wallet/new-ui/new_dashboard.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/about_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/account_customizer.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/bridge/bridge_amount_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/bridge/bridge_network_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/bridge/bridge_network_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/charts_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/coin_control_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/addresses_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/home_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/send_page.dart';
|
||||
import "package:hash_wallet/new-ui/services/wallet_switch_service.dart";
|
||||
import 'package:hash_wallet/new-ui/pages/lightning_username_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/receive_page.dart';
|
||||
import "package:hash_wallet/new-ui/pages/seed/pre_seed_page.dart";
|
||||
import "package:hash_wallet/new-ui/pages/seed/show_keys_disclaimer_page.dart";
|
||||
import 'package:hash_wallet/new-ui/viewmodels/charts/charts_bloc.dart';
|
||||
import 'package:hash_wallet/new-ui/viewmodels/lightning_username/lightning_username_bloc.dart';
|
||||
import 'package:hash_wallet/new-ui/widgets/addresses_page/address_label_input.dart';
|
||||
import 'package:hash_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:hash_wallet/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart';
|
||||
import 'package:hash_wallet/new-ui/widgets/receive_page/receive_label_modal.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/swap_page.dart';
|
||||
@@ -64,22 +75,11 @@ import 'package:hash_wallet/src/screens/anonpay_details/anonpay_details_page.dar
|
||||
import 'package:hash_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:hash_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:hash_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
import 'package:hash_wallet/src/screens/buy/buy_sell_options_page.dart';
|
||||
import 'package:hash_wallet/src/screens/buy/buy_webview_page.dart';
|
||||
import 'package:hash_wallet/src/screens/buy/payment_method_options_page.dart';
|
||||
import 'package:hash_wallet/src/screens/buy/webview_page.dart';
|
||||
import 'package:hash_wallet/src/screens/contact/contact_list_page.dart';
|
||||
import 'package:hash_wallet/src/screens/contact/contact_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/dashboard_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/pages/address_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/pages/cake_features_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/pages/transactions_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dev/background_sync_logs_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dev/exchange_provider_logs_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
@@ -90,43 +90,24 @@ import 'package:hash_wallet/src/screens/dev/qr_tools_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dev/secure_preferences_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dev/shared_preferences_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dev/socket_health_logs_page.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
||||
import 'package:hash_wallet/src/screens/faq/faq_page.dart';
|
||||
import 'package:hash_wallet/src/screens/integrations/deuro/savings_page.dart';
|
||||
import 'package:hash_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
||||
import 'package:hash_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||
import 'package:hash_wallet/src/screens/nano/nano_change_rep_page.dart';
|
||||
import 'package:hash_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
|
||||
import 'package:hash_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
|
||||
import 'package:hash_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
|
||||
import 'package:hash_wallet/src/screens/nodes/node_create_or_edit_page.dart';
|
||||
import 'package:hash_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
||||
import 'package:hash_wallet/src/screens/order_details/order_details_page.dart';
|
||||
import 'package:hash_wallet/src/screens/payjoin_details/payjoin_details_page.dart';
|
||||
import 'package:hash_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:hash_wallet/src/screens/receive/address_list_page.dart';
|
||||
import 'package:hash_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
import 'package:hash_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
import 'package:hash_wallet/src/screens/receive/fullscreen_qr_page.dart';
|
||||
import 'package:hash_wallet/src/screens/receive/receive_page.dart';
|
||||
import 'package:hash_wallet/src/screens/rescan/rescan_page.dart';
|
||||
import 'package:hash_wallet/src/screens/restore/restore_from_backup_page.dart';
|
||||
import 'package:hash_wallet/src/screens/restore/restore_options_page.dart';
|
||||
import 'package:hash_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
|
||||
import 'package:hash_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:hash_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:hash_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
||||
import 'package:hash_wallet/src/screens/seed/wallet_seed_page.dart';
|
||||
import 'package:hash_wallet/src/screens/send/send_page.dart';
|
||||
import 'package:hash_wallet/src/screens/send/send_template_page.dart';
|
||||
import 'package:hash_wallet/src/screens/send/transaction_success_info_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/background_sync_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/connection_sync_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/display_settings_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/domain_lookups_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
@@ -138,7 +119,7 @@ import 'package:hash_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/security_backup_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/silent_payments_logs_page.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/silent_payments_settings.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/trocador_providers_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/wallet_accounts_page.dart';
|
||||
import 'package:hash_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||
import 'package:hash_wallet/src/screens/setup_2fa/setup_2fa.dart';
|
||||
import 'package:hash_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
||||
@@ -146,7 +127,6 @@ import 'package:hash_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
|
||||
import 'package:hash_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
|
||||
import 'package:hash_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
||||
import 'package:hash_wallet/src/screens/start_tor/start_tor_page.dart';
|
||||
import 'package:hash_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
|
||||
import 'package:hash_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:hash_wallet/src/screens/support_chat/support_chat_page.dart';
|
||||
import 'package:hash_wallet/src/screens/support_other_links/support_other_links_page.dart';
|
||||
@@ -154,10 +134,8 @@ import 'package:hash_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||
import 'package:hash_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:hash_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||
import 'package:hash_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:hash_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:hash_wallet/src/screens/ur/animated_ur_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/bridge/bridge_detail_page.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/bridge/bridge_history_page.dart';
|
||||
import 'package:hash_wallet/src/screens/wallet/wallet_edit_page.dart';
|
||||
import 'package:hash_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:hash_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart';
|
||||
@@ -181,19 +159,14 @@ import 'package:hash_wallet/store/dashboard/trade_filter_store.dart';
|
||||
import 'package:hash_wallet/store/bridge_transfers_store.dart';
|
||||
import 'package:hash_wallet/store/dashboard/trades_store.dart';
|
||||
import 'package:hash_wallet/store/dashboard/transaction_filter_store.dart';
|
||||
import 'package:hash_wallet/store/node_list_store.dart';
|
||||
import 'package:hash_wallet/store/seed_settings_store.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:hash_wallet/store/templates/exchange_template_store.dart';
|
||||
import 'package:hash_wallet/store/templates/send_template_store.dart';
|
||||
import 'package:hash_wallet/store/wallet_list_store.dart';
|
||||
import 'package:hash_wallet/store/yat/yat_store.dart';
|
||||
import 'package:hash_wallet/themes/core/theme_store.dart';
|
||||
import 'package:hash_wallet/utils/device_info.dart';
|
||||
import 'package:hash_wallet/utils/payment_request.dart';
|
||||
import 'package:hash_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/animated_ur_model.dart';
|
||||
import 'package:hash_wallet/view_model/anon_invoice_page_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/anonpay_details_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/backup_view_model.dart';
|
||||
@@ -201,13 +174,12 @@ import 'package:hash_wallet/view_model/bridge/bridge_details_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/bridge/bridge_history_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/contact_list/contact_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import "package:hash_wallet/view_model/dashboard/nft_send_view_model.dart";
|
||||
import 'package:hash_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/sign_view_model.dart';
|
||||
@@ -223,17 +195,19 @@ import 'package:hash_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/hardware_wallet/trezor_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/integrations/deuro_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/link_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
|
||||
import "package:hash_wallet/view_model/wallet_account_list/account_edit_or_create_view_model.dart";
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/account_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/bitcoin_account_list/bitcoin_account_edit_or_create_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/bitcoin_account_list/bitcoin_account_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/nano_account_list/nano_account_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/node_list/node_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/order_details_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/payjoin_details_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/payment/payment_view_model.dart';
|
||||
@@ -242,7 +216,6 @@ import 'package:hash_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:hash_wallet/view_model/restore_from_backup_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/send/fees_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/send/send_template_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||
import 'package:hash_wallet/view_model/settings/connection_sync_view_model.dart';
|
||||
@@ -262,6 +235,7 @@ import 'package:hash_wallet/view_model/unspent_coins/unspent_coins_details_view_
|
||||
import 'package:hash_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:hash_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/bridge/bridge_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_account_list/wallet_account_list_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
@@ -302,11 +276,12 @@ import 'new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'new-ui/widgets/addresses_page/address_info.dart';
|
||||
import 'src/screens/buy/buy_sell_page.dart';
|
||||
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
var _isSetupFinished = false;
|
||||
late Box<Node> _nodeSource;
|
||||
late Box<Node> _powNodeSource;
|
||||
// late Box<Node> _nodeSource;
|
||||
// late Box<Node> _powNodeSource;
|
||||
late Box<Contact> _contactSource;
|
||||
late Box<Template> _templates;
|
||||
late Box<ExchangeTemplate> _exchangeTemplates;
|
||||
@@ -316,8 +291,6 @@ late Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
|
||||
late Box<PayjoinSession> _payjoinSessionSource;
|
||||
late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
|
||||
Future<void> setup({
|
||||
required Box<Node> nodeSource,
|
||||
required Box<Node> powNodeSource,
|
||||
required Box<Contact> contactSource,
|
||||
required Box<Template> templates,
|
||||
required Box<ExchangeTemplate> exchangeTemplates,
|
||||
@@ -329,8 +302,6 @@ Future<void> setup({
|
||||
required SecureStorage secureStorage,
|
||||
required GlobalKey<NavigatorState> navigatorKey,
|
||||
}) async {
|
||||
_nodeSource = nodeSource;
|
||||
_powNodeSource = powNodeSource;
|
||||
_contactSource = contactSource;
|
||||
_templates = templates;
|
||||
_exchangeTemplates = exchangeTemplates;
|
||||
@@ -353,8 +324,6 @@ Future<void> setup({
|
||||
const isBitcoinBuyEnabled = false;
|
||||
|
||||
final settingsStore = await SettingsStoreBase.load(
|
||||
nodeSource: _nodeSource,
|
||||
powNodeSource: _powNodeSource,
|
||||
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
||||
);
|
||||
|
||||
@@ -390,18 +359,16 @@ Future<void> setup({
|
||||
}
|
||||
|
||||
void _registerCoreSingletons(SettingsStore settingsStore) {
|
||||
getIt.registerFactory<Box<Node>>(() => _nodeSource);
|
||||
getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
|
||||
|
||||
getIt.registerSingleton(AuthenticationStore());
|
||||
getIt.registerSingleton<WalletListStore>(WalletListStore());
|
||||
getIt.registerSingleton(NodeListStoreBase.instance);
|
||||
// getIt.registerSingleton(NodeListStoreBase.instance);
|
||||
getIt.registerSingleton<SettingsStore>(settingsStore);
|
||||
getIt.registerSingleton<AppStore>(AppStore(
|
||||
authenticationStore: getIt.get<AuthenticationStore>(),
|
||||
walletList: getIt.get<WalletListStore>(),
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
nodeListStore: getIt.get<NodeListStore>(),
|
||||
// nodeListStore: getIt.get<NodeListStore>(),
|
||||
themeStore: getIt.get<ThemeStore>()));
|
||||
getIt.registerSingleton<TradesStore>(TradesStore(appStore: getIt.get<AppStore>()));
|
||||
getIt.registerSingleton<OrdersStore>(
|
||||
@@ -413,11 +380,6 @@ void _registerCoreSingletons(SettingsStore settingsStore) {
|
||||
getIt.registerSingleton<OrderFilterStore>(OrderFilterStore());
|
||||
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore(getIt.get<AppStore>()));
|
||||
getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
|
||||
getIt.registerSingleton<SendTemplateStore>(SendTemplateStore(templateSource: _templates));
|
||||
getIt.registerSingleton<ExchangeTemplateStore>(
|
||||
ExchangeTemplateStore(templateSource: _exchangeTemplates));
|
||||
getIt.registerSingleton<YatStore>(
|
||||
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())..init());
|
||||
getIt.registerSingleton<AnonpayTransactionsStore>(
|
||||
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
|
||||
getIt.registerSingleton<SeedSettingsStore>(SeedSettingsStore());
|
||||
@@ -429,23 +391,28 @@ void _registerCoreSingletons(SettingsStore settingsStore) {
|
||||
case HardwareWalletType.ledger:
|
||||
return getIt<LedgerViewModel>();
|
||||
case HardwareWalletType.trezor:
|
||||
return getIt<TrezorViewModel>();
|
||||
return getIt<TrezorConnectViewModel>();
|
||||
case HardwareWalletType.cupcake:
|
||||
case HardwareWalletType.coldcard:
|
||||
case HardwareWalletType.seedsigner:
|
||||
case HardwareWalletType.keystone:
|
||||
throw Exception(
|
||||
"This should not have happened, because airgapped Wallets don't need View Models");
|
||||
"This should not have happened, because airgapped Wallets don't need View Models",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
getIt.registerLazySingleton(() => LedgerViewModel());
|
||||
getIt.registerLazySingleton(() => LedgerViewModel(getIt<AppStore>()));
|
||||
|
||||
getIt.registerLazySingleton(() => BitboxViewModel());
|
||||
getIt.registerLazySingleton(BitboxViewModel.new);
|
||||
|
||||
getIt.registerLazySingleton(
|
||||
() => TrezorConnect("hashbags://trezor_connect", appName: "Hash Bags"));
|
||||
getIt.registerLazySingleton(() => TrezorViewModel(getIt<TrezorConnect>()));
|
||||
() => TrezorConnect("hashbags://trezor_connect", appName: "Hash Bags"),
|
||||
);
|
||||
|
||||
getIt.registerLazySingleton(
|
||||
() => TrezorConnectViewModel(getIt<TrezorConnect>(), getIt<SecureStorage>()),
|
||||
);
|
||||
|
||||
getIt.registerFactory<KeyService>(() => KeyService(getIt.get<SecureStorage>()));
|
||||
|
||||
@@ -551,7 +518,6 @@ void _registerCoreViewModels(SettingsStore settingsStore) {
|
||||
|
||||
getIt.registerFactory<WalletAddressListViewModel>(() => WalletAddressListViewModel(
|
||||
appStore: getIt.get<AppStore>(),
|
||||
yatStore: getIt.get<YatStore>(),
|
||||
fiatConversionStore: getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactory(() => BalanceViewModel(
|
||||
@@ -562,7 +528,6 @@ void _registerCoreViewModels(SettingsStore settingsStore) {
|
||||
getIt.registerFactory(
|
||||
() => ExchangeViewModel(
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<ExchangeTemplateStore>(),
|
||||
getIt.get<TradesStore>(),
|
||||
getIt.get<SharedPreferences>(),
|
||||
getIt.get<ContactListViewModel>(),
|
||||
@@ -583,6 +548,13 @@ void _registerCoreViewModels(SettingsStore settingsStore) {
|
||||
getIt.registerFactory(() => DashboardViewModel(
|
||||
tradeMonitor: getIt.get<TradeMonitor>(),
|
||||
balanceViewModel: getIt.get<BalanceViewModel>(),
|
||||
accountListViewModelFactory: () {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
if (wallet == null) return null;
|
||||
final hasAccounts = wallet.hasAccountsSupport;
|
||||
if (!hasAccounts) return null;
|
||||
return getIt.get<WalletAccountListViewModel>();
|
||||
},
|
||||
appStore: getIt.get<AppStore>(),
|
||||
tradesStore: getIt.get<TradesStore>(),
|
||||
ordersStore: getIt.get<OrdersStore>(),
|
||||
@@ -590,24 +562,30 @@ void _registerCoreViewModels(SettingsStore settingsStore) {
|
||||
orderFilterStore: getIt.get<OrderFilterStore>(),
|
||||
transactionFilterStore: getIt.get<TransactionFilterStore>(),
|
||||
settingsStore: settingsStore,
|
||||
yatStore: getIt.get<YatStore>(),
|
||||
anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
|
||||
payjoinTransactionsStore: getIt.get<PayjoinTransactionsStore>(),
|
||||
sharedPreferences: getIt.get<SharedPreferences>(),
|
||||
keyService: getIt.get<KeyService>()));
|
||||
|
||||
getIt.registerFactoryParam<CardCustomizerBloc, bool, BitcoinAmountDisplayMode?>(
|
||||
(lightningMode, displayMode) {
|
||||
getIt.registerFactoryParam<CardCustomizerBloc, CardCustomizerBlocParams, void>(
|
||||
(params, _) {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
return CardCustomizerBloc(wallet,
|
||||
lightningMode: lightningMode,
|
||||
canHide: params.canHide,
|
||||
lightningMode: params.lightningMode,
|
||||
displaySats: wallet.type == WalletType.bitcoin &&
|
||||
(displayMode == BitcoinAmountDisplayMode.satoshi ||
|
||||
(displayMode == BitcoinAmountDisplayMode.satoshiForLightning && lightningMode)));
|
||||
(params.amountDisplayMode == BitcoinAmountDisplayMode.satoshi ||
|
||||
(params.amountDisplayMode == BitcoinAmountDisplayMode.satoshiForLightning && params.lightningMode)));
|
||||
});
|
||||
|
||||
getIt.registerFactory<AccountCreationModal>(() => AccountCreationModal(
|
||||
accountEditOrCreateViewModel: getIt.get<MoneroAccountEditOrCreateViewModel>()));
|
||||
getIt.registerLazySingleton<PriceStore>(() => PriceStore(settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory<ChartsBloc>(
|
||||
() => ChartsBloc(appStore: getIt.get<AppStore>(), priceStore: getIt.get<PriceStore>()));
|
||||
|
||||
getIt.registerFactory<ChartsPage>(() => ChartsPage(
|
||||
chartsBloc: getIt.get<ChartsBloc>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<LightningUsernameBloc>(
|
||||
() => LightningUsernameBloc(getIt.get<AppStore>().wallet!));
|
||||
@@ -775,17 +753,6 @@ void _registerDashboardAndSendPages(AppStore appStore) {
|
||||
return walletKitService;
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => BalancePage(
|
||||
nftViewModel: getIt.get<NFTViewModel>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory<DashboardPage>(() => DashboardPage(
|
||||
bottomSheetService: getIt.get<BottomSheetService>(),
|
||||
balancePage: getIt.get<BalancePage>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<NewDashboard>(() => NewDashboard(
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
@@ -797,27 +764,6 @@ void _registerDashboardAndSendPages(AppStore appStore) {
|
||||
nftViewModel: getIt.get<NFTViewModel>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<DesktopSidebarWrapper>(() {
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||
return DesktopSidebarWrapper(
|
||||
bottomSheetService: getIt.get<BottomSheetService>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
desktopSidebarViewModel: getIt.get<DesktopSidebarViewModel>(),
|
||||
child: getIt.get<DesktopDashboardPage>(param1: _navigatorKey),
|
||||
desktopNavigatorKey: _navigatorKey,
|
||||
);
|
||||
});
|
||||
getIt.registerFactoryParam<DesktopDashboardPage, GlobalKey<NavigatorState>, void>(
|
||||
(desktopKey, _) => DesktopDashboardPage(
|
||||
balancePage: getIt.get<BalancePage>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
desktopKey: desktopKey,
|
||||
));
|
||||
|
||||
getIt.registerFactory<TransactionsPage>(
|
||||
() => TransactionsPage(dashboardViewModel: getIt.get<DashboardViewModel>()));
|
||||
|
||||
getIt.registerFactory<Setup2FAInfoPage>(() => Setup2FAInfoPage());
|
||||
|
||||
getIt.registerFactory<Setup2FAPage>(
|
||||
@@ -829,43 +775,9 @@ void _registerDashboardAndSendPages(AppStore appStore) {
|
||||
getIt.registerFactory<Modify2FAPage>(
|
||||
() => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
|
||||
|
||||
getIt.registerFactory<DesktopSettingsPage>(
|
||||
() => DesktopSettingsPage(getIt.get<DashboardViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
|
||||
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
|
||||
|
||||
getIt.registerFactoryParam<AnonInvoicePageViewModel, List<dynamic>, void>((args, _) {
|
||||
final address = args.first as String;
|
||||
final pageOption = args.last as ReceivePageOption;
|
||||
return AnonInvoicePageViewModel(
|
||||
getIt.get<AnonPayApi>(),
|
||||
address,
|
||||
getIt.get<SettingsStore>(),
|
||||
getIt.get<AppStore>().wallet!,
|
||||
_anonpayInvoiceInfoSource,
|
||||
getIt.get<SharedPreferences>(),
|
||||
pageOption,
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<AnonPayInvoicePage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final pageOption = args.last as ReceivePageOption;
|
||||
return AnonPayInvoicePage(getIt.get<AnonInvoicePageViewModel>(param1: args),
|
||||
getIt.get<ReceiveOptionViewModel>(param1: pageOption));
|
||||
});
|
||||
|
||||
getIt.registerFactory<ReceivePage>(
|
||||
() => ReceivePage(addressListViewModel: getIt.get<WalletAddressListViewModel>()));
|
||||
|
||||
getIt.registerFactory<AddressPage>(
|
||||
() => AddressPage(
|
||||
addressListViewModel: getIt<WalletAddressListViewModel>(),
|
||||
dashboardViewModel: getIt<DashboardViewModel>(),
|
||||
receiveOptionViewModel: getIt<ReceiveOptionViewModel>(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NewReceivePage, bool?, CryptoCurrency?>((param1, param2) =>
|
||||
NewReceivePage(
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
@@ -878,10 +790,6 @@ void _registerDashboardAndSendPages(AppStore appStore) {
|
||||
(WalletAddressListItem? item, _) =>
|
||||
WalletAddressEditOrCreateViewModel(wallet: getIt.get<AppStore>().wallet!, item: item));
|
||||
|
||||
getIt.registerFactoryParam<AddressEditOrCreatePage, dynamic, void>((dynamic item, _) =>
|
||||
AddressEditOrCreatePage(
|
||||
addressEditOrCreateViewModel:
|
||||
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
|
||||
|
||||
getIt.registerFactoryParam<AddressLabelInputPopup, dynamic, void>((dynamic item, _) =>
|
||||
AddressLabelInputPopup(
|
||||
@@ -897,17 +805,12 @@ void _registerDashboardAndSendPages(AppStore appStore) {
|
||||
walletAddressEditOrCreateViewModel:
|
||||
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
|
||||
|
||||
getIt.registerFactory<SendTemplateViewModel>(() => SendTemplateViewModel(
|
||||
getIt.get<AppStore>().wallet!,
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<SendTemplateStore>(),
|
||||
getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactoryParam<SendViewModel, UnspentCoinType?, void>(
|
||||
(coinTypeToSpendFrom, _) => SendViewModel(
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<SendTemplateViewModel>(),
|
||||
getIt.get<FiatConversionStore>(),
|
||||
getIt.get<AddressResolverService>(),
|
||||
getIt.get<BalanceViewModel>(),
|
||||
getIt.get<ContactListViewModel>(),
|
||||
_transactionDescriptionBox,
|
||||
@@ -920,14 +823,7 @@ void _registerDashboardAndSendPages(AppStore appStore) {
|
||||
getIt.get<FeesViewModel>()),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<SendPage, PaymentRequest?, UnspentCoinType?>(
|
||||
(PaymentRequest? initialPaymentRequest, coinTypeToSpendFrom) => SendPage(
|
||||
sendViewModel: getIt.get<SendViewModel>(param1: coinTypeToSpendFrom),
|
||||
authService: getIt.get<AuthService>(),
|
||||
initialPaymentRequest: initialPaymentRequest,
|
||||
paymentViewModel: getIt.get<PaymentViewModel>(),
|
||||
walletSwitcherViewModel: getIt.get<WalletSwitcherViewModel>(),
|
||||
));
|
||||
|
||||
|
||||
getIt.registerFactoryParam<NewSendPage, SendPageParams?, void>((params, _) {
|
||||
params ??= SendPageParams();
|
||||
@@ -936,13 +832,12 @@ void _registerDashboardAndSendPages(AppStore appStore) {
|
||||
authService: getIt.get<AuthService>(),
|
||||
params: params,
|
||||
contactListViewModel: getIt.get<ContactListViewModel>(),
|
||||
paymentViewModel: getIt.get<PaymentViewModel>(),
|
||||
anyPayService: getIt.get<AnyPayService>(),
|
||||
linkViewModel: getIt.get<LinkViewModel>(),
|
||||
walletSwitcherViewModel: getIt.get<WalletSwitcherViewModel>(),
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactory(
|
||||
() => SendTemplatePage(sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
|
||||
}
|
||||
|
||||
void _registerWalletListAndAccounts() {
|
||||
@@ -1009,44 +904,65 @@ void _registerWalletListAndAccounts() {
|
||||
getIt.registerFactory<MoneroAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet.type == WalletType.monero || wallet.type == WalletType.wownero) {
|
||||
return MoneroAccountListViewModel(wallet, getIt.get<SettingsStore>());
|
||||
return MoneroAccountListViewModel(wallet);
|
||||
}
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${wallet.type} for generate Monero AccountListViewModel');
|
||||
});
|
||||
|
||||
getIt.registerFactory(
|
||||
() => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => NanoAccountListPage(accountListViewModel: getIt.get<NanoAccountListViewModel>()));
|
||||
|
||||
/*getIt.registerFactory(() {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
return MoneroAccountEditOrCreateViewModel(wallet.accountList);
|
||||
getIt.registerFactory<BitcoinAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
return BitcoinAccountListViewModel(wallet);
|
||||
}
|
||||
|
||||
// FIXME: throw exception.
|
||||
return null;
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${wallet.type} for generate Bitcoin AccountListViewModel');
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => MoneroAccountEditOrCreatePage(
|
||||
moneroAccountCreationViewModel:
|
||||
getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
|
||||
getIt.registerFactory<WalletAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
return getIt.get<BitcoinAccountListViewModel>();
|
||||
case WalletType.monero:
|
||||
case WalletType.wownero:
|
||||
return getIt.get<MoneroAccountListViewModel>();
|
||||
default:
|
||||
throw Exception('Unsupported wallet type for WalletAccountListViewModel: ${wallet.type}');
|
||||
}
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel, AccountListItem?, void>(
|
||||
(AccountListItem? account, _) => MoneroAccountEditOrCreateViewModel(
|
||||
monero!.getAccountList(getIt.get<AppStore>().wallet!),
|
||||
wownero?.getAccountList(getIt.get<AppStore>().wallet!),
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
accountListItem: account));
|
||||
(AccountListItem? account, _) {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem?, void>(
|
||||
(AccountListItem? account, _) => MoneroAccountEditOrCreatePage(
|
||||
moneroAccountCreationViewModel:
|
||||
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
|
||||
if (wallet.type == WalletType.monero) {
|
||||
return MoneroAccountEditOrCreateViewModel(
|
||||
monero!.getAccountList(wallet),
|
||||
null,
|
||||
wallet: wallet,
|
||||
accountListItem: account,
|
||||
);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.wownero) {
|
||||
return MoneroAccountEditOrCreateViewModel(
|
||||
null,
|
||||
wownero!.getAccountList(wallet),
|
||||
wallet: wallet,
|
||||
accountListItem: account,
|
||||
);
|
||||
}
|
||||
|
||||
throw StateError("Account creation is unavailable for ${wallet.type}");
|
||||
});
|
||||
|
||||
getIt.registerFactory<BitcoinAccountEditOrCreateViewModel>(
|
||||
() => BitcoinAccountEditOrCreateViewModel(
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NanoAccountEditOrCreateViewModel, NanoAccount?, void>(
|
||||
(NanoAccount? account, _) =>
|
||||
@@ -1055,10 +971,6 @@ void _registerWalletListAndAccounts() {
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
accountListItem: account));
|
||||
|
||||
getIt.registerFactoryParam<NanoAccountEditOrCreatePage, NanoAccount?, void>(
|
||||
(NanoAccount? account, _) => NanoAccountEditOrCreatePage(
|
||||
nanoAccountCreationViewModel:
|
||||
getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
|
||||
}
|
||||
|
||||
void _registerSettingsAndNodes(List<WalletInfo> walletList) {
|
||||
@@ -1103,13 +1015,17 @@ void _registerSettingsAndNodes(List<WalletInfo> walletList) {
|
||||
|
||||
getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => ShowKeysDisclaimerPage(
|
||||
authService: getIt.get<AuthService>(), settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
|
||||
|
||||
getIt.registerFactoryParam<AnimatedURPage, Map<String, String>, void>(
|
||||
(Map<String, String> urQr, _) => AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
|
||||
|
||||
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
|
||||
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
|
||||
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>((ContactRecord? contact, _) =>
|
||||
ContactViewModel(_contactSource, getIt.get<AppStore>(), getIt.get<AddressResolverService>(),
|
||||
contact: contact));
|
||||
|
||||
getIt.registerFactoryParam<ContactListViewModel, CryptoCurrency?, void>(
|
||||
(CryptoCurrency? cur, _) =>
|
||||
@@ -1125,26 +1041,21 @@ void _registerSettingsAndNodes(List<WalletInfo> walletList) {
|
||||
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
|
||||
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
||||
|
||||
getIt.registerFactory(() => AddressListPage(getIt.get<WalletAddressListViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<NewAddressesPage, bool, void>(
|
||||
(showHidden, _) => NewAddressesPage(
|
||||
showHidden: showHidden,
|
||||
getIt.registerFactoryParam<NodeListViewModel, bool, void>((isPow, _) {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
return NodeListViewModel(appStore, isPow);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<NewAddressesPage, AddressesPageArgs, void>(
|
||||
(args, _) => NewAddressesPage(
|
||||
showHidden: args.showHidden,
|
||||
popOnSelection: args.popOnSelection,
|
||||
addressListViewModel: getIt<WalletAddressListViewModel>(),
|
||||
dashboardViewModel: getIt<DashboardViewModel>(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
return NodeListViewModel(_nodeSource, appStore);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
return PowNodeListViewModel(_powNodeSource, appStore);
|
||||
});
|
||||
|
||||
getIt.registerFactory(
|
||||
() => ConnectionSyncViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
||||
@@ -1153,9 +1064,32 @@ void _registerSettingsAndNodes(List<WalletInfo> walletList) {
|
||||
getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(),
|
||||
getIt.get<AuthService>(), getIt.get<AppStore>().wallet!.isHardwareWallet));
|
||||
|
||||
|
||||
getIt.registerFactory<WalletAccountEditOrCreateViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
return getIt.get<BitcoinAccountEditOrCreateViewModel>();
|
||||
case WalletType.monero:
|
||||
case WalletType.wownero:
|
||||
return getIt.get<MoneroAccountEditOrCreateViewModel>();
|
||||
default:
|
||||
throw Exception(
|
||||
'Unsupported wallet type for WalletAccountEditOrCreateViewModel: ${wallet.type}');
|
||||
}
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<WalletAccountsPage, DashboardViewModel, void>(
|
||||
(dashboardViewModel, _) => WalletAccountsPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
accountListViewModel:
|
||||
dashboardViewModel.accountListViewModel ?? getIt.get<WalletAccountListViewModel>(),
|
||||
accountEditOrCreateViewModel: getIt.get<WalletAccountEditOrCreateViewModel>(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => TrocadorProvidersPage(getIt.get<TrocadorProvidersViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => DomainLookupsPage(getIt.get<ConnectionSyncViewModel>()));
|
||||
|
||||
@@ -1186,8 +1120,8 @@ void _registerSettingsAndNodes(List<WalletInfo> walletList) {
|
||||
final WalletType type = args['type'] as WalletType? ?? getIt.get<AppStore>().wallet!.type;
|
||||
final bool isPow = args['isPow'] as bool? ?? false;
|
||||
final Node? editingNode = args['editingNode'] as Node?;
|
||||
final nodeSourceArgs = isPow ? _powNodeSource : _nodeSource;
|
||||
return NodeCreateOrEditViewModel(nodeSourceArgs, type, editingNode, getIt.get<SettingsStore>());
|
||||
return NodeCreateOrEditViewModel(isPow, type, getIt.get<SettingsStore>(),
|
||||
editingNode: editingNode);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
|
||||
@@ -1216,7 +1150,6 @@ void _registerExchangeAndSwap() {
|
||||
isTestEnvironment: kDebugMode || kProfileMode,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
|
||||
|
||||
getIt.registerFactory<FeesViewModel>(
|
||||
() => FeesViewModel(
|
||||
@@ -1235,33 +1168,22 @@ void _registerExchangeAndSwap() {
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<ExchangePage, PaymentRequest?, void>(
|
||||
(PaymentRequest? paymentRequest, __) {
|
||||
return ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>(), paymentRequest);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<NewSwapPage, PaymentRequest?, CryptoCurrency?>(
|
||||
(PaymentRequest? paymentRequest, CryptoCurrency? initialCurrency) {
|
||||
return NewSwapPage(
|
||||
getIt.get<ExchangeViewModel>(),
|
||||
getIt.get<AuthService>(),
|
||||
getIt.get<AddressResolverService>(),
|
||||
paymentRequest,
|
||||
walletSwitcherViewModel: getIt.get<WalletSwitcherViewModel>(),
|
||||
initialCurrency: initialCurrency,
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => ExchangeTradePage(exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
ExchangeTradeExternalSendPage(exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => BackgroundSyncPage(getIt.get<DashboardViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<SwapConfirmationBottomSheet, PaymentFlowResult, void>(
|
||||
(paymentFlowResult, _) => SwapConfirmationBottomSheet(
|
||||
@@ -1281,6 +1203,16 @@ void _registerExchangeAndSwap() {
|
||||
appStore: getIt.get<AppStore>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory(() => WalletSwitchService(
|
||||
walletLoadingService: getIt.get<WalletLoadingService>(),
|
||||
appStore: getIt.get<AppStore>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory(() => AnyPayService(
|
||||
appStore: getIt.get<AppStore>(),
|
||||
walletSwitchService: getIt.get<WalletSwitchService>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory(() => WalletSwitcherViewModel(
|
||||
appStore: getIt.get<AppStore>(),
|
||||
walletLoadingService: getIt.get<WalletLoadingService>(),
|
||||
@@ -1348,8 +1280,6 @@ void _registerRestoreAndBackup() {
|
||||
|
||||
getIt.registerFactory(() => RescanPage(getIt.get<RescanViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, Map<String, dynamic>?>(
|
||||
(type, additionalParams) {
|
||||
final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?;
|
||||
@@ -1374,6 +1304,9 @@ void _registerRestoreAndBackup() {
|
||||
param1: derivations,
|
||||
)));
|
||||
|
||||
getIt.registerFactory<CsvExportService>(
|
||||
() => CsvExportService(transactionDescriptionBox: _transactionDescriptionBox));
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsViewModel, List<dynamic>, void>((params, _) {
|
||||
final transactionInfo = params[0] as TransactionInfo;
|
||||
final canReplaceByFee = params[1] as bool? ?? false;
|
||||
@@ -1389,10 +1322,12 @@ void _registerRestoreAndBackup() {
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsModal, TransactionInfo, void>((transactionInfo, _) =>
|
||||
TransactionDetailsModal(
|
||||
transactionDetailsViewModel:
|
||||
getIt.get<TransactionDetailsViewModel>(param1: [transactionInfo, false])));
|
||||
getIt.registerFactoryParam<TransactionDetailsModal, TransactionInfo, bool?>(
|
||||
(transactionInfo, highlightNoteField) => TransactionDetailsModal(
|
||||
transactionDetailsViewModel:
|
||||
getIt.get<TransactionDetailsViewModel>(param1: [transactionInfo, false]),
|
||||
highlightNoteField: highlightNoteField ?? false,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
|
||||
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
|
||||
@@ -1417,10 +1352,7 @@ void _registerRestoreAndBackup() {
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactory<PreSeedPage>(() => PreSeedPage());
|
||||
|
||||
getIt.registerFactoryParam<TransactionSuccessPage, String, void>(
|
||||
(content, _) => TransactionSuccessPage(content: content));
|
||||
getIt.registerFactory<PreSeedPage>(() => PreSeedPage(getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>(
|
||||
(trade, _) => TradeDetailsViewModel(tradeForDetails: trade, appStore: getIt.get<AppStore>()));
|
||||
@@ -1456,40 +1388,12 @@ void _registerRestoreAndBackup() {
|
||||
void _registerBuyAndSupport() {
|
||||
getIt.registerFactory(() => BuyAmountViewModel());
|
||||
|
||||
getIt.registerFactory(() => BuySellViewModel(getIt.get<AppStore>()));
|
||||
getIt.registerFactoryParam<BuySellViewModel, NewBuySellParams, void>((params, _) => BuySellViewModel(
|
||||
params: params, getIt.get<AppStore>(), fiatConversionStore: getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactory(() => BuySellPage(getIt.get<BuySellViewModel>()));
|
||||
getIt.registerFactoryParam<NewBuySellAmountPage, NewBuySellParams, void>((params, _) =>
|
||||
NewBuySellAmountPage(buySellViewModel: getIt.get<BuySellViewModel>(param1: params)));
|
||||
|
||||
getIt.registerFactoryParam<BuyOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final items = args.first as List<SelectableItem>;
|
||||
final pickAnOption = args[1] as void Function(SelectableOption option)?;
|
||||
final confirmOption = args[2] as void Function(BuildContext contex)?;
|
||||
return BuyOptionsPage(items: items, pickAnOption: pickAnOption, confirmOption: confirmOption);
|
||||
});
|
||||
|
||||
getIt
|
||||
.registerFactoryParam<PaymentMethodOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final items = args.first as List<SelectableOption>;
|
||||
final pickAnOption = args[1] as void Function(SelectableOption option)?;
|
||||
|
||||
return PaymentMethodOptionsPage(items: items, pickAnOption: pickAnOption);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
return BuyViewModel(_ordersSource, getIt.get<OrdersStore>(), getIt.get<SettingsStore>(),
|
||||
getIt.get<BuyAmountViewModel>(),
|
||||
wallet: wallet!);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<BuyWebViewPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final url = args.first as String;
|
||||
final buyViewModel = args[1] as BuyViewModel;
|
||||
|
||||
return BuyWebViewPage(
|
||||
buyViewModel: buyViewModel, ordersStore: getIt.get<OrdersStore>(), url: url);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
@@ -1522,15 +1426,12 @@ void _registerBuyAndSupport() {
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsListPage, UnspentCoinType?, void>(
|
||||
(coinTypeToSpendFrom, _) => UnspentCoinsListPage(
|
||||
unspentCoinsListViewModel:
|
||||
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom)));
|
||||
|
||||
getIt.registerFactoryParam<NewCoinControlPage, UnspentCoinType?, void>((coinTypeToSpendFrom, _) =>
|
||||
NewCoinControlPage(
|
||||
unspentCoinsListViewModel:
|
||||
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom)));
|
||||
getIt.registerFactoryParam<NewCoinControlPage, UnspentCoinType?, bool?>(
|
||||
(coinTypeToSpendFrom, canEdit) => NewCoinControlPage(
|
||||
unspentCoinsListViewModel:
|
||||
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
|
||||
canEdit: canEdit ?? true,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel, UnspentCoinsItem,
|
||||
UnspentCoinsListViewModel>(
|
||||
@@ -1548,8 +1449,9 @@ void _registerBuyAndSupport() {
|
||||
}
|
||||
|
||||
void _registerDevToolsAndBridge(AppStore appStore) {
|
||||
getIt.registerFactory(() => AddressResolver(
|
||||
wallet: getIt.get<AppStore>().wallet!, settingsStore: getIt.get<SettingsStore>()));
|
||||
getIt.registerFactory<AddressResolverService>(
|
||||
() => AddressResolverService(settingsStore: getIt.get<SettingsStore>()),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
|
||||
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
|
||||
@@ -1560,11 +1462,6 @@ void _registerDevToolsAndBridge(AppStore appStore) {
|
||||
useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
|
||||
wallet: getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>(), getIt.get<AuthService>()));
|
||||
|
||||
getIt.registerFactory(() => DesktopSidebarViewModel());
|
||||
|
||||
getIt.registerFactoryParam<AnonpayDetailsViewModel, AnonpayInvoiceInfo, void>(
|
||||
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsViewModel(
|
||||
anonPayApi: getIt.get<AnonPayApi>(),
|
||||
@@ -1580,10 +1477,6 @@ void _registerDevToolsAndBridge(AppStore appStore) {
|
||||
themeStore: getIt.get<ThemeStore>(),
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<AnonPayReceivePage, AnonPayReceivePageArgs, void>(
|
||||
(AnonPayReceivePageArgs anonpayReceivePageArgs, _) =>
|
||||
AnonPayReceivePage(args: anonpayReceivePageArgs));
|
||||
|
||||
getIt.registerFactoryParam<AnonpayDetailsPage, AnonpayInvoiceInfo, void>(
|
||||
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
|
||||
anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
|
||||
@@ -1608,13 +1501,8 @@ void _registerDevToolsAndBridge(AppStore appStore) {
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<ManageNodesPage, bool, void>((bool isPow, _) {
|
||||
if (isPow) {
|
||||
return ManageNodesPage(isPow,
|
||||
powNodeListViewModel: getIt.get<PowNodeListViewModel>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>());
|
||||
}
|
||||
return ManageNodesPage(isPow,
|
||||
nodeListViewModel: getIt.get<NodeListViewModel>(),
|
||||
nodeListViewModel: getIt.get<NodeListViewModel>(param1: isPow),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>());
|
||||
});
|
||||
|
||||
@@ -1624,9 +1512,14 @@ void _registerDevToolsAndBridge(AppStore appStore) {
|
||||
|
||||
getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
NFTSendViewModel(appStore, getIt.get<ContactListViewModel>(param1: CryptoCurrency.sol)));
|
||||
|
||||
getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
|
||||
getIt.registerFactoryParam<SeedVerificationPage, bool, void>((bool isNewWalletCreated, _) =>
|
||||
SeedVerificationPage(getIt.get<WalletSeedViewModel>(),
|
||||
isNewWalletCreated: isNewWalletCreated));
|
||||
|
||||
getIt.registerFactory(() => DevMoneroBackgroundSyncPage(getIt.get<DevMoneroBackgroundSync>()));
|
||||
|
||||
@@ -1700,7 +1593,6 @@ void _registerDevToolsAndBridge(AppStore appStore) {
|
||||
getIt.registerLazySingleton(() => NodeSwitchingService(
|
||||
appStore: getIt.get<AppStore>(),
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
nodeSource: _nodeSource,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<BridgeAmountPage, CryptoCurrency, void>(
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import 'package:cw_core/amount/amount_sanitizer.dart';
|
||||
|
||||
String calculateFiatAmount({double? price, String? cryptoAmount, bool raw = false}) {
|
||||
if (price == null || cryptoAmount == null) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
cryptoAmount = cryptoAmount.replaceAll(',', '.');
|
||||
cryptoAmount = cryptoAmount.sanitized();
|
||||
|
||||
final _amount = double.tryParse(cryptoAmount);
|
||||
if (_amount == null || _amount.isNaN) return '0.00';
|
||||
if (_amount == null || !_amount.isFinite) return '0.00';
|
||||
final _result = price * _amount;
|
||||
if (!_result.isFinite) return '0.00';
|
||||
final result = _result < 0 ? _result * -1 : _result;
|
||||
|
||||
if (result == 0.0) {
|
||||
|
||||
@@ -7,6 +7,10 @@ String calculateFiatAmountRaw({required double cryptoAmount, double? price}) {
|
||||
|
||||
final result = price * cryptoAmount;
|
||||
|
||||
if (!result.isFinite) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
if (result == 0.0) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/keyable.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'contact.g.dart';
|
||||
part 'contact.part.dart';
|
||||
|
||||
@HiveType(typeId: Contact.typeId)
|
||||
// @HiveType(typeId: Contact.typeId)
|
||||
class Contact extends HiveObject with Keyable {
|
||||
Contact(
|
||||
{required this.name,
|
||||
@@ -22,19 +22,19 @@ class Contact extends HiveObject with Keyable {
|
||||
static const typeId = CONTACT_TYPE_ID;
|
||||
static const boxName = 'Contacts';
|
||||
|
||||
@HiveField(0, defaultValue: '')
|
||||
// @HiveField(0, defaultValue: '')
|
||||
String name;
|
||||
|
||||
@HiveField(1, defaultValue: '')
|
||||
// @HiveField(1, defaultValue: '')
|
||||
String address;
|
||||
|
||||
@HiveField(2, defaultValue: 0)
|
||||
// @HiveField(2, defaultValue: 0)
|
||||
late int raw;
|
||||
|
||||
@HiveField(3)
|
||||
// @HiveField(3)
|
||||
DateTime lastChange;
|
||||
|
||||
@HiveField(4, defaultValue: "")
|
||||
// @HiveField(4, defaultValue: "")
|
||||
String displayName;
|
||||
|
||||
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'contact.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class ContactAdapter extends TypeAdapter<Contact> {
|
||||
@override
|
||||
final int typeId = 0;
|
||||
|
||||
@override
|
||||
Contact read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return Contact(
|
||||
name: fields[0] == null ? '' : fields[0] as String,
|
||||
address: fields[1] == null ? '' : fields[1] as String,
|
||||
lastChange: fields[3] as DateTime?,
|
||||
displayName: fields[4] == null ? '' : fields[4] as String,
|
||||
)..raw = fields[2] == null ? 0 : fields[2] as int;
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, Contact obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
..write(obj.name)
|
||||
..writeByte(1)
|
||||
..write(obj.address)
|
||||
..writeByte(2)
|
||||
..write(obj.raw)
|
||||
..writeByte(3)
|
||||
..write(obj.lastChange)
|
||||
..writeByte(4)
|
||||
..write(obj.displayName);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ContactAdapter && runtimeType == other.runtimeType && typeId == other.typeId;
|
||||
}
|
||||
@@ -9,14 +9,16 @@ import 'package:hash_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:hash_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:hash_wallet/entities/fiat_currency.dart';
|
||||
import 'package:hash_wallet/entities/fs_migration.dart';
|
||||
import 'package:hash_wallet/entities/node_list.dart';
|
||||
import 'package:hash_wallet/entities/preferences_key.dart';
|
||||
import 'package:hash_wallet/entities/secret_store_key.dart';
|
||||
import 'package:hash_wallet/monero/monero.dart';
|
||||
import 'package:hash_wallet/new-ui/model/charts/charts_asset.dart';
|
||||
import 'package:hash_wallet/wownero/wownero.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:hash_wallet/entities/sync_status_display_mode.dart';
|
||||
import 'package:cw_core/node_list.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -25,7 +27,6 @@ import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
|
||||
const newCakeWalletMoneroUri =
|
||||
@@ -46,11 +47,10 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
|
||||
const nanoDefaultNodeUri = 'rpc.nano.to';
|
||||
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
||||
const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
|
||||
const tronDefaultNodeUri = 'api.trongrid.io';
|
||||
const tronDefaultNodeUri = 'trx.nownodes.io';
|
||||
const newCakeWalletBitcoinUri =
|
||||
'electrum.blockstream.info:50002'; // Hash Bags: default flipped to Blockstream
|
||||
const wowneroDefaultNodeUri = 'node.wownero.org:443';
|
||||
const zanoDefaultNodeUri = '37.27.100.59:10500';
|
||||
const moneroWorldNodeUri = '.moneroworld.com';
|
||||
const decredDefaultUri = "default-spv-nodes";
|
||||
const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
|
||||
@@ -62,15 +62,13 @@ Future<void> defaultSettingsMigration(
|
||||
{required int version,
|
||||
required SharedPreferences sharedPreferences,
|
||||
required SecureStorage secureStorage,
|
||||
required Box<Node> nodes,
|
||||
required Box<Node> powNodes,
|
||||
required Box<Contact> contactSource}) async {
|
||||
if (Platform.isIOS) {
|
||||
await ios_migrate_v1(contactSource);
|
||||
}
|
||||
|
||||
// check current nodes for nullability regardless of the version
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
// await checkCurrentNodes(sharedPreferences);
|
||||
|
||||
final isNewInstall =
|
||||
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
|
||||
@@ -98,15 +96,18 @@ Future<void> defaultSettingsMigration(
|
||||
case 1:
|
||||
await sharedPreferences.setString(
|
||||
PreferencesKey.currentFiatCurrencyKey, FiatCurrency.usd.toString());
|
||||
await sharedPreferences.setInt(PreferencesKey.currentTransactionPriorityKeyLegacy,
|
||||
monero!.getDefaultTransactionPriority().raw);
|
||||
|
||||
if (monero != null) {
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentTransactionPriorityKeyLegacy,
|
||||
monero!.getDefaultTransactionPriority().raw);
|
||||
}
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw);
|
||||
await sharedPreferences.setBool('save_recipient_address', true);
|
||||
await resetToDefault(nodes);
|
||||
await resetToDefault();
|
||||
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.monero,
|
||||
currentNodePreferenceKey: PreferencesKey.currentNodeIdKey,
|
||||
@@ -114,14 +115,12 @@ Future<void> defaultSettingsMigration(
|
||||
trusted: true,
|
||||
);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.bitcoin,
|
||||
currentNodePreferenceKey: PreferencesKey.currentBitcoinElectrumSererIdKey,
|
||||
useSSL: true,
|
||||
);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.litecoin,
|
||||
currentNodePreferenceKey: PreferencesKey.currentLitecoinElectrumSererIdKey,
|
||||
@@ -129,9 +128,8 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
await replaceNodesMigration(nodes: nodes);
|
||||
await replaceNodesMigration();
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.monero,
|
||||
newDefaultUri: newCakeWalletMoneroUri,
|
||||
@@ -146,13 +144,12 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
await updateNodeTypes(nodes: nodes);
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.bitcoin);
|
||||
await updateNodeTypes();
|
||||
await addWalletNodeList(type: WalletType.bitcoin);
|
||||
|
||||
break;
|
||||
case 4:
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.bitcoin,
|
||||
newDefaultUri: newCakeWalletBitcoinUri,
|
||||
@@ -179,7 +176,6 @@ Future<void> defaultSettingsMigration(
|
||||
|
||||
case 11:
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.monero,
|
||||
newDefaultUri: newCakeWalletMoneroUri,
|
||||
@@ -190,33 +186,32 @@ Future<void> defaultSettingsMigration(
|
||||
break;
|
||||
|
||||
case 12:
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
// await checkCurrentNodes(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 13:
|
||||
await resetBitcoinElectrumServer(nodes, sharedPreferences);
|
||||
await resetBitcoinElectrumServer(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 15:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.litecoin);
|
||||
await addWalletNodeList(type: WalletType.litecoin);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.litecoin,
|
||||
currentNodePreferenceKey: PreferencesKey.currentLitecoinElectrumSererIdKey,
|
||||
);
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
// await checkCurrentNodes(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 16:
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
// await checkCurrentNodes(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 17:
|
||||
break;
|
||||
|
||||
case 18:
|
||||
addWalletNodeList(nodes: nodes, type: WalletType.monero);
|
||||
addWalletNodeList(type: WalletType.monero);
|
||||
break;
|
||||
|
||||
case 19:
|
||||
@@ -226,25 +221,22 @@ Future<void> defaultSettingsMigration(
|
||||
await migrateExchangeStatus(sharedPreferences);
|
||||
break;
|
||||
case 21:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.ethereum);
|
||||
await addWalletNodeList(type: WalletType.ethereum);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.ethereum,
|
||||
currentNodePreferenceKey: PreferencesKey.currentEthereumNodeIdKey,
|
||||
);
|
||||
break;
|
||||
case 22:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.nano);
|
||||
await addNanoPowNodeList(nodes: powNodes);
|
||||
await addWalletNodeList(type: WalletType.nano);
|
||||
await addNanoPowNodeList();
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.nano,
|
||||
currentNodePreferenceKey: PreferencesKey.currentNanoNodeIdKey,
|
||||
);
|
||||
await _changeDefaultNode(
|
||||
nodes: powNodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.nano,
|
||||
currentNodePreferenceKey: PreferencesKey.currentNanoPowNodeIdKey,
|
||||
@@ -252,18 +244,16 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 23:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.bitcoinCash);
|
||||
await addWalletNodeList(type: WalletType.bitcoinCash);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.bitcoinCash,
|
||||
currentNodePreferenceKey: PreferencesKey.currentBitcoinCashNodeIdKey,
|
||||
);
|
||||
break;
|
||||
case 24:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.polygon);
|
||||
await addWalletNodeList(type: WalletType.polygon);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.polygon,
|
||||
currentNodePreferenceKey: PreferencesKey.currentPolygonNodeIdKey,
|
||||
@@ -287,7 +277,6 @@ Future<void> defaultSettingsMigration(
|
||||
break;
|
||||
case 29:
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.bitcoin,
|
||||
newDefaultUri: newCakeWalletBitcoinUri,
|
||||
@@ -300,7 +289,7 @@ Future<void> defaultSettingsMigration(
|
||||
await disableServiceStatusFiatDisabled(sharedPreferences);
|
||||
break;
|
||||
case 31:
|
||||
await updateNanoNodeList(nodes: nodes);
|
||||
await updateNanoNodeList();
|
||||
break;
|
||||
case 32:
|
||||
await updateBtcNanoWalletInfos();
|
||||
@@ -308,10 +297,9 @@ Future<void> defaultSettingsMigration(
|
||||
case 33:
|
||||
break;
|
||||
case 34:
|
||||
addWalletNodeList(nodes: nodes, type: WalletType.bitcoin);
|
||||
addWalletNodeList(type: WalletType.bitcoin);
|
||||
case 35:
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.bitcoin,
|
||||
newDefaultUri: newCakeWalletBitcoinUri,
|
||||
@@ -321,9 +309,8 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 36:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.wownero);
|
||||
await addWalletNodeList(type: WalletType.wownero);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.wownero,
|
||||
currentNodePreferenceKey: PreferencesKey.currentWowneroNodeIdKey,
|
||||
@@ -337,9 +324,8 @@ Future<void> defaultSettingsMigration(
|
||||
await fixBtcDerivationPaths();
|
||||
break;
|
||||
case 39:
|
||||
_fixNodesUseSSLFlag(nodes);
|
||||
_fixNodesUseSSLFlag();
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.nano,
|
||||
newDefaultUri: nanoDefaultNodeUri,
|
||||
@@ -349,7 +335,7 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 40:
|
||||
await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
await removeMoneroWorld(sharedPreferences: sharedPreferences);
|
||||
break;
|
||||
case 41:
|
||||
_changeExchangeProviderAvailability(
|
||||
@@ -357,13 +343,13 @@ Future<void> defaultSettingsMigration(
|
||||
providerName: "SwapTrade",
|
||||
enabled: false,
|
||||
);
|
||||
addWalletNodeList(nodes: nodes, type: WalletType.bitcoin);
|
||||
addWalletNodeList(type: WalletType.bitcoin);
|
||||
break;
|
||||
case 42:
|
||||
_fixNodesUseSSLFlag(nodes);
|
||||
_fixNodesUseSSLFlag();
|
||||
break;
|
||||
case 43:
|
||||
_fixNodesUseSSLFlag(nodes);
|
||||
_fixNodesUseSSLFlag();
|
||||
_changeExchangeProviderAvailability(
|
||||
sharedPreferences,
|
||||
providerName: "THORChain",
|
||||
@@ -376,9 +362,8 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 44:
|
||||
_fixNodesUseSSLFlag(nodes);
|
||||
_fixNodesUseSSLFlag();
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.bitcoin,
|
||||
newDefaultUri: newCakeWalletBitcoinUri,
|
||||
@@ -388,20 +373,18 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 45:
|
||||
addWalletNodeList(nodes: nodes, type: WalletType.polygon);
|
||||
addWalletNodeList(nodes: nodes, type: WalletType.ethereum);
|
||||
addWalletNodeList(type: WalletType.polygon);
|
||||
addWalletNodeList(type: WalletType.ethereum);
|
||||
break;
|
||||
case 46:
|
||||
await _fixNodesUseSSLFlag(nodes);
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.litecoin);
|
||||
await _fixNodesUseSSLFlag();
|
||||
await addWalletNodeList(type: WalletType.litecoin);
|
||||
await _updateNode(
|
||||
nodes: nodes,
|
||||
currentUri: "ethereum.publicnode.com",
|
||||
newUri: "ethereum-rpc.publicnode.com",
|
||||
useSSL: true,
|
||||
);
|
||||
await _updateNode(
|
||||
nodes: nodes,
|
||||
currentUri: "polygon-bor.publicnode.com",
|
||||
newUri: "polygon-bor-rpc.publicnode.com",
|
||||
useSSL: true,
|
||||
@@ -428,42 +411,36 @@ Future<void> defaultSettingsMigration(
|
||||
);
|
||||
break;
|
||||
case 50:
|
||||
migrateExistingNodesToUseAutoSwitching(nodes: nodes, powNodes: powNodes);
|
||||
migrateExistingNodesToUseAutoSwitching();
|
||||
break;
|
||||
case 51:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.dogecoin);
|
||||
await addWalletNodeList(type: WalletType.dogecoin);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.dogecoin,
|
||||
currentNodePreferenceKey: PreferencesKey.currentDogecoinNodeIdKey,
|
||||
);
|
||||
break;
|
||||
case 52:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.base);
|
||||
await addWalletNodeList(type: WalletType.base);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.base,
|
||||
currentNodePreferenceKey: PreferencesKey.currentBaseNodeIdKey,
|
||||
);
|
||||
break;
|
||||
case 53:
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.arbitrum);
|
||||
await addWalletNodeList(type: WalletType.arbitrum);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.arbitrum,
|
||||
currentNodePreferenceKey: PreferencesKey.currentArbitrumNodeIdKey,
|
||||
);
|
||||
break;
|
||||
case 54:
|
||||
// Hash Bags: upstream backed up Wownero seeds here ahead of dropping Wownero.
|
||||
break;
|
||||
case 55:
|
||||
// Hash Bags: zcash removed. The original migration here added
|
||||
// zcash node list + set default zcash node. We no longer load
|
||||
// zcash_node_list.yml (it was deleted), so skip the whole step.
|
||||
break;
|
||||
case 56:
|
||||
await sharedPreferences.setString(
|
||||
PreferencesKey.syncStatusDisplayMode, SyncStatusDisplayMode.blocksRemaining.name);
|
||||
@@ -471,9 +448,8 @@ Future<void> defaultSettingsMigration(
|
||||
case 57:
|
||||
await _addXautTokenToExistingEthereumWallets();
|
||||
|
||||
await addWalletNodeList(nodes: nodes, type: WalletType.bsc);
|
||||
await addWalletNodeList(type: WalletType.bsc);
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.bsc,
|
||||
currentNodePreferenceKey: PreferencesKey.currentBscNodeIdKey,
|
||||
@@ -516,13 +492,55 @@ Future<void> defaultSettingsMigration(
|
||||
break;
|
||||
case 65:
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.base,
|
||||
currentNodePreferenceKey: PreferencesKey.currentBaseNodeIdKey,
|
||||
oldUri: ['base.nownodes.io'],
|
||||
);
|
||||
break;
|
||||
case 66:
|
||||
await _changeDefaultNode(
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.arbitrum,
|
||||
currentNodePreferenceKey: PreferencesKey.currentArbitrumNodeIdKey,
|
||||
oldUri: ["arbitrum.nownodes.io"]);
|
||||
break;
|
||||
case 67:
|
||||
_changeExchangeProviderAvailability(
|
||||
sharedPreferences,
|
||||
providerName: "LetsExchange",
|
||||
enabled: true,
|
||||
);
|
||||
break;
|
||||
case 68:
|
||||
await _changeDefaultNode(
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.monero,
|
||||
newDefaultUri: newCakeWalletMoneroUri,
|
||||
currentNodePreferenceKey: PreferencesKey.currentNodeIdKey,
|
||||
useSSL: true,
|
||||
oldUri: ['nodes.hashvault.pro:18081'],
|
||||
);
|
||||
break;
|
||||
case 69:
|
||||
_changeExchangeProviderAvailability(
|
||||
sharedPreferences,
|
||||
providerName: "Exolix",
|
||||
enabled: false,
|
||||
);
|
||||
break;
|
||||
case 70:
|
||||
break;
|
||||
case 71:
|
||||
_changeExchangeProviderAvailability(
|
||||
sharedPreferences,
|
||||
providerName: "Swaps.XYZ",
|
||||
enabled: false,
|
||||
);
|
||||
break;
|
||||
case 72:
|
||||
await createDefaultChartsData();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -539,12 +557,13 @@ Future<void> defaultSettingsMigration(
|
||||
}
|
||||
|
||||
Future<void> _updateNode({
|
||||
required Box<Node> nodes,
|
||||
required String currentUri,
|
||||
String? newUri,
|
||||
bool? useSSL,
|
||||
}) async {
|
||||
for (Node node in nodes.values) {
|
||||
final nodes = await Node.getAll();
|
||||
|
||||
for (Node node in nodes) {
|
||||
if (node.uriRaw == currentUri) {
|
||||
if (newUri != null) {
|
||||
node.uriRaw = newUri;
|
||||
@@ -560,7 +579,6 @@ Future<void> _updateNode({
|
||||
/// generic function for changing any wallet default node
|
||||
/// instead of making a new function for each change
|
||||
Future<void> _changeDefaultNode({
|
||||
required Box<Node> nodes,
|
||||
required SharedPreferences sharedPreferences,
|
||||
required WalletType type,
|
||||
required String currentNodePreferenceKey,
|
||||
@@ -570,19 +588,21 @@ Future<void> _changeDefaultNode({
|
||||
List<String>?
|
||||
oldUri, // ignore, if you want to force replace the node regardless of the user's current node
|
||||
}) async {
|
||||
List<Node> nodes = await Node.getAll();
|
||||
|
||||
final currentNodeId = sharedPreferences.getInt(currentNodePreferenceKey);
|
||||
final bool shouldReplace;
|
||||
if (currentNodeId == null) {
|
||||
shouldReplace = true;
|
||||
} else {
|
||||
final currentNode = nodes.values.firstWhere((node) => node.key == currentNodeId);
|
||||
shouldReplace = oldUri?.any((e) => currentNode.uriRaw.contains(e)) ?? true;
|
||||
final currentNode = nodes.firstWhereOrNull((node) => node.id == currentNodeId);
|
||||
shouldReplace =
|
||||
currentNode == null || (oldUri?.any((e) => currentNode.uriRaw.contains(e)) ?? true);
|
||||
}
|
||||
|
||||
if (shouldReplace) {
|
||||
newDefaultUri ??= _getDefaultNodeUri(type);
|
||||
var newNodeId =
|
||||
nodes.values.firstWhereOrNull((element) => element.uriRaw == newDefaultUri)?.key;
|
||||
newDefaultUri ??= (await getDefaultNodeFromFiles(type)).uriRaw;
|
||||
var newNodeId = nodes.firstWhereOrNull((element) => element.uriRaw == newDefaultUri)?.id;
|
||||
|
||||
// new node doesn't exist, then add it
|
||||
if (newNodeId == null) {
|
||||
@@ -593,43 +613,11 @@ Future<void> _changeDefaultNode({
|
||||
trusted: trusted,
|
||||
);
|
||||
|
||||
await nodes.add(newNode);
|
||||
newNodeId = newNode.key;
|
||||
await newNode.save();
|
||||
newNodeId = newNode.id;
|
||||
}
|
||||
|
||||
await sharedPreferences.setInt(currentNodePreferenceKey, newNodeId as int);
|
||||
}
|
||||
}
|
||||
|
||||
String _getDefaultNodeUri(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return newCakeWalletMoneroUri;
|
||||
case WalletType.bitcoin:
|
||||
return newCakeWalletBitcoinUri;
|
||||
case WalletType.litecoin:
|
||||
return cakeWalletLitecoinElectrumUri;
|
||||
case WalletType.ethereum:
|
||||
return ethereumDefaultNodeUri;
|
||||
case WalletType.nano:
|
||||
return nanoDefaultNodeUri;
|
||||
case WalletType.bitcoinCash:
|
||||
return cakeWalletBitcoinCashDefaultNodeUri;
|
||||
case WalletType.polygon:
|
||||
return polygonDefaultNodeUri;
|
||||
case WalletType.wownero:
|
||||
return wowneroDefaultNodeUri;
|
||||
case WalletType.dogecoin:
|
||||
return dogecoinDefaultNodeUri;
|
||||
case WalletType.base:
|
||||
return baseDefaultNodeUri;
|
||||
case WalletType.arbitrum:
|
||||
return arbitrumDefaultNodeUri;
|
||||
case WalletType.bsc:
|
||||
return bscDefaultNodeUri;
|
||||
case WalletType.banano:
|
||||
case WalletType.none:
|
||||
return '';
|
||||
await sharedPreferences.setInt(currentNodePreferenceKey, newNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,8 +635,9 @@ Future<void> _changeExchangeProviderAvailability(SharedPreferences sharedPrefere
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fixNodesUseSSLFlag(Box<Node> nodes) async {
|
||||
for (Node node in nodes.values) {
|
||||
Future<void> _fixNodesUseSSLFlag() async {
|
||||
final nodes = await Node.getAll();
|
||||
for (Node node in nodes) {
|
||||
switch (node.uriRaw) {
|
||||
case cakeWalletLitecoinElectrumUri:
|
||||
case cakeWalletBitcoinElectrumUri:
|
||||
@@ -661,7 +650,8 @@ Future<void> _fixNodesUseSSLFlag(Box<Node> nodes) async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
|
||||
Future<void> updateNanoNodeList() async {
|
||||
final nodes = await Node.getAll();
|
||||
final nodeList = await loadDefaultNodes(WalletType.nano);
|
||||
var listOfNewEndpoints = <String>[
|
||||
"app.natrium.io",
|
||||
@@ -672,14 +662,13 @@ Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
|
||||
];
|
||||
// add new nodes:
|
||||
for (final node in nodeList) {
|
||||
if (listOfNewEndpoints.contains(node.uriRaw) && !nodes.values.contains(node)) {
|
||||
await nodes.add(node);
|
||||
if (listOfNewEndpoints.contains(node.uriRaw) && !nodes.contains(node)) {
|
||||
await node.save();
|
||||
}
|
||||
}
|
||||
|
||||
// update the nautilus node:
|
||||
final nautilusNode =
|
||||
nodes.values.firstWhereOrNull((element) => element.uriRaw == "node.perish.co");
|
||||
final nautilusNode = nodes.firstWhereOrNull((element) => element.uriRaw == "node.perish.co");
|
||||
if (nautilusNode != null) {
|
||||
nautilusNode.uriRaw = "node.nautilus.io";
|
||||
nautilusNode.path = "/api";
|
||||
@@ -699,7 +688,12 @@ Future<void> disableServiceStatusFiatDisabled(SharedPreferences sharedPreference
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _updateMoneroPriority(SharedPreferences sharedPreferences) async {
|
||||
if (monero == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentPriority =
|
||||
await sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority) ??
|
||||
monero!.getDefaultTransactionPriority().serialize();
|
||||
@@ -790,7 +784,7 @@ Future<void> validateBitcoinSavedTransactionPriority(SharedPreferences sharedPre
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
|
||||
Future<void> replaceNodesMigration() async {
|
||||
final replaceNodes = <String, Node>{
|
||||
'eu-node.cakewallet.io:18081':
|
||||
Node(uri: 'xmr-node-eu.cakewallet.com:18081', type: WalletType.monero),
|
||||
@@ -799,7 +793,8 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
|
||||
'node.xmr.ru:13666': Node(uri: 'node.monero.net:18081', type: WalletType.monero)
|
||||
};
|
||||
|
||||
nodes.values.forEach((Node node) async {
|
||||
List<Node> nodes = await Node.getAll();
|
||||
nodes.forEach((Node node) async {
|
||||
final nodeToReplace = replaceNodes[node.uri];
|
||||
|
||||
if (nodeToReplace != null) {
|
||||
@@ -811,16 +806,11 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
|
||||
});
|
||||
}
|
||||
|
||||
Node? getBitcoinTestnetDefaultElectrumServer({required Box<Node> nodes}) {
|
||||
return nodes.values
|
||||
.firstWhereOrNull((Node node) => node.uriRaw == publicBitcoinTestnetElectrumUri) ??
|
||||
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
|
||||
}
|
||||
Future<Node?> getBitcoinTestnetDefaultElectrumServer() async {
|
||||
final nodes = await Node.getAll();
|
||||
|
||||
Node? getDefaultNode({required Box<Node> nodes, required WalletType type}) {
|
||||
final defaultUri = _getDefaultNodeUri(type);
|
||||
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == defaultUri) ??
|
||||
nodes.values.firstWhereOrNull((node) => node.type == type);
|
||||
return nodes.firstWhereOrNull((Node node) => node.uriRaw == publicBitcoinTestnetElectrumUri) ??
|
||||
nodes.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
|
||||
}
|
||||
|
||||
Future<void> insecureStorageMigration({
|
||||
@@ -931,8 +921,9 @@ Future<void> rewriteSecureStoragePin({required SecureStorage secureStorage}) asy
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateNodeTypes({required Box<Node> nodes}) async {
|
||||
nodes.values.forEach((node) async {
|
||||
Future<void> updateNodeTypes() async {
|
||||
List<Node> nodes = await Node.getAll();
|
||||
nodes.forEach((node) async {
|
||||
if (node.type == null) {
|
||||
node.type = WalletType.monero;
|
||||
await node.save();
|
||||
@@ -983,11 +974,15 @@ Future<void> generateBackupPassword(SecureStorage secureStorage) async {
|
||||
|
||||
Future<void> changeTransactionPriorityAndFeeRateKeys(SharedPreferences sharedPreferences) async {
|
||||
final legacyTransactionPriority =
|
||||
sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
|
||||
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
|
||||
bitcoin!.getMediumTransactionPriority().serialize());
|
||||
sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy);
|
||||
if (legacyTransactionPriority != null) {
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
|
||||
}
|
||||
if (bitcoin != null) {
|
||||
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
|
||||
bitcoin!.getMediumTransactionPriority().serialize());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fixBtcDerivationPaths() async {
|
||||
@@ -1020,153 +1015,14 @@ Future<void> updateBtcNanoWalletInfos() async {}
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<void> checkCurrentNodes(
|
||||
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final currentBitcoinElectrumSeverId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
final currentLitecoinElectrumSeverId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||
final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
|
||||
final currentBaseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
|
||||
final currentArbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey);
|
||||
final currentBscNodeId = sharedPreferences.getInt(PreferencesKey.currentBscNodeIdKey);
|
||||
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
|
||||
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
|
||||
final currentBitcoinCashNodeId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
|
||||
final currentDogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey);
|
||||
final currentWowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
|
||||
final currentMoneroNode =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
|
||||
final currentBitcoinElectrumServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId);
|
||||
final currentLitecoinElectrumServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId);
|
||||
final currentEthereumNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
|
||||
final currentPolygonNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
|
||||
final currentBaseNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentBaseNodeId);
|
||||
final currentArbitrumNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentArbitrumNodeId);
|
||||
final currentBscNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentBscNodeId);
|
||||
final currentNanoNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
|
||||
final currentNanoPowNodeServer =
|
||||
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
|
||||
final currentBitcoinCashNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinCashNodeId);
|
||||
final currentDogecoinNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentDogecoinNodeId);
|
||||
final currentWowneroNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId);
|
||||
|
||||
if (currentMoneroNode == null) {
|
||||
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
||||
await nodeSource.add(newCakeWalletNode);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
|
||||
}
|
||||
|
||||
if (currentBitcoinElectrumServer == null) {
|
||||
final cakeWalletElectrum = Node(
|
||||
uri: cakeWalletBitcoinElectrumUri,
|
||||
type: WalletType.bitcoin,
|
||||
useSSL: false,
|
||||
isEnabledForAutoSwitching: true);
|
||||
await nodeSource.add(cakeWalletElectrum);
|
||||
final cakeWalletElectrumTestnet =
|
||||
Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||
await nodeSource.add(cakeWalletElectrumTestnet);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
}
|
||||
|
||||
if (currentLitecoinElectrumServer == null) {
|
||||
final cakeWalletElectrum =
|
||||
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin, useSSL: false);
|
||||
await nodeSource.add(cakeWalletElectrum);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
}
|
||||
|
||||
if (currentEthereumNodeServer == null) {
|
||||
final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentNanoNodeServer == null) {
|
||||
final node = Node(uri: nanoDefaultNodeUri, useSSL: true, type: WalletType.nano);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentNanoPowNodeServer == null) {
|
||||
Node? node = powNodeSource.values
|
||||
.firstWhereOrNull((node) => node.uri.toString() == nanoDefaultPowNodeUri);
|
||||
if (node == null) {
|
||||
node = Node(uri: nanoDefaultPowNodeUri, useSSL: true, type: WalletType.nano);
|
||||
await powNodeSource.add(node);
|
||||
}
|
||||
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentBitcoinCashNodeServer == null) {
|
||||
final node =
|
||||
Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash, useSSL: false);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentDogecoinNodeServer == null) {
|
||||
final node = Node(uri: dogecoinDefaultNodeUri, type: WalletType.dogecoin, useSSL: true);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentDogecoinNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentPolygonNodeServer == null) {
|
||||
final node = Node(uri: polygonDefaultNodeUri, type: WalletType.polygon);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentBaseNodeServer == null) {
|
||||
final node = Node(uri: baseDefaultNodeUri, type: WalletType.base);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentArbitrumNodeServer == null) {
|
||||
final node = Node(uri: arbitrumDefaultNodeUri, type: WalletType.arbitrum);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentArbitrumNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentBscNodeServer == null) {
|
||||
final node = Node(uri: bscDefaultNodeUri, type: WalletType.bsc);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentBscNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentWowneroNodeServer == null) {
|
||||
final node = Node(uri: wowneroDefaultNodeUri, type: WalletType.wownero);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resetBitcoinElectrumServer(
|
||||
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||
Future<void> resetBitcoinElectrumServer(SharedPreferences sharedPreferences) async {
|
||||
final nodeSource = await Node.getAll();
|
||||
final currentElectrumSeverId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
final oldElectrumServer = nodeSource.values
|
||||
final oldElectrumServer = nodeSource
|
||||
.firstWhereOrNull((node) => node.uri.toString().contains('electrumx.cakewallet.com'));
|
||||
var cakeWalletNode = nodeSource.values
|
||||
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
|
||||
var cakeWalletNode =
|
||||
nodeSource.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
|
||||
|
||||
if (cakeWalletNode == null) {
|
||||
cakeWalletNode = Node(
|
||||
@@ -1177,12 +1033,12 @@ Future<void> resetBitcoinElectrumServer(
|
||||
// final cakeWalletElectrumTestnet =
|
||||
// Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||
// await nodeSource.add(cakeWalletElectrumTestnet);
|
||||
await nodeSource.add(cakeWalletNode);
|
||||
await cakeWalletNode.save();
|
||||
}
|
||||
|
||||
if (currentElectrumSeverId == oldElectrumServer?.key) {
|
||||
if (currentElectrumSeverId == oldElectrumServer?.id) {
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int);
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.id);
|
||||
}
|
||||
|
||||
await oldElectrumServer?.delete();
|
||||
@@ -1200,38 +1056,41 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
|
||||
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
|
||||
}
|
||||
|
||||
Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
|
||||
Future<void> addNanoPowNodeList() async {
|
||||
final nodeList = await loadDefaultNanoPowNodes();
|
||||
final nodes = await Node.getAllPow();
|
||||
for (var node in nodeList) {
|
||||
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await nodes.add(node);
|
||||
if (nodes.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await node.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
|
||||
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
|
||||
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
|
||||
Future<Node?> getNanoDefaultPowNode() async {
|
||||
final nodes = await Node.getAll();
|
||||
return nodes.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
|
||||
nodes.firstWhereOrNull((node) => (node.type == WalletType.nano));
|
||||
}
|
||||
|
||||
Future<void> addWalletNodeList({required Box<Node> nodes, required WalletType type}) async {
|
||||
Future<void> addWalletNodeList({required WalletType type}) async {
|
||||
final nodes = await Node.getAll();
|
||||
final List<Node> nodeList = await loadDefaultNodes(type);
|
||||
for (var node in nodeList) {
|
||||
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await nodes.add(node);
|
||||
if (nodes.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await node.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeMoneroWorld(
|
||||
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
||||
Future<void> removeMoneroWorld({required SharedPreferences sharedPreferences}) async {
|
||||
final nodes = await Node.getAll();
|
||||
const cakeWalletMoneroNodeUriPattern = '.moneroworld.com';
|
||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final currentMoneroNode = nodes.values.firstWhere((node) => node.key == currentMoneroNodeId);
|
||||
final currentMoneroNode = nodes.firstWhere((node) => node.id == currentMoneroNodeId);
|
||||
final needToReplaceCurrentMoneroNode =
|
||||
currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
|
||||
|
||||
nodes.values.forEach((node) async {
|
||||
nodes.forEach((node) async {
|
||||
if (node.type == WalletType.monero &&
|
||||
node.uri.toString().contains(cakeWalletMoneroNodeUriPattern)) {
|
||||
await node.delete();
|
||||
@@ -1240,7 +1099,6 @@ Future<void> removeMoneroWorld(
|
||||
|
||||
if (needToReplaceCurrentMoneroNode) {
|
||||
await _changeDefaultNode(
|
||||
nodes: nodes,
|
||||
sharedPreferences: sharedPreferences,
|
||||
type: WalletType.monero,
|
||||
newDefaultUri: newCakeWalletMoneroUri,
|
||||
@@ -1250,8 +1108,7 @@ Future<void> removeMoneroWorld(
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> migrateExistingNodesToUseAutoSwitching(
|
||||
{required Box<Node> nodes, required Box<Node> powNodes}) async {
|
||||
Future<void> migrateExistingNodesToUseAutoSwitching() async {
|
||||
final listOfDefaultNodesWithAutoSwitching = [
|
||||
'bitcoincash.stackwallet.com:50002',
|
||||
'bch.aftrek.org:50002',
|
||||
@@ -1285,12 +1142,23 @@ Future<void> migrateExistingNodesToUseAutoSwitching(
|
||||
'zano.cakewallet.com:11211',
|
||||
'electrum.cakewallet.com:50002',
|
||||
];
|
||||
for (var node in [...nodes.values.toList(), ...powNodes.values.toList()]) {
|
||||
final nodes = await Node.getAll();
|
||||
for (var node in nodes) {
|
||||
if (listOfDefaultNodesWithAutoSwitching.contains(node.uriRaw)) {
|
||||
node.isEnabledForAutoSwitching = true;
|
||||
await node.save();
|
||||
}
|
||||
}
|
||||
|
||||
final powNodes = await Node.getAllPow();
|
||||
|
||||
for (var node in powNodes) {
|
||||
if (listOfDefaultNodesWithAutoSwitching.contains(node.uriRaw)) {
|
||||
node.isEnabledForAutoSwitching = true;
|
||||
node.isPow = true;
|
||||
await node.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addXautTokenToExistingEthereumWallets() async {
|
||||
@@ -1308,24 +1176,31 @@ Future<void> _addXautTokenToExistingEthereumWallets() async {
|
||||
|
||||
final ethereumWallets =
|
||||
allWallets.where((wallet) => wallet.type == WalletType.ethereum).toList();
|
||||
const ethereumChainId = 1;
|
||||
|
||||
for (final walletInfo in ethereumWallets) {
|
||||
final sanitizedName = walletInfo.name.replaceAll(' ', '_');
|
||||
final boxName = '${sanitizedName}_${Erc20Token.ethereumBoxName}';
|
||||
final existingToken = await Erc20Token.getByContract(
|
||||
walletInfo.name,
|
||||
ethereumChainId,
|
||||
xautToken.contractAddress,
|
||||
);
|
||||
|
||||
Box<Erc20Token> tokenBox;
|
||||
if (CakeHive.isBoxOpen(boxName)) {
|
||||
tokenBox = CakeHive.box<Erc20Token>(boxName);
|
||||
} else {
|
||||
tokenBox = await CakeHive.openBox<Erc20Token>(boxName);
|
||||
}
|
||||
if (existingToken != null) continue;
|
||||
|
||||
final xautAddress = xautToken.contractAddress;
|
||||
if (!tokenBox.containsKey(xautAddress)) {
|
||||
await tokenBox.put(xautAddress, xautToken);
|
||||
}
|
||||
await Erc20Token.copyWith(
|
||||
xautToken,
|
||||
walletName: walletInfo.name,
|
||||
chainId: ethereumChainId,
|
||||
).save();
|
||||
}
|
||||
} catch (e) {
|
||||
printV('Error in XAUT migration: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> createDefaultChartsData() async {
|
||||
await ChartsAsset(asset: CryptoCurrency.btc, isFavorite: true).insert();
|
||||
await ChartsAsset(asset: CryptoCurrency.xmr, isFavorite: false).insert();
|
||||
await ChartsAsset(asset: CryptoCurrency.eth, isFavorite: false).insert();
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import 'package:hash_wallet/evm/evm.dart';
|
||||
import 'package:hash_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.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:ens_dart/ens_dart.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
|
||||
class EnsRecord {
|
||||
static Future<String> fetchEnsAddress(String name, {WalletBase? wallet}) async {
|
||||
Web3Client? _client;
|
||||
|
||||
if (wallet != null && (isEVMCompatibleChain(wallet.type))) {
|
||||
_client = evm!.getWeb3Client(wallet);
|
||||
}
|
||||
|
||||
if (_client == null) {
|
||||
late final client = ProxyWrapper().getHttpIOClient();
|
||||
|
||||
_client = Web3Client("https://ethereum-rpc.publicnode.com", client);
|
||||
}
|
||||
|
||||
try {
|
||||
final ens = Ens(client: _client);
|
||||
|
||||
if (wallet != null) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return await ens.withName(name).getCoinAddress(CoinType.XMR);
|
||||
case WalletType.bitcoin:
|
||||
return await ens.withName(name).getCoinAddress(CoinType.BTC);
|
||||
case WalletType.litecoin:
|
||||
return await ens.withName(name).getCoinAddress(CoinType.LTC);
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.base:
|
||||
case WalletType.arbitrum:
|
||||
case WalletType.bsc:
|
||||
default:
|
||||
return (await ens.withName(name).getAddress()).hex;
|
||||
}
|
||||
}
|
||||
|
||||
final addr = await ens.withName(name).getAddress();
|
||||
return addr.hex;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
+187
-64
@@ -1,18 +1,36 @@
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/enumerable_item.dart';
|
||||
import "package:cw_core/amount/money.dart";
|
||||
import "package:cw_core/currency.dart";
|
||||
import "package:cw_core/enumerable_item.dart";
|
||||
|
||||
class FiatCurrency extends EnumerableItem<String> with Serializable<String> implements Currency {
|
||||
const FiatCurrency({
|
||||
required String symbol,
|
||||
required this.symbol,
|
||||
required this.countryCode,
|
||||
required this.fullName,
|
||||
required this.emoji,
|
||||
this.decimals = 2,
|
||||
this.isoCountryCode,
|
||||
}) : super(title: symbol, raw: symbol);
|
||||
|
||||
final String countryCode;
|
||||
final String? isoCountryCode;
|
||||
|
||||
@override
|
||||
final String fullName;
|
||||
|
||||
@override
|
||||
final int decimals;
|
||||
|
||||
@override
|
||||
String get apiString => "fiat.$title";
|
||||
|
||||
@override
|
||||
final String symbol;
|
||||
|
||||
String get apiCountryCode => isoCountryCode ?? countryCode.toUpperCase();
|
||||
|
||||
final String emoji;
|
||||
|
||||
static List<FiatCurrency> get all => _all.values.toList();
|
||||
|
||||
static List<FiatCurrency> get currenciesAvailableToBuyWith => [
|
||||
@@ -60,67 +78,164 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
||||
kes,
|
||||
];
|
||||
|
||||
static const amd = FiatCurrency(symbol: 'AMD', countryCode: "arm", fullName: "Armenian Dram");
|
||||
static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso");
|
||||
static const aud = FiatCurrency(symbol: 'AUD', countryCode: "aus", fullName: "Australian Dollar");
|
||||
static const bdt = FiatCurrency(symbol: 'BDT', countryCode: "bgd", fullName: "Bangladeshi Taka");
|
||||
static const bgn = FiatCurrency(symbol: 'BGN', countryCode: "bgr", fullName: "Bulgarian Lev");
|
||||
static const brl = FiatCurrency(symbol: 'BRL', countryCode: "bra", fullName: "Brazilian Real");
|
||||
static const cad = FiatCurrency(symbol: 'CAD', countryCode: "cad", fullName: "Canadian Dollar");
|
||||
static const chf = FiatCurrency(symbol: 'CHF', countryCode: "che", fullName: "Swiss Franc");
|
||||
static const clp = FiatCurrency(symbol: 'CLP', countryCode: "chl", fullName: "Chilean Peso");
|
||||
static const cny = FiatCurrency(symbol: 'CNY', countryCode: "chn", fullName: "Chinese Yuan");
|
||||
static const cop = FiatCurrency(symbol: 'COP', countryCode: "col", fullName: "Colombian Peso");
|
||||
static const czk = FiatCurrency(symbol: 'CZK', countryCode: "czk", fullName: "Czech Koruna");
|
||||
static const dkk = FiatCurrency(symbol: 'DKK', countryCode: "dnk", fullName: "Danish Krone");
|
||||
static const egp = FiatCurrency(symbol: 'EGP', countryCode: "egy", fullName: "Egyptian Pound");
|
||||
static const eur = FiatCurrency(symbol: 'EUR', countryCode: "eur", fullName: "Euro");
|
||||
static const gbp = FiatCurrency(symbol: 'GBP', countryCode: "gbr", fullName: "Pound Sterling");
|
||||
static const ghs = FiatCurrency(symbol: 'GHS', countryCode: "gha", fullName: "Ghanaian Cedi");
|
||||
static const gtq =
|
||||
FiatCurrency(symbol: 'GTQ', countryCode: "gtm", fullName: "Guatemalan Quetzal");
|
||||
static const hkd = FiatCurrency(symbol: 'HKD', countryCode: "hkg", fullName: "Hong Kong Dollar");
|
||||
static const hrk = FiatCurrency(symbol: 'HRK', countryCode: "hrv", fullName: "Croatian Kuna");
|
||||
static const huf = FiatCurrency(symbol: 'HUF', countryCode: "hun", fullName: "Hungarian Forint");
|
||||
static const idr = FiatCurrency(symbol: 'IDR', countryCode: "idn", fullName: "Indonesian Rupiah");
|
||||
static const ils =
|
||||
FiatCurrency(symbol: 'ILS', countryCode: "isr", fullName: "Israeli New Shekel");
|
||||
static const inr = FiatCurrency(symbol: 'INR', countryCode: "ind", fullName: "Indian Rupee");
|
||||
static const irr = FiatCurrency(symbol: 'IRR', countryCode: "irn", fullName: "Iranian Rial");
|
||||
static const isk =
|
||||
FiatCurrency(symbol: 'ISK', countryCode: "isl", fullName: "Icelandic Krona Króna");
|
||||
static const jpy = FiatCurrency(symbol: 'JPY', countryCode: "jpn", fullName: "Japanese Yen");
|
||||
static const krw = FiatCurrency(symbol: 'KRW', countryCode: "kor", fullName: "South Korean Won");
|
||||
static const mad = FiatCurrency(symbol: 'MAD', countryCode: "mar", fullName: "Moroccan Dirham");
|
||||
static const mxn = FiatCurrency(symbol: 'MXN', countryCode: "mex", fullName: "Mexican Peso");
|
||||
static const myr = FiatCurrency(symbol: 'MYR', countryCode: "mys", fullName: "Malaysian Ringgit");
|
||||
static const ngn = FiatCurrency(symbol: 'NGN', countryCode: "nga", fullName: "Nigerian Naira");
|
||||
static const nok = FiatCurrency(symbol: 'NOK', countryCode: "nor", fullName: "Norwegian Krone");
|
||||
static const nzd =
|
||||
FiatCurrency(symbol: 'NZD', countryCode: "nzl", fullName: "New Zealand Dollar");
|
||||
static const php = FiatCurrency(symbol: 'PHP', countryCode: "phl", fullName: "Philippine Peso");
|
||||
static const pkr = FiatCurrency(symbol: 'PKR', countryCode: "pak", fullName: "Pakistani Rupee");
|
||||
static const pln =
|
||||
FiatCurrency(symbol: 'PLN', countryCode: "pol", fullName: "Poland Zloty złoty");
|
||||
static const ron = FiatCurrency(symbol: 'RON', countryCode: "rou", fullName: "Romanian Leu");
|
||||
static const rub = FiatCurrency(symbol: 'RUB', countryCode: "rus", fullName: "Russian Ruble");
|
||||
static const sar = FiatCurrency(symbol: 'SAR', countryCode: "sau", fullName: "Saudi Riyal");
|
||||
static const sek = FiatCurrency(symbol: 'SEK', countryCode: "swe", fullName: "Swedish Krona");
|
||||
static const sgd = FiatCurrency(symbol: 'SGD', countryCode: "sgp", fullName: "Singapore Dollar");
|
||||
static const thb =
|
||||
FiatCurrency(symbol: 'THB', countryCode: "tha", fullName: "New Thaiwan Dollar");
|
||||
static const twd = FiatCurrency(symbol: 'TWD', countryCode: "twn", fullName: "Thai Baht");
|
||||
static const uah = FiatCurrency(symbol: 'UAH', countryCode: "ukr", fullName: "Ukrainian Hryvnia");
|
||||
static const usd =
|
||||
FiatCurrency(symbol: 'USD', countryCode: "usa", fullName: "United States Dollar");
|
||||
static const vef =
|
||||
FiatCurrency(symbol: 'VEF', countryCode: "ven", fullName: "Venezuelan Bolivar Bolívar");
|
||||
static const vnd =
|
||||
FiatCurrency(symbol: 'VND', countryCode: "vnm", fullName: "Vietnamese Dong đồng");
|
||||
static const zar =
|
||||
FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand");
|
||||
static const tur = FiatCurrency(symbol: 'TRY', countryCode: "tur", fullName: "Turkish Lira");
|
||||
static const kes = FiatCurrency(symbol: 'KES', countryCode: "ken", fullName: "Kenyan Shillings");
|
||||
static const amd =
|
||||
FiatCurrency(symbol: "AMD", countryCode: "arm", fullName: "Armenian Dram", emoji: "🇦🇲");
|
||||
static const ars =
|
||||
FiatCurrency(symbol: "ARS", countryCode: "arg", fullName: "Argentine Peso", emoji: "🇦🇷");
|
||||
static const aud =
|
||||
FiatCurrency(symbol: "AUD", countryCode: "aus", fullName: "Australian Dollar", emoji: "🇦🇺");
|
||||
static const bdt =
|
||||
FiatCurrency(symbol: "BDT", countryCode: "bgd", fullName: "Bangladeshi Taka", emoji: "🇧🇩");
|
||||
static const bgn =
|
||||
FiatCurrency(symbol: "BGN", countryCode: "bgr", fullName: "Bulgarian Lev", emoji: "🇧🇬");
|
||||
static const brl =
|
||||
FiatCurrency(symbol: "BRL", countryCode: "bra", fullName: "Brazilian Real", emoji: "🇧🇷");
|
||||
static const cad = FiatCurrency(
|
||||
symbol: "CAD",
|
||||
countryCode: "cad",
|
||||
fullName: "Canadian Dollar",
|
||||
emoji: "🇨🇦",
|
||||
isoCountryCode: "CAN",
|
||||
);
|
||||
static const chf =
|
||||
FiatCurrency(symbol: "CHF", countryCode: "che", fullName: "Swiss Franc", emoji: "🇨🇭");
|
||||
static const clp =
|
||||
FiatCurrency(symbol: "CLP", countryCode: "chl", fullName: "Chilean Peso", emoji: "🇨🇱");
|
||||
static const cny =
|
||||
FiatCurrency(symbol: "CNY", countryCode: "chn", fullName: "Chinese Yuan", emoji: "🇨🇳");
|
||||
static const cop =
|
||||
FiatCurrency(symbol: "COP", countryCode: "col", fullName: "Colombian Peso", emoji: "🇨🇴");
|
||||
static const czk = FiatCurrency(
|
||||
symbol: "CZK",
|
||||
countryCode: "czk",
|
||||
fullName: "Czech Koruna",
|
||||
emoji: "🇨🇿",
|
||||
isoCountryCode: "CZE",
|
||||
);
|
||||
static const dkk =
|
||||
FiatCurrency(symbol: "DKK", countryCode: "dnk", fullName: "Danish Krone", emoji: "🇩🇰");
|
||||
static const egp =
|
||||
FiatCurrency(symbol: "EGP", countryCode: "egy", fullName: "Egyptian Pound", emoji: "🇪🇬");
|
||||
static const eur = FiatCurrency(
|
||||
symbol: "EUR",
|
||||
countryCode: "eur",
|
||||
fullName: "Euro",
|
||||
emoji: "🇪🇺",
|
||||
isoCountryCode: "DEU",
|
||||
);
|
||||
static const gbp =
|
||||
FiatCurrency(symbol: "GBP", countryCode: "gbr", fullName: "Pound Sterling", emoji: "🇬🇧");
|
||||
static const ghs =
|
||||
FiatCurrency(symbol: "GHS", countryCode: "gha", fullName: "Ghanaian Cedi", emoji: "🇬🇭");
|
||||
static const gtq = FiatCurrency(
|
||||
symbol: "GTQ",
|
||||
countryCode: "gtm",
|
||||
fullName: "Guatemalan Quetzal",
|
||||
emoji: "🇬🇹",
|
||||
);
|
||||
static const hkd =
|
||||
FiatCurrency(symbol: "HKD", countryCode: "hkg", fullName: "Hong Kong Dollar", emoji: "🇭🇰");
|
||||
static const hrk =
|
||||
FiatCurrency(symbol: "HRK", countryCode: "hrv", fullName: "Croatian Kuna", emoji: "🇭🇷");
|
||||
static const huf =
|
||||
FiatCurrency(symbol: "HUF", countryCode: "hun", fullName: "Hungarian Forint", emoji: "🇭🇺");
|
||||
static const idr =
|
||||
FiatCurrency(symbol: "IDR", countryCode: "idn", fullName: "Indonesian Rupiah", emoji: "🇮🇩");
|
||||
static const ils = FiatCurrency(
|
||||
symbol: "ILS",
|
||||
countryCode: "isr",
|
||||
fullName: "Israeli New Shekel",
|
||||
emoji: "🇮🇱",
|
||||
);
|
||||
static const inr =
|
||||
FiatCurrency(symbol: "INR", countryCode: "ind", fullName: "Indian Rupee", emoji: "🇮🇳");
|
||||
static const irr =
|
||||
FiatCurrency(symbol: "IRR", countryCode: "irn", fullName: "Iranian Rial", emoji: "🇮🇷");
|
||||
static const isk = FiatCurrency(
|
||||
symbol: "ISK",
|
||||
countryCode: "isl",
|
||||
fullName: "Icelandic Krona Króna",
|
||||
emoji: "🇮🇸",
|
||||
);
|
||||
static const jpy =
|
||||
FiatCurrency(symbol: "JPY", countryCode: "jpn", fullName: "Japanese Yen", emoji: "🇯🇵");
|
||||
static const krw =
|
||||
FiatCurrency(symbol: "KRW", countryCode: "kor", fullName: "South Korean Won", emoji: "🇰🇷");
|
||||
static const mad =
|
||||
FiatCurrency(symbol: "MAD", countryCode: "mar", fullName: "Moroccan Dirham", emoji: "🇲🇦");
|
||||
static const mxn =
|
||||
FiatCurrency(symbol: "MXN", countryCode: "mex", fullName: "Mexican Peso", emoji: "🇲🇽");
|
||||
static const myr =
|
||||
FiatCurrency(symbol: "MYR", countryCode: "mys", fullName: "Malaysian Ringgit", emoji: "🇲🇾");
|
||||
static const ngn =
|
||||
FiatCurrency(symbol: "NGN", countryCode: "nga", fullName: "Nigerian Naira", emoji: "🇳🇬");
|
||||
static const nok =
|
||||
FiatCurrency(symbol: "NOK", countryCode: "nor", fullName: "Norwegian Krone", emoji: "🇳🇴");
|
||||
static const nzd = FiatCurrency(
|
||||
symbol: "NZD",
|
||||
countryCode: "nzl",
|
||||
fullName: "New Zealand Dollar",
|
||||
emoji: "🇳🇿",
|
||||
);
|
||||
static const php =
|
||||
FiatCurrency(symbol: "PHP", countryCode: "phl", fullName: "Philippine Peso", emoji: "🇵🇭");
|
||||
static const pkr =
|
||||
FiatCurrency(symbol: "PKR", countryCode: "pak", fullName: "Pakistani Rupee", emoji: "🇵🇰");
|
||||
static const pln = FiatCurrency(
|
||||
symbol: "PLN",
|
||||
countryCode: "pol",
|
||||
fullName: "Poland Zloty złoty",
|
||||
emoji: "🇵🇱",
|
||||
);
|
||||
static const ron =
|
||||
FiatCurrency(symbol: "RON", countryCode: "rou", fullName: "Romanian Leu", emoji: "🇷🇴");
|
||||
static const rub =
|
||||
FiatCurrency(symbol: "RUB", countryCode: "rus", fullName: "Russian Ruble", emoji: "🇷🇺");
|
||||
static const sar =
|
||||
FiatCurrency(symbol: "SAR", countryCode: "sau", fullName: "Saudi Riyal", emoji: "🇸🇦");
|
||||
static const sek =
|
||||
FiatCurrency(symbol: "SEK", countryCode: "swe", fullName: "Swedish Krona", emoji: "🇸🇪");
|
||||
static const sgd =
|
||||
FiatCurrency(symbol: "SGD", countryCode: "sgp", fullName: "Singapore Dollar", emoji: "🇸🇬");
|
||||
static const thb = FiatCurrency(
|
||||
symbol: "THB",
|
||||
countryCode: "tha",
|
||||
fullName: "Thai Baht",
|
||||
emoji: "🇹🇭",
|
||||
);
|
||||
static const twd = FiatCurrency(
|
||||
symbol: "TWD", countryCode: "twn", fullName: "New Thaiwan Dollar", emoji: "🇹🇼");
|
||||
static const uah =
|
||||
FiatCurrency(symbol: "UAH", countryCode: "ukr", fullName: "Ukrainian Hryvnia", emoji: "🇺🇦");
|
||||
static const usd = FiatCurrency(
|
||||
symbol: "USD",
|
||||
countryCode: "usa",
|
||||
fullName: "United States Dollar",
|
||||
emoji: "🇺🇸",
|
||||
);
|
||||
static const vef = FiatCurrency(
|
||||
symbol: "VEF",
|
||||
countryCode: "ven",
|
||||
fullName: "Venezuelan Bolivar Bolívar",
|
||||
emoji: "🇻🇪",
|
||||
);
|
||||
static const vnd = FiatCurrency(
|
||||
symbol: "VND",
|
||||
countryCode: "vnm",
|
||||
fullName: "Vietnamese Dong đồng",
|
||||
emoji: "🇻🇳",
|
||||
);
|
||||
static const zar = FiatCurrency(
|
||||
symbol: "ZAR",
|
||||
countryCode: "saf",
|
||||
fullName: "South African Rand",
|
||||
emoji: "🇿🇦",
|
||||
isoCountryCode: "ZAF",
|
||||
);
|
||||
static const tur =
|
||||
FiatCurrency(symbol: "TRY", countryCode: "tur", fullName: "Turkish Lira", emoji: "🇹🇷");
|
||||
static const kes =
|
||||
FiatCurrency(symbol: "KES", countryCode: "ken", fullName: "Kenyan Shillings", emoji: "🇰🇪");
|
||||
|
||||
static final _all = {
|
||||
FiatCurrency.amd.raw: FiatCurrency.amd,
|
||||
@@ -178,6 +293,8 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
||||
|
||||
static FiatCurrency deserialize({required String raw}) => _all[raw] ?? FiatCurrency.usd;
|
||||
|
||||
static FiatCurrency? tryDeserialize({required String raw}) => _all[raw];
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => other is FiatCurrency && other.raw == raw;
|
||||
|
||||
@@ -192,4 +309,10 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
||||
|
||||
@override
|
||||
String get iconPath => "assets/images/flags/$countryCode.png";
|
||||
|
||||
@override
|
||||
Money parseAmount(String value) => Money.parse(value, this);
|
||||
|
||||
@override
|
||||
Money? tryParseAmount(String value) => Money.tryParse(value, this);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
|
||||
|
||||
abstract class HardwareWalletDevice {
|
||||
String get name;
|
||||
@@ -41,6 +42,21 @@ class BitboxHardwareWalletDevice extends HardwareWalletDevice {
|
||||
HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
|
||||
}
|
||||
|
||||
class TrezorHardwareWalletDevice extends HardwareWalletDevice {
|
||||
final trezor.TrezorDevice device;
|
||||
|
||||
TrezorHardwareWalletDevice(this.device);
|
||||
|
||||
@override
|
||||
String get name => device.name;
|
||||
|
||||
@override
|
||||
HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
|
||||
|
||||
@override
|
||||
HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
|
||||
}
|
||||
|
||||
enum HardwareWalletDeviceType {
|
||||
ledgerBlue,
|
||||
ledgerNanoS,
|
||||
@@ -50,7 +66,12 @@ enum HardwareWalletDeviceType {
|
||||
ledgerStax,
|
||||
ledgerFlex,
|
||||
BitBox02,
|
||||
BitBox02Nova;
|
||||
BitBox02Nova,
|
||||
trezorModelOne,
|
||||
trezorModelT,
|
||||
trezorSafe3,
|
||||
trezorSafe5,
|
||||
trezorSafe7;
|
||||
}
|
||||
|
||||
enum HardwareWalletConnectionType {
|
||||
@@ -60,7 +81,7 @@ enum HardwareWalletConnectionType {
|
||||
qr;
|
||||
}
|
||||
|
||||
extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
|
||||
extension LedgerToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
|
||||
HardwareWalletDeviceType toGeneric() {
|
||||
switch (this) {
|
||||
case ledger.LedgerDeviceType.blue:
|
||||
@@ -81,7 +102,24 @@ extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
|
||||
}
|
||||
}
|
||||
|
||||
extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
|
||||
extension TrezorToGenericHardwareWalletDeviceType on trezor.TrezorDeviceType {
|
||||
HardwareWalletDeviceType toGeneric() {
|
||||
switch (this) {
|
||||
case trezor.TrezorDeviceType.modelOne:
|
||||
return HardwareWalletDeviceType.trezorModelOne;
|
||||
case trezor.TrezorDeviceType.modelT:
|
||||
return HardwareWalletDeviceType.trezorModelT;
|
||||
case trezor.TrezorDeviceType.safe3:
|
||||
return HardwareWalletDeviceType.trezorSafe3;
|
||||
case trezor.TrezorDeviceType.safe5:
|
||||
return HardwareWalletDeviceType.trezorSafe5;
|
||||
case trezor.TrezorDeviceType.safe7:
|
||||
return HardwareWalletDeviceType.trezorSafe7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LedgerToGenericHardwareWalletConnectionType on ledger.ConnectionType {
|
||||
HardwareWalletConnectionType toGeneric() {
|
||||
switch (this) {
|
||||
case ledger.ConnectionType.usb:
|
||||
@@ -91,3 +129,14 @@ extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TrezorToGenericHardwareWalletConnectionType on trezor.ConnectionType {
|
||||
HardwareWalletConnectionType toGeneric() {
|
||||
switch (this) {
|
||||
case trezor.ConnectionType.usb:
|
||||
return HardwareWalletConnectionType.usb;
|
||||
case trezor.ConnectionType.ble:
|
||||
return HardwareWalletConnectionType.ble;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import 'package:hash_wallet/generated/locales.dart';
|
||||
import 'package:devicelocale/devicelocale.dart';
|
||||
import "package:flutter/widgets.dart";
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
// Flutter's Material delegates match on the language subtag, so "pt_BR" must be split into two
|
||||
Locale localeFromLanguageCode(String code) {
|
||||
final parts = code.split("_");
|
||||
|
||||
return parts.length == 2 ? Locale(parts[0], parts[1]) : Locale(code);
|
||||
}
|
||||
|
||||
class LanguageService {
|
||||
static const Map<String, String> supportedLocales = {
|
||||
'en': 'English',
|
||||
@@ -20,7 +28,6 @@ class LanguageService {
|
||||
'ru': 'Русский (Russian)',
|
||||
'uk': 'Українська (Ukrainian)',
|
||||
'zh': '简体中文 (Chinese Simplified)',
|
||||
'zh_TW': '繁體中文 (Chinese Traditional)',
|
||||
'hr': 'Hrvatski (Croatian)',
|
||||
'it': 'Italiano (Italian)',
|
||||
'th': 'ภาษาไทย (Thai)',
|
||||
@@ -55,7 +62,6 @@ class LanguageService {
|
||||
'ru': 'rus',
|
||||
'uk': 'ukr',
|
||||
'zh': 'chn',
|
||||
'zh_TW': 'twn',
|
||||
'hr': 'hrv',
|
||||
'it': 'ita',
|
||||
'th': 'tha',
|
||||
@@ -87,12 +93,16 @@ class LanguageService {
|
||||
|
||||
static Future<String> localeDetection() async {
|
||||
try {
|
||||
var locale = await Devicelocale.currentLocale ?? '';
|
||||
locale = Intl.shortLocale(locale);
|
||||
|
||||
final locale = Intl.canonicalizedLocale(await Devicelocale.currentLocale ?? "");
|
||||
if (list.keys.contains(locale)) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
final language = Intl.shortLocale(locale);
|
||||
if (list.keys.contains(language)) {
|
||||
return language;
|
||||
}
|
||||
|
||||
return LanguageService.defaultLocale;
|
||||
} catch (_) {
|
||||
return LanguageService.defaultLocale;
|
||||
|
||||
@@ -82,7 +82,7 @@ class MainActions {
|
||||
canShow: (viewModel) => viewModel.hasTradeAction,
|
||||
onTap: (BuildContext context, DashboardViewModel viewModel) async {
|
||||
if (!viewModel.isEnabledTradeAction) return;
|
||||
await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false);
|
||||
await Navigator.of(context).pushNamed(Routes.buySellPage);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hash_wallet/utils/feature_flag.dart';
|
||||
import "package:hash_wallet/generated/i18n.dart";
|
||||
import "package:hash_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class NewMainActions {
|
||||
final Key key;
|
||||
final String Function(BuildContext context) name;
|
||||
final String image;
|
||||
final VoidCallback onTap;
|
||||
final bool Function(DashboardViewModel viewModel)? isEnabled;
|
||||
final bool Function(DashboardViewModel viewModel)? canShow;
|
||||
|
||||
NewMainActions._({
|
||||
required this.key,
|
||||
@@ -18,48 +13,54 @@ class NewMainActions {
|
||||
this.isEnabled,
|
||||
this.canShow,
|
||||
});
|
||||
final Key key;
|
||||
final String Function(BuildContext context) name;
|
||||
final String image;
|
||||
final VoidCallback onTap;
|
||||
final bool Function(DashboardViewModel viewModel)? isEnabled;
|
||||
final bool Function(DashboardViewModel viewModel)? canShow;
|
||||
|
||||
static List<NewMainActions> all = [
|
||||
homeAction,
|
||||
walletsAction,
|
||||
contactsAction,
|
||||
appsAction,
|
||||
//chartsAction,
|
||||
if (FeatureFlag.isChartsEnabled) chartsAction,
|
||||
];
|
||||
|
||||
static NewMainActions homeAction = NewMainActions._(
|
||||
name: (context) => S.of(context).home,
|
||||
image: 'assets/new-ui/navbar/home.svg',
|
||||
key: ValueKey('dashboard_page_home_action_button_key'),
|
||||
image: "assets/new-ui/navbar/home.svg",
|
||||
key: const ValueKey("dashboard_page_home_action_button_key"),
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
static NewMainActions walletsAction = NewMainActions._(
|
||||
name: (context) => S.of(context).wallets,
|
||||
image: 'assets/new-ui/navbar/wallets.svg',
|
||||
key: ValueKey('dashboard_page_wallets_action_button_key'),
|
||||
image: "assets/new-ui/navbar/wallets.svg",
|
||||
key: const ValueKey("dashboard_page_wallets_action_button_key"),
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
static NewMainActions contactsAction = NewMainActions._(
|
||||
name: (context) => S.of(context).contacts,
|
||||
image: 'assets/new-ui/navbar/contacts.svg',
|
||||
key: ValueKey('dashboard_page_contacts_action_button_key'),
|
||||
image: "assets/new-ui/navbar/contacts.svg",
|
||||
key: const ValueKey("dashboard_page_contacts_action_button_key"),
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
static NewMainActions appsAction = NewMainActions._(
|
||||
name: (context) => S.of(context).apps,
|
||||
image: 'assets/new-ui/navbar/apps.svg',
|
||||
key: ValueKey('dashboard_page_apps_action_button_key'),
|
||||
image: "assets/new-ui/navbar/apps.svg",
|
||||
key: const ValueKey("dashboard_page_apps_action_button_key"),
|
||||
canShow: (dashboardVM) => dashboardVM.showApps,
|
||||
onTap: () {},
|
||||
);
|
||||
|
||||
// static NewMainActions chartsAction = NewMainActions._(
|
||||
// name: (context) => 'Charts', //TODO S.of(context).charts,
|
||||
// image: 'assets/new-ui/navbar/charts.svg',
|
||||
// key: ValueKey('dashboard_page_charts_action_button_key'),
|
||||
// onTap: () {},
|
||||
// );
|
||||
static NewMainActions chartsAction = NewMainActions._(
|
||||
name: (context) => S.of(context).charts,
|
||||
image: "assets/new-ui/navbar/charts.svg",
|
||||
key: const ValueKey("dashboard_page_charts_action_button_key"),
|
||||
onTap: () {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ class ListItemCheckbox extends ListItem {
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.subtitle,
|
||||
this.subtitleColor,
|
||||
this.iconPath,
|
||||
this.onTap,
|
||||
this.showArrow = false,
|
||||
@@ -18,5 +19,6 @@ class ListItemCheckbox extends ListItem {
|
||||
final bool value;
|
||||
final String? subtitle;
|
||||
final String? iconPath;
|
||||
final Color? subtitleColor;
|
||||
final bool showArrow;
|
||||
}
|
||||
|
||||
@@ -2,32 +2,47 @@ import 'package:hash_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ListItemRegularRow extends ListItem {
|
||||
const ListItemRegularRow(
|
||||
{required super.keyValue,
|
||||
required super.label,
|
||||
this.subtitle,
|
||||
this.trailingText,
|
||||
this.iconPath,
|
||||
this.onTap,
|
||||
this.trailingIconPath,
|
||||
this.showArrow = true,
|
||||
this.bottomWidget,
|
||||
this.trailingWidget,
|
||||
this.truncateTrailingText = false,
|
||||
this.foregroundColor,
|
||||
this.trailingIconSize,
|
||||
this.copyableText});
|
||||
const ListItemRegularRow({
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.subtitle,
|
||||
this.trailingText,
|
||||
this.iconPath,
|
||||
this.badgeIconPath,
|
||||
this.onTap,
|
||||
this.trailingIconPath,
|
||||
this.showArrow = true,
|
||||
this.bottomWidget,
|
||||
this.trailingWidget,
|
||||
this.truncateTrailingText = false,
|
||||
this.foregroundColor,
|
||||
this.trailingIconSize,
|
||||
this.copyableText,
|
||||
this.leadingIconErrorWidget,
|
||||
this.leadingIconSize,
|
||||
this.badgeIconSize,
|
||||
this.iconColor,
|
||||
this.secondaryLabel,
|
||||
this.subtitleColor,
|
||||
});
|
||||
|
||||
final String? subtitle;
|
||||
final String? trailingText;
|
||||
final String? iconPath;
|
||||
final String? trailingIconPath;
|
||||
final String? badgeIconPath;
|
||||
final String? copyableText;
|
||||
final VoidCallback? onTap;
|
||||
final String? secondaryLabel;
|
||||
final bool showArrow;
|
||||
final Widget? bottomWidget;
|
||||
final Widget? trailingWidget;
|
||||
final bool truncateTrailingText;
|
||||
final Color? subtitleColor;
|
||||
final Color? foregroundColor;
|
||||
final double? trailingIconSize;
|
||||
final Widget? leadingIconErrorWidget;
|
||||
final double? leadingIconSize;
|
||||
final double? badgeIconSize;
|
||||
final Color? iconColor;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,16 @@ class ListItemToggle extends ListItem {
|
||||
const ListItemToggle({
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.subtitle,
|
||||
this.iconPath,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.leadingEndWidget,
|
||||
});
|
||||
|
||||
final String? subtitle;
|
||||
final String? iconPath;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final Widget? leadingEndWidget;
|
||||
final bool value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:hash_wallet/core/utilities.dart';
|
||||
import 'package:hash_wallet/entities/preferences_key.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/node_list.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
const Map<WalletType, String> nodePreferenceKeys = {
|
||||
WalletType.monero: PreferencesKey.currentNodeIdKey,
|
||||
WalletType.bitcoin: PreferencesKey.currentBitcoinElectrumSererIdKey,
|
||||
WalletType.litecoin: PreferencesKey.currentLitecoinElectrumSererIdKey,
|
||||
WalletType.ethereum: PreferencesKey.currentEthereumNodeIdKey,
|
||||
WalletType.polygon: PreferencesKey.currentPolygonNodeIdKey,
|
||||
WalletType.base: PreferencesKey.currentBaseNodeIdKey,
|
||||
WalletType.arbitrum: PreferencesKey.currentArbitrumNodeIdKey,
|
||||
WalletType.bsc: PreferencesKey.currentBscNodeIdKey,
|
||||
WalletType.nano: PreferencesKey.currentNanoNodeIdKey,
|
||||
WalletType.bitcoinCash: PreferencesKey.currentBitcoinCashNodeIdKey,
|
||||
WalletType.dogecoin: PreferencesKey.currentDogecoinNodeIdKey,
|
||||
WalletType.wownero: PreferencesKey.currentWowneroNodeIdKey,
|
||||
};
|
||||
|
||||
const Map<WalletType, String> powNodePreferenceKeys = {
|
||||
WalletType.nano: PreferencesKey.currentNanoPowNodeIdKey
|
||||
};
|
||||
|
||||
Future<void> checkNodeForWalletType(SharedPreferences sharedPreferences,
|
||||
SettingsStore settingsStore, WalletType type, bool isPow) async {
|
||||
final preferenceKey = isPow ? powNodePreferenceKeys[type] : nodePreferenceKeys[type];
|
||||
|
||||
if (preferenceKey == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentId = await sharedPreferences.getInt(preferenceKey);
|
||||
|
||||
final nodes =
|
||||
isPow ? await Node.getAllForWalletTypePow(type) : await Node.getAllForWalletType(type);
|
||||
final currentNode = nodes.firstWhereOrNull((item) => item.id == currentId);
|
||||
|
||||
if (currentNode == null) {
|
||||
final newNode = (isPow
|
||||
? await Node.getDefaultPowForWalletType(type)
|
||||
: await Node.getDefaultForWalletType(type)) ??
|
||||
await getDefaultNodeFromFiles(type, isPow: isPow);
|
||||
final newId = await newNode.save();
|
||||
sharedPreferences.setInt(preferenceKey, newId);
|
||||
settingsStore.nodes[type] = newNode;
|
||||
} else {
|
||||
settingsStore.nodes[type] = currentNode;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkCurrentNodes(
|
||||
SharedPreferences sharedPreferences, SettingsStore settingsStore) async {
|
||||
for (final walletType in nodePreferenceKeys.keys) {
|
||||
await checkNodeForWalletType(sharedPreferences, settingsStore, walletType, false);
|
||||
}
|
||||
|
||||
for (final walletType in powNodePreferenceKeys.keys) {
|
||||
await checkNodeForWalletType(sharedPreferences, settingsStore, walletType, true);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +1 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import "package:yaml/yaml.dart";
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
Future<List<Node>> loadDefaultNodes(WalletType type) async {
|
||||
String path;
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
path = 'assets/node_list.yml';
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
path = 'assets/bitcoin_electrum_server_list.yml';
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
path = 'assets/litecoin_electrum_server_list.yml';
|
||||
break;
|
||||
// TODO: (refactoring) each wallet would have its path, so `wallet.nodePath` would be decided based on chain id in Evm wallet
|
||||
case WalletType.ethereum:
|
||||
path = 'assets/ethereum_server_list.yml';
|
||||
break;
|
||||
case WalletType.nano:
|
||||
path = 'assets/nano_node_list.yml';
|
||||
break;
|
||||
case WalletType.bitcoinCash:
|
||||
path = 'assets/bitcoin_cash_electrum_server_list.yml';
|
||||
break;
|
||||
case WalletType.polygon:
|
||||
path = 'assets/polygon_node_list.yml';
|
||||
break;
|
||||
case WalletType.wownero:
|
||||
path = 'assets/wownero_node_list.yml';
|
||||
break;
|
||||
case WalletType.dogecoin:
|
||||
path = 'assets/dogecoin_electrum_server_list.yml';
|
||||
break;
|
||||
case WalletType.base:
|
||||
path = 'assets/base_node_list.yml';
|
||||
break;
|
||||
case WalletType.arbitrum:
|
||||
path = 'assets/arbitrum_node_list.yml';
|
||||
break;
|
||||
case WalletType.bsc:
|
||||
path = 'assets/bsc_node_list.yml';
|
||||
break;
|
||||
case WalletType.banano:
|
||||
case WalletType.none:
|
||||
path = '';
|
||||
break;
|
||||
}
|
||||
|
||||
final nodesRaw = await rootBundle.loadString(path);
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = type;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultNanoPowNodes() async {
|
||||
final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml');
|
||||
final loadedPowNodes = loadYaml(powNodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedPowNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.nano;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<void> resetToDefault(Box<Node> nodeSource) async {
|
||||
final wowneroNodes = await loadDefaultNodes(WalletType.wownero);
|
||||
final moneroNodes = await loadDefaultNodes(WalletType.monero);
|
||||
final bitcoinElectrumServerList = await loadDefaultNodes(WalletType.bitcoin);
|
||||
final litecoinElectrumServerList = await loadDefaultNodes(WalletType.litecoin);
|
||||
final bitcoinCashElectrumServerList = await loadDefaultNodes(WalletType.bitcoinCash);
|
||||
final ethereumNodes = await loadDefaultNodes(WalletType.ethereum);
|
||||
final nanoNodes = await loadDefaultNodes(WalletType.nano);
|
||||
final polygonNodes = await loadDefaultNodes(WalletType.polygon);
|
||||
final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin);
|
||||
final baseNodes = await loadDefaultNodes(WalletType.base);
|
||||
final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum);
|
||||
final bscNodes = await loadDefaultNodes(WalletType.bsc);
|
||||
|
||||
final nodes = wowneroNodes +
|
||||
moneroNodes +
|
||||
bitcoinElectrumServerList +
|
||||
litecoinElectrumServerList +
|
||||
ethereumNodes +
|
||||
bitcoinCashElectrumServerList +
|
||||
nanoNodes +
|
||||
polygonNodes +
|
||||
dogecoinElectrumServerList +
|
||||
baseNodes +
|
||||
arbitrumNodes +
|
||||
bscNodes;
|
||||
|
||||
await nodeSource.clear();
|
||||
await nodeSource.addAll(nodes);
|
||||
}
|
||||
|
||||
Future<void> resetPowToDefault(Box<Node> powNodeSource) async {
|
||||
final nanoPowNodes = await loadDefaultNanoPowNodes();
|
||||
final nodes = nanoPowNodes;
|
||||
await powNodeSource.clear();
|
||||
await powNodeSource.addAll(nodes);
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
|
||||
class OpenaliasRecord {
|
||||
OpenaliasRecord({
|
||||
required this.address,
|
||||
required this.name,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String address;
|
||||
final String description;
|
||||
|
||||
static String formatDomainName(String name) {
|
||||
String formattedName = name;
|
||||
|
||||
if (name.contains("@")) {
|
||||
formattedName = name.replaceAll("@", ".");
|
||||
}
|
||||
|
||||
return formattedName;
|
||||
}
|
||||
|
||||
static Future<List<RRecord>?> lookupOpenAliasRecord(String name) async {
|
||||
try {
|
||||
final txtRecord = await DnsUtils.lookupRecord(name, RRecordType.TXT, dnssec: true);
|
||||
|
||||
return txtRecord;
|
||||
} catch (e) {
|
||||
printV("${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static OpenaliasRecord fetchAddressAndName({
|
||||
required String formattedName,
|
||||
required String ticker,
|
||||
required List<RRecord> txtRecord,
|
||||
}) {
|
||||
String address = '';
|
||||
String name = formattedName;
|
||||
String note = '';
|
||||
|
||||
for (RRecord element in txtRecord) {
|
||||
String record = element.data;
|
||||
|
||||
if (record.contains("oa1:$ticker") && record.contains("recipient_address")) {
|
||||
record = record.replaceAll('\"', "");
|
||||
|
||||
final dataList = record.split(";");
|
||||
|
||||
address = dataList
|
||||
.where((item) => (item.contains("recipient_address")))
|
||||
.toString()
|
||||
.replaceAll("oa1:$ticker recipient_address=", "")
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
final recipientName = dataList
|
||||
.where((item) => (item.contains("recipient_name")))
|
||||
.toString()
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
if (recipientName.isNotEmpty) {
|
||||
name = recipientName.replaceAll("recipient_name=", "");
|
||||
}
|
||||
|
||||
final description = dataList
|
||||
.where((item) => (item.contains("tx_description")))
|
||||
.toString()
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
if (description.isNotEmpty) {
|
||||
note = description.replaceAll("tx_description=", "");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return OpenaliasRecord(address: address, name: name, description: note);
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:hash_wallet/entities/ens_record.dart';
|
||||
import 'package:hash_wallet/entities/lnurlpay_record.dart';
|
||||
import 'package:hash_wallet/entities/openalias_record.dart';
|
||||
import 'package:hash_wallet/entities/parsed_address.dart';
|
||||
import 'package:hash_wallet/entities/wellknown_record.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/crypto_currency.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/cupertino.dart';
|
||||
|
||||
import 'bip_353_record.dart';
|
||||
|
||||
/// Resolves human-readable names typed into the address field.
|
||||
///
|
||||
/// Privacy contract: every lookup here either queries DNS (OpenAlias,
|
||||
/// BIP-353), the domain the user explicitly typed (.well-known, LNURL-pay),
|
||||
/// or the wallet's own configured RPC node (ENS). Resolvers that shipped the
|
||||
/// typed input to unrelated third-party services (Twitter, Mastodon, Nostr,
|
||||
/// Yat, Unstoppable Domains, FIO, ThorChain names, zcash.me, Zano aliases)
|
||||
/// were removed deliberately — do not reintroduce one without updating
|
||||
/// PRIVACY.md and gating it behind an off-by-default setting.
|
||||
class AddressResolver {
|
||||
AddressResolver({required this.wallet, required this.settingsStore}) : walletType = wallet.type;
|
||||
|
||||
final WalletType walletType;
|
||||
final WalletBase wallet;
|
||||
final SettingsStore settingsStore;
|
||||
|
||||
static String? extractAddressByType(
|
||||
{required String raw,
|
||||
required CryptoCurrency type,
|
||||
bool requireSurroundingWhitespaces = true}) {
|
||||
var addressPattern = AddressValidator.getAddressFromStringPattern(type);
|
||||
|
||||
if (addressPattern == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (requireSurroundingWhitespaces) addressPattern = "$BEFORE_REGEX$addressPattern$AFTER_REGEX";
|
||||
|
||||
final match = RegExp(addressPattern, multiLine: true).firstMatch(raw);
|
||||
return match?.group(0)?.replaceAllMapped(RegExp('[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'),
|
||||
(Match match) {
|
||||
String group = match.group(0)!;
|
||||
if (group.startsWith('bitcoincash:') ||
|
||||
group.startsWith('nano_') ||
|
||||
group.startsWith('ban_')) {
|
||||
return group;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
bool isEmailFormat(String address) {
|
||||
final RegExp emailRegex = RegExp(
|
||||
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
|
||||
caseSensitive: false,
|
||||
);
|
||||
return emailRegex.hasMatch(address);
|
||||
}
|
||||
|
||||
Future<ParsedAddress> resolve(BuildContext context, String text, CryptoCurrency currency) async {
|
||||
final ticker = currency.title;
|
||||
try {
|
||||
// user@domain identifiers: resolved against the domain the user typed.
|
||||
if (text.contains('.') && text.contains('@')) {
|
||||
if (settingsStore.lookupsWellKnown) {
|
||||
final record =
|
||||
await WellKnownRecord.fetchAddressAndName(formattedName: text, currency: currency);
|
||||
if (record != null) {
|
||||
return ParsedAddress.fetchWellKnownAddress(address: record.address, name: text);
|
||||
}
|
||||
}
|
||||
|
||||
if (walletType == WalletType.bitcoin) {
|
||||
final record =
|
||||
await LNUrlPayRecord.fetchAddressAndName(formattedName: text, currency: currency);
|
||||
if (record != null) {
|
||||
return ParsedAddress.fetchLNUrlPayAddress(address: record.address, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final formattedName = OpenaliasRecord.formatDomainName(text);
|
||||
final domainParts = formattedName.split('.');
|
||||
final name = domainParts.last;
|
||||
|
||||
if (domainParts.length <= 1 || domainParts.first.isEmpty || name.isEmpty) {
|
||||
return ParsedAddress(addresses: [text]);
|
||||
}
|
||||
|
||||
final bip353AddressMap = await Bip353Record.fetchUriByCryptoCurrency(text, ticker);
|
||||
|
||||
if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) {
|
||||
final chosenAddress =
|
||||
await Bip353Record.pickBip353AddressChoice(context, text, bip353AddressMap);
|
||||
if (chosenAddress != null) {
|
||||
try {
|
||||
final dnsProof = await Bip353Record.fetchDnsProof(text);
|
||||
return ParsedAddress.fetchBip353AddressAddress(
|
||||
address: chosenAddress, name: text, dnsProof: dnsProof);
|
||||
} catch (e) {
|
||||
printV('Bip353Record.fetchBip353AddressAddress error: $e');
|
||||
return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text.endsWith(".eth")) {
|
||||
if (settingsStore.lookupsENS) {
|
||||
final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet);
|
||||
if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") {
|
||||
return ParsedAddress.fetchEnsAddress(name: text, address: address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (formattedName.contains(".")) {
|
||||
if (settingsStore.lookupsOpenAlias) {
|
||||
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
|
||||
|
||||
if (txtRecord != null) {
|
||||
final record = await OpenaliasRecord.fetchAddressAndName(
|
||||
formattedName: formattedName, ticker: ticker.toLowerCase(), txtRecord: txtRecord);
|
||||
return ParsedAddress.fetchOpenAliasAddress(record: record, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
}
|
||||
|
||||
return ParsedAddress(addresses: [text]);
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
import 'package:hash_wallet/entities/openalias_record.dart';
|
||||
import 'package:hash_wallet/entities/yat_record.dart';
|
||||
|
||||
enum ParseFrom {
|
||||
unstoppableDomains,
|
||||
openAlias,
|
||||
yatRecord,
|
||||
fio,
|
||||
notParsed,
|
||||
twitter,
|
||||
ens,
|
||||
contact,
|
||||
mastodon,
|
||||
nostr,
|
||||
thorChain,
|
||||
wellKnown,
|
||||
zanoAlias,
|
||||
zcashAddress,
|
||||
bip353,
|
||||
lnurlpay,
|
||||
}
|
||||
|
||||
class ParsedAddress {
|
||||
ParsedAddress({
|
||||
required this.addresses,
|
||||
this.name = '',
|
||||
this.description = '',
|
||||
this.profileImageUrl = '',
|
||||
this.profileName = '',
|
||||
this.parseFrom = ParseFrom.notParsed,
|
||||
this.bip353DnsProof,
|
||||
});
|
||||
|
||||
factory ParsedAddress.fetchEmojiAddress({
|
||||
List<YatRecord>? addresses,
|
||||
required String name,
|
||||
}) {
|
||||
if (addresses?.isEmpty ?? true) {
|
||||
return ParsedAddress(addresses: [name], parseFrom: ParseFrom.yatRecord);
|
||||
}
|
||||
return ParsedAddress(
|
||||
addresses: addresses!.map((e) => e.address).toList(),
|
||||
name: name,
|
||||
parseFrom: ParseFrom.yatRecord,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchUnstoppableDomainAddress({
|
||||
String? address,
|
||||
required String name,
|
||||
}) {
|
||||
if (address?.isEmpty ?? true) {
|
||||
return ParsedAddress(addresses: [name]);
|
||||
}
|
||||
return ParsedAddress(
|
||||
addresses: [address!],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.unstoppableDomains,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchBip353AddressAddress({
|
||||
required String address,
|
||||
required String name,
|
||||
String? dnsProof,
|
||||
}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.bip353,
|
||||
bip353DnsProof: dnsProof,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchOpenAliasAddress(
|
||||
{required OpenaliasRecord record, required String name}) {
|
||||
if (record.address.isEmpty) {
|
||||
return ParsedAddress(addresses: [name]);
|
||||
}
|
||||
return ParsedAddress(
|
||||
addresses: [record.address],
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
parseFrom: ParseFrom.openAlias,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchFioAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.fio,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchTwitterAddress(
|
||||
{required String address,
|
||||
required String name,
|
||||
required String profileImageUrl,
|
||||
required String profileName,
|
||||
String? description}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
description: description ?? '',
|
||||
profileImageUrl: profileImageUrl,
|
||||
profileName: profileName,
|
||||
parseFrom: ParseFrom.twitter,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchMastodonAddress(
|
||||
{required String address,
|
||||
required String name,
|
||||
required String profileImageUrl,
|
||||
required String profileName}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.mastodon,
|
||||
profileImageUrl: profileImageUrl,
|
||||
profileName: profileName,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchContactAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.contact,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchEnsAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.ens,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.nostrAddress(
|
||||
{required String address,
|
||||
required String name,
|
||||
required String profileImageUrl,
|
||||
required String profileName}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.nostr,
|
||||
profileImageUrl: profileImageUrl,
|
||||
profileName: profileName,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.thorChainAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.thorChain,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.zanoAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.zanoAlias,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.zcashAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.zcashAddress,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.wellKnown,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchLNUrlPayAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.lnurlpay,
|
||||
);
|
||||
}
|
||||
|
||||
final List<String> addresses;
|
||||
final String name;
|
||||
final String description;
|
||||
final String profileImageUrl;
|
||||
final String profileName;
|
||||
final ParseFrom parseFrom;
|
||||
final String? bip353DnsProof;
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
|
||||
class PreferencesKey {
|
||||
static const currentWalletType = 'current_wallet_type';
|
||||
static const currentWalletName = 'current_wallet_name';
|
||||
static const accountsHomePromoDismissed = 'accounts_home_promo_dismissed';
|
||||
static String educationDismissed(String educationId) => "education_dismissed_$educationId";
|
||||
static const currentNodeIdKey = 'current_node_id';
|
||||
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
|
||||
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
|
||||
@@ -11,6 +15,7 @@ class PreferencesKey {
|
||||
static const currentBaseNodeIdKey = 'current_node_id_base';
|
||||
static const currentArbitrumNodeIdKey = 'current_node_id_arbitrum';
|
||||
static const currentBscNodeIdKey = 'current_node_id_bsc';
|
||||
static const currentRobinhoodNodeIdKey = "current_node_id_robinhood";
|
||||
static const currentNanoNodeIdKey = 'current_node_id_nano';
|
||||
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
|
||||
static const currentDecredNodeIdKey = 'current_node_id_decred';
|
||||
@@ -63,7 +68,8 @@ class PreferencesKey {
|
||||
static const zanoTransactionPriority = 'current_fee_priority_zano';
|
||||
static const wowneroTransactionPriority = 'current_fee_priority_wownero';
|
||||
static const decredTransactionPriority = 'current_fee_priority_decred';
|
||||
static const zcashTransactionPriority = 'current_fee_priority_decred';
|
||||
static const zcashTransactionPriority = 'current_fee_priority_zcash';
|
||||
static const dogecoinTransactionPriority = 'current_fee_priority_dogecoin';
|
||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||
static const mwebCardDisplay = 'mwebCardDisplay';
|
||||
@@ -89,6 +95,7 @@ class PreferencesKey {
|
||||
static const useBaseScan = 'use_base_scan';
|
||||
static const useArbiScan = 'use_arbitrum_scan';
|
||||
static const useBscScan = 'use_bscscan';
|
||||
static const useRobinhoodScan = "use_robinhood_scan";
|
||||
static const useTronGrid = 'use_trongrid';
|
||||
static const useMempoolFeeAPI = 'use_mempool_fee_api';
|
||||
static const evmHiddenChainIds = 'evm_hidden_chain_ids';
|
||||
@@ -101,7 +108,14 @@ class PreferencesKey {
|
||||
static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain';
|
||||
static const lookupsOpenAlias = 'looks_up_open_alias';
|
||||
static const lookupsENS = 'looks_up_ens';
|
||||
static const lookupsZcashNames = 'looks_up_zcash_names';
|
||||
static const lookupsZcashAddress = 'looks_up_zcash_address';
|
||||
static const lookupsWellKnown = 'looks_up_well_known';
|
||||
static const lookupsFio = 'looks_up_fio';
|
||||
static const lookupsNostr = 'looks_up_nostr';
|
||||
static const lookupsThorChain = 'looks_up_thor_chain';
|
||||
static const lookupsBip353 = 'looks_up_bip353';
|
||||
static const lookupsLNUrl = 'looks_up_lnurl';
|
||||
static const useBlinkProtection = 'use_blink_protection';
|
||||
static const usePayjoin = 'use_payjoin';
|
||||
static const showPayjoinCard = 'show_payjoin_card';
|
||||
@@ -109,7 +123,6 @@ class PreferencesKey {
|
||||
static const showDecredInfoCard = 'show_decred_info_card';
|
||||
static const forceDecentralizedExchanges = 'force_decentralized_exchanges';
|
||||
static const decentralizedExchangesPromptDismissed = 'decentralized_exchanges_prompt_dismissed';
|
||||
|
||||
static String moneroWalletUpdateV1Key(String name) =>
|
||||
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||
|
||||
@@ -138,4 +151,8 @@ class PreferencesKey {
|
||||
static const backgroundImage = 'background_image';
|
||||
static const mwebAdDismissed = "mweb_ad_dismissed";
|
||||
static const balanceHideCounter = "balance_hide_counter";
|
||||
static const zcashMigrationModalViewed = "zcash_migration_modal_viewed";
|
||||
static const showCiBuildOverlay = "show_ci_build_overlay";
|
||||
static String deprecationPopupViewed(WalletType type) =>
|
||||
"deprecation_popup_viewed_${walletTypeToString(type)}";
|
||||
}
|
||||
|
||||
@@ -1,26 +1,40 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/new-ui/pages/scan_page.dart';
|
||||
import 'package:hash_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:hash_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:fast_scanner/fast_scanner.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:ur/ur_decoder.dart';
|
||||
|
||||
var isQrScannerShown = false;
|
||||
|
||||
Future<String?> presentQRScanner(BuildContext context) async {
|
||||
Future<String?> presentQRScanner(BuildContext context,
|
||||
{bool showHelp = false, bool showManualInput = true, bool useModal = false}) async {
|
||||
isQrScannerShown = true;
|
||||
try {
|
||||
final result = await Navigator.of(context).push<String>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return BarcodeScannerSimple();
|
||||
},
|
||||
),
|
||||
);
|
||||
final result = useModal
|
||||
? await CupertinoScaffold.showCupertinoModalBottomSheet<String?>(
|
||||
context: context,
|
||||
builder: (context) => ScanPage(
|
||||
showHelp: showHelp,
|
||||
showManualInput: showManualInput,
|
||||
))
|
||||
: await Navigator.of(context).push<String>(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) {
|
||||
return ScanPage(
|
||||
showHelp: showHelp,
|
||||
showManualInput: showManualInput,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
isQrScannerShown = false;
|
||||
return result;
|
||||
} catch (e) {
|
||||
@@ -55,7 +69,7 @@ class _BarcodeScannerSimpleState extends State<BarcodeScannerSimple> {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: S.of(context).error_dialog_content,
|
||||
buttonText: 'ok',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import "package:hash_wallet/entities/solana_nft_asset_model.dart";
|
||||
import "package:hash_wallet/entities/wallet_nft_response.dart";
|
||||
import "package:cw_core/imported_nft.dart";
|
||||
|
||||
class SavedNFTs {
|
||||
Future<Map<String, SolanaNFTAssetModel>> solanaNFTs(String walletName) async {
|
||||
final rows = await ImportedNFT.getAllForWallet(walletName, ImportedNFT.solanaChain);
|
||||
|
||||
return {
|
||||
for (final row in rows)
|
||||
row.identifier: SolanaNFTAssetModel(
|
||||
address: row.identifier,
|
||||
mint: row.identifier,
|
||||
name: row.name,
|
||||
symbol: row.symbol,
|
||||
description: row.description,
|
||||
imageOriginalUrl: row.imageUrl,
|
||||
isOwned: row.isOwned,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> addSolana(String walletName, SolanaNFTAssetModel asset) async {
|
||||
final mint = asset.mint;
|
||||
|
||||
if (mint == null || mint.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _rowForSolana(walletName, mint, asset).save();
|
||||
}
|
||||
|
||||
Future<void> refreshSolana(String walletName, List<SolanaNFTAssetModel> assets) async {
|
||||
for (final asset in assets) {
|
||||
final mint = asset.mint;
|
||||
|
||||
if (mint == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await ImportedNFT.updateMetadata(_rowForSolana(walletName, mint, asset));
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<NFTAssetModel>> evmNFTs(String walletName, String chain) async {
|
||||
final rows = await ImportedNFT.getAllForWallet(walletName, chain);
|
||||
|
||||
return rows
|
||||
.map(
|
||||
(row) => NFTAssetModel(
|
||||
tokenAddress: _tokenAddressOf(row.identifier),
|
||||
tokenId: _tokenIdOf(row.identifier),
|
||||
name: row.name,
|
||||
symbol: row.symbol,
|
||||
normalizedMetadata: NormalizedMetadata(
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
image: row.imageUrl,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> addEvm(String walletName, String chain, NFTAssetModel asset) async {
|
||||
final identifier = evmIdentifier(asset);
|
||||
|
||||
if (identifier == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ImportedNFT(
|
||||
walletName: walletName,
|
||||
chain: chain,
|
||||
identifier: identifier,
|
||||
name: asset.name,
|
||||
symbol: asset.symbol,
|
||||
description: asset.normalizedMetadata?.description,
|
||||
imageUrl: asset.normalizedMetadata?.imageUrl,
|
||||
).save();
|
||||
}
|
||||
|
||||
Future<void> removeSolana(String walletName, String mint) =>
|
||||
ImportedNFT.deleteOne(walletName, ImportedNFT.solanaChain, mint);
|
||||
|
||||
static String? evmIdentifier(NFTAssetModel asset) {
|
||||
final address = asset.tokenAddress;
|
||||
final tokenId = asset.tokenId;
|
||||
|
||||
if (address == null || address.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "$address:${tokenId ?? ""}";
|
||||
}
|
||||
|
||||
ImportedNFT _rowForSolana(String walletName, String mint, SolanaNFTAssetModel asset) =>
|
||||
ImportedNFT(
|
||||
walletName: walletName,
|
||||
chain: ImportedNFT.solanaChain,
|
||||
identifier: mint,
|
||||
name: asset.name,
|
||||
symbol: asset.symbol,
|
||||
description: asset.description,
|
||||
imageUrl: asset.imageOriginalUrl,
|
||||
isOwned: asset.isOwned,
|
||||
);
|
||||
|
||||
String _tokenAddressOf(String identifier) => identifier.split(":").first;
|
||||
|
||||
String _tokenIdOf(String identifier) {
|
||||
final parts = identifier.split(":");
|
||||
|
||||
return parts.length > 1 ? parts.sublist(1).join(":") : "";
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import "package:cw_core/utils/ipfs_url.dart";
|
||||
import "package:cw_core/utils/nft_text.dart";
|
||||
|
||||
class SolanaNFTAssetModel {
|
||||
String? address;
|
||||
String? mint;
|
||||
@@ -12,46 +15,50 @@ class SolanaNFTAssetModel {
|
||||
Metaplex? metaplex;
|
||||
Collection? collection;
|
||||
Contract? contract;
|
||||
bool? isOwned;
|
||||
|
||||
SolanaNFTAssetModel({
|
||||
this.address,
|
||||
this.mint,
|
||||
this.standard,
|
||||
this.name,
|
||||
this.symbol,
|
||||
this.description,
|
||||
this.imageOriginalUrl,
|
||||
String? name,
|
||||
String? symbol,
|
||||
String? description,
|
||||
String? imageOriginalUrl,
|
||||
this.externalUrl,
|
||||
this.metadataOriginalUrl,
|
||||
this.totalSupply,
|
||||
this.metaplex,
|
||||
this.collection,
|
||||
this.contract,
|
||||
});
|
||||
this.isOwned,
|
||||
}) : name = sanitizeNFTText(name),
|
||||
symbol = sanitizeNFTText(symbol),
|
||||
description = sanitizeNFTText(description),
|
||||
imageOriginalUrl = tryNormalizeIpfsUrl(imageOriginalUrl);
|
||||
|
||||
factory SolanaNFTAssetModel.fromJson(Map<String, dynamic> json) {
|
||||
return SolanaNFTAssetModel(
|
||||
address: json['address'] as String?,
|
||||
mint: json['mint'] as String?,
|
||||
standard: json['standard'] as String?,
|
||||
name: json['name'] as String?,
|
||||
symbol: json['symbol'] as String?,
|
||||
description: json['description'] as String?,
|
||||
imageOriginalUrl: json['imageOriginalUrl'] as String?,
|
||||
externalUrl: json['externalUrl'] as String?,
|
||||
metadataOriginalUrl: json['metadataOriginalUrl'] as String?,
|
||||
totalSupply: json['totalSupply'] as String?,
|
||||
metaplex: json['metaplex'] != null
|
||||
? Metaplex.fromJson(json['metaplex'] as Map<String, dynamic>)
|
||||
: null,
|
||||
collection: json['collection'] != null
|
||||
? Collection.fromJson(json['collection'] as Map<String, dynamic>)
|
||||
: null,
|
||||
contract: json['contract'] != null
|
||||
? Contract.fromJson(json['contract'] as Map<String, dynamic>)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
factory SolanaNFTAssetModel.fromJson(Map<String, dynamic> json) => SolanaNFTAssetModel(
|
||||
address: json["address"] as String?,
|
||||
mint: json["mint"] as String?,
|
||||
standard: json["standard"] as String?,
|
||||
name: json["name"] as String?,
|
||||
symbol: json["symbol"] as String?,
|
||||
description: json["description"] as String?,
|
||||
imageOriginalUrl: json["imageOriginalUrl"] as String?,
|
||||
externalUrl: json["externalUrl"] as String?,
|
||||
metadataOriginalUrl: json["metadataOriginalUrl"] as String?,
|
||||
totalSupply: json["totalSupply"] as String?,
|
||||
metaplex: json["metaplex"] != null
|
||||
? Metaplex.fromJson(json["metaplex"] as Map<String, dynamic>)
|
||||
: null,
|
||||
collection: json["collection"] != null
|
||||
? Collection.fromJson(json["collection"] as Map<String, dynamic>)
|
||||
: null,
|
||||
contract: json["contract"] != null
|
||||
? Contract.fromJson(json["contract"] as Map<String, dynamic>)
|
||||
: null,
|
||||
isOwned: json["isOwned"] as bool?,
|
||||
);
|
||||
}
|
||||
|
||||
class Metaplex {
|
||||
@@ -100,17 +107,15 @@ class Collection {
|
||||
this.metaplexMint,
|
||||
this.sellerFeeBasisPoints});
|
||||
|
||||
factory Collection.fromJson(Map<String, dynamic> json) {
|
||||
return Collection(
|
||||
collectionAddress: json['collectionAddress'] as String?,
|
||||
name: json['name'] as String?,
|
||||
description: json['description'] as String?,
|
||||
imageOriginalUrl: json['imageOriginalUrl'] as String?,
|
||||
externalUrl: json['externalUrl'] as String?,
|
||||
metaplexMint: json['metaplexMint'] as String?,
|
||||
sellerFeeBasisPoints: json['sellerFeeBasisPoints'] as int?,
|
||||
);
|
||||
}
|
||||
factory Collection.fromJson(Map<String, dynamic> json) => Collection(
|
||||
collectionAddress: json["collectionAddress"] as String?,
|
||||
name: sanitizeNFTText(json["name"] as String?),
|
||||
description: sanitizeNFTText(json["description"] as String?),
|
||||
imageOriginalUrl: tryNormalizeIpfsUrl(json["imageOriginalUrl"] as String?),
|
||||
externalUrl: json["externalUrl"] as String?,
|
||||
metaplexMint: json["metaplexMint"] as String?,
|
||||
sellerFeeBasisPoints: json["sellerFeeBasisPoints"] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
class Contract {
|
||||
@@ -123,8 +128,8 @@ class Contract {
|
||||
factory Contract.fromJson(Map<String, dynamic> json) {
|
||||
return Contract(
|
||||
type: json['type'] as String?,
|
||||
name: json['name'] as String?,
|
||||
symbol: json['symbol'] as String?,
|
||||
name: sanitizeNFTText(json["name"] as String?),
|
||||
symbol: sanitizeNFTText(json["symbol"] as String?),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,9 +1,9 @@
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'template.g.dart';
|
||||
part 'template.part.dart';
|
||||
|
||||
@HiveType(typeId: Template.typeId)
|
||||
// @HiveType(typeId: Template.typeId)
|
||||
class Template extends HiveObject {
|
||||
Template(
|
||||
{required this.nameRaw,
|
||||
@@ -18,28 +18,28 @@ class Template extends HiveObject {
|
||||
static const typeId = TEMPLATE_TYPE_ID;
|
||||
static const boxName = 'Template';
|
||||
|
||||
@HiveField(0)
|
||||
// @HiveField(0)
|
||||
String? nameRaw;
|
||||
|
||||
@HiveField(1)
|
||||
// @HiveField(1)
|
||||
String? addressRaw;
|
||||
|
||||
@HiveField(2)
|
||||
// @HiveField(2)
|
||||
String? cryptoCurrencyRaw;
|
||||
|
||||
@HiveField(3)
|
||||
// @HiveField(3)
|
||||
String? amountRaw;
|
||||
|
||||
@HiveField(4)
|
||||
// @HiveField(4)
|
||||
String? fiatCurrencyRaw;
|
||||
|
||||
@HiveField(5)
|
||||
// @HiveField(5)
|
||||
bool? isCurrencySelectedRaw;
|
||||
|
||||
@HiveField(6)
|
||||
// @HiveField(6)
|
||||
String? amountFiatRaw;
|
||||
|
||||
@HiveField(7)
|
||||
// @HiveField(7)
|
||||
List<Template>? additionalRecipientsRaw;
|
||||
|
||||
bool get isCurrencySelected => isCurrencySelectedRaw ?? false;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'template.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class TemplateAdapter extends TypeAdapter<Template> {
|
||||
@override
|
||||
final int typeId = 6;
|
||||
|
||||
@override
|
||||
Template read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return Template(
|
||||
nameRaw: fields[0] as String?,
|
||||
isCurrencySelectedRaw: fields[5] as bool?,
|
||||
addressRaw: fields[1] as String?,
|
||||
cryptoCurrencyRaw: fields[2] as String?,
|
||||
amountRaw: fields[3] as String?,
|
||||
fiatCurrencyRaw: fields[4] as String?,
|
||||
amountFiatRaw: fields[6] as String?,
|
||||
additionalRecipientsRaw: (fields[7] as List?)?.cast<Template>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, Template obj) {
|
||||
writer
|
||||
..writeByte(8)
|
||||
..writeByte(0)
|
||||
..write(obj.nameRaw)
|
||||
..writeByte(1)
|
||||
..write(obj.addressRaw)
|
||||
..writeByte(2)
|
||||
..write(obj.cryptoCurrencyRaw)
|
||||
..writeByte(3)
|
||||
..write(obj.amountRaw)
|
||||
..writeByte(4)
|
||||
..write(obj.fiatCurrencyRaw)
|
||||
..writeByte(5)
|
||||
..write(obj.isCurrencySelectedRaw)
|
||||
..writeByte(6)
|
||||
..write(obj.amountFiatRaw)
|
||||
..writeByte(7)
|
||||
..write(obj.additionalRecipientsRaw);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TemplateAdapter && runtimeType == other.runtimeType && typeId == other.typeId;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'transaction_description.g.dart';
|
||||
part 'transaction_description.part.dart';
|
||||
|
||||
@HiveType(typeId: TransactionDescription.typeId)
|
||||
// @HiveType(typeId: TransactionDescription.typeId)
|
||||
class TransactionDescription extends HiveObject {
|
||||
TransactionDescription(
|
||||
{required this.id, this.recipientAddress, this.transactionNote, this.transactionKey});
|
||||
@@ -12,16 +12,16 @@ class TransactionDescription extends HiveObject {
|
||||
static const boxName = 'TransactionDescriptions';
|
||||
static const boxKey = 'transactionDescriptionsBoxKey';
|
||||
|
||||
@HiveField(0, defaultValue: '')
|
||||
// @HiveField(0, defaultValue: '')
|
||||
String id;
|
||||
|
||||
@HiveField(1)
|
||||
// @HiveField(1)
|
||||
String? recipientAddress;
|
||||
|
||||
@HiveField(2)
|
||||
// @HiveField(2)
|
||||
String? transactionNote;
|
||||
|
||||
@HiveField(3)
|
||||
// @HiveField(3)
|
||||
String? transactionKey;
|
||||
|
||||
String get note => transactionNote ?? '';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'transaction_description.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class TransactionDescriptionAdapter extends TypeAdapter<TransactionDescription> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
TransactionDescription read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return TransactionDescription(
|
||||
id: fields[0] == null ? '' : fields[0] as String,
|
||||
recipientAddress: fields[1] as String?,
|
||||
transactionNote: fields[2] as String?,
|
||||
transactionKey: fields[3] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, TransactionDescription obj) {
|
||||
writer
|
||||
..writeByte(4)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.recipientAddress)
|
||||
..writeByte(2)
|
||||
..write(obj.transactionNote)
|
||||
..writeByte(3)
|
||||
..write(obj.transactionKey);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TransactionDescriptionAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import "package:cw_core/utils/ipfs_url.dart";
|
||||
import "package:cw_core/utils/nft_text.dart";
|
||||
|
||||
class WalletNFTsResponseModel {
|
||||
final int? page;
|
||||
final int? pageSize;
|
||||
@@ -40,8 +43,8 @@ class NFTAssetModel {
|
||||
tokenAddress: json['token_address'] as String?,
|
||||
tokenId: json['token_id'] as String?,
|
||||
contractType: json['contract_type'] as String?,
|
||||
name: json['name'] as String?,
|
||||
symbol: json['symbol'] as String?,
|
||||
name: sanitizeNFTText(json["name"] as String?),
|
||||
symbol: sanitizeNFTText(json["symbol"] as String?),
|
||||
normalizedMetadata: json['normalized_metadata'] != null
|
||||
? new NormalizedMetadata.fromJson(json['normalized_metadata'] as Map<String, dynamic>)
|
||||
: null,
|
||||
@@ -61,33 +64,11 @@ class NormalizedMetadata {
|
||||
|
||||
factory NormalizedMetadata.fromJson(Map<String, dynamic> json) {
|
||||
return NormalizedMetadata(
|
||||
name: json['name'] as String?,
|
||||
description: json['description'] as String?,
|
||||
image: json['image'] as String?,
|
||||
name: sanitizeNFTText(json["name"] as String?),
|
||||
description: sanitizeNFTText(json["description"] as String?),
|
||||
image: json["image"] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
String? get imageUrl {
|
||||
if (image == null) return image;
|
||||
|
||||
if (image!.contains('ipfs.io')) return image;
|
||||
|
||||
if (!image!.contains('ipfs')) return image;
|
||||
|
||||
// IPFS public gateway provided by Cloudflare is https://cloudflare-ipfs.com/ipfs/
|
||||
//
|
||||
// Here is an example of an ipfs image link:
|
||||
//
|
||||
// [ipfs://bafkreia2i2ctfexpovgzfff66wqhbmwwpvqjvozan7ioifzcnq76jharwu]
|
||||
|
||||
//https://ipfs.io/ipfs/QmTRcRXo6cXByjHYHTVxGpag6vpocrG3rxjPC9PxKAArR9/1620.png
|
||||
|
||||
const String ipfsPublicGateway = 'https://cloudflare-ipfs.com/ipfs/';
|
||||
|
||||
final ipfsPath = image?.split('//')[1];
|
||||
|
||||
final imageLink = '$ipfsPublicGateway$ipfsPath';
|
||||
|
||||
return imageLink;
|
||||
}
|
||||
String? get imageUrl => tryNormalizeIpfsUrl(image);
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
class WellKnownRecord {
|
||||
WellKnownRecord({
|
||||
required this.address,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String address;
|
||||
|
||||
static Future<String?> checkWellKnownUsername(String username, CryptoCurrency currency) async {
|
||||
String jsonLocation = "";
|
||||
switch (currency) {
|
||||
case CryptoCurrency.nano:
|
||||
jsonLocation = "nano-currency";
|
||||
break;
|
||||
// TODO: add other currencies
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
// split the string by the @ symbol:
|
||||
try {
|
||||
final List<String> splitStrs = username.split("@");
|
||||
String name = splitStrs.first.toLowerCase();
|
||||
final String domain = splitStrs.last;
|
||||
|
||||
if (splitStrs.length == 3) {
|
||||
// for username like @alice@domain.org instead of alice@domain.org
|
||||
name = splitStrs[1];
|
||||
}
|
||||
|
||||
if (name.isEmpty) {
|
||||
name = "_";
|
||||
}
|
||||
|
||||
// lookup domain/.well-known/nano-currency.json and check if it has a nano address:
|
||||
final response = await ProxyWrapper().get(
|
||||
clearnetUri: Uri.parse("https://$domain/.well-known/$jsonLocation.json?names=$name"),
|
||||
headers: <String, String>{"Accept": "application/json"},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
// Access the first element in the names array and retrieve its address
|
||||
final List<dynamic> names = decoded["names"] as List<dynamic>;
|
||||
for (final dynamic item in names) {
|
||||
if (item["name"].toLowerCase() == name) {
|
||||
return item["address"] as String;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
printV("error checking well-known username: $e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String formatDomainName(String name) {
|
||||
String formattedName = name;
|
||||
|
||||
if (name.contains("@")) {
|
||||
formattedName = name.replaceAll("@", ".");
|
||||
}
|
||||
|
||||
return formattedName;
|
||||
}
|
||||
|
||||
static Future<WellKnownRecord?> fetchAddressAndName({
|
||||
required String formattedName,
|
||||
required CryptoCurrency currency,
|
||||
}) async {
|
||||
String name = formattedName;
|
||||
|
||||
printV("formattedName: $formattedName");
|
||||
|
||||
final address = await checkWellKnownUsername(formattedName, currency);
|
||||
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return WellKnownRecord(address: address, name: name);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
class YatRecord {
|
||||
YatRecord({
|
||||
required this.category,
|
||||
required this.address,
|
||||
});
|
||||
|
||||
YatRecord.fromJson(Map<String, dynamic> json)
|
||||
: address = json['address'] as String,
|
||||
category = json['category'] as String;
|
||||
|
||||
String category;
|
||||
String address;
|
||||
}
|
||||
+103
-75
@@ -105,26 +105,25 @@ class CWEVM extends EVM {
|
||||
TransactionPriority? priority,
|
||||
int? feeRate,
|
||||
bool useBlinkProtection = true,
|
||||
}) {
|
||||
return EVMChainTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as EVMChainTransactionPriority?,
|
||||
currency: currency,
|
||||
feeRate: feeRate,
|
||||
useBlinkProtection: useBlinkProtection,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
EVMChainTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmountMoney,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
memo: out.memo,
|
||||
))
|
||||
.toList(),
|
||||
priority: priority as EVMChainTransactionPriority?,
|
||||
currency: currency,
|
||||
feeRate: feeRate,
|
||||
useBlinkProtection: useBlinkProtection,
|
||||
);
|
||||
|
||||
@override
|
||||
Object createEVMTransactionCredentialsRaw(
|
||||
@@ -144,23 +143,42 @@ class CWEVM extends EVM {
|
||||
}
|
||||
|
||||
@override
|
||||
int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
|
||||
TransactionInfo getTransactionInfo({
|
||||
required String id,
|
||||
required int height,
|
||||
required Money amount,
|
||||
required Money fee,
|
||||
required String tokenSymbol,
|
||||
int exponent = 18,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int confirmations,
|
||||
String? to,
|
||||
String? from,
|
||||
String? evmSignatureName,
|
||||
String? contractAddress,
|
||||
required int chainId,
|
||||
}) =>
|
||||
EVMChainTransactionInfo(
|
||||
id: id,
|
||||
height: height,
|
||||
amount: amount,
|
||||
fee: fee,
|
||||
tokenSymbol: tokenSymbol,
|
||||
exponent: exponent,
|
||||
direction: direction,
|
||||
isPending: isPending,
|
||||
date: date,
|
||||
confirmations: confirmations,
|
||||
to: to,
|
||||
from: from,
|
||||
evmSignatureName: evmSignatureName,
|
||||
contractAddress: contractAddress,
|
||||
chainId: chainId);
|
||||
|
||||
@override
|
||||
double formatterEVMAmountToDouble({
|
||||
TransactionInfo? transaction,
|
||||
BigInt? amount,
|
||||
int exponent = 18,
|
||||
}) {
|
||||
assert(transaction != null || amount != null);
|
||||
|
||||
if (transaction != null) {
|
||||
transaction as EVMChainTransactionInfo;
|
||||
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
|
||||
} else {
|
||||
return (amount!) / BigInt.from(10).pow(exponent);
|
||||
}
|
||||
}
|
||||
int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
|
||||
|
||||
@override
|
||||
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
|
||||
@@ -181,10 +199,13 @@ class CWEVM extends EVM {
|
||||
@override
|
||||
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) {
|
||||
final evmWallet = wallet as EVMChainWallet;
|
||||
final chainName = EVMChainUtils.getDefaultTokenSymbol(evmWallet.selectedChainId).toLowerCase();
|
||||
return evmWallet.getErc20Token(contractAddress, chainName);
|
||||
return evmWallet.getErc20Token(contractAddress, getMoralisChainName(wallet));
|
||||
}
|
||||
|
||||
@override
|
||||
String? getMoralisChainName(WalletBase wallet) =>
|
||||
EVMChainUtils.getMoralisChainName((wallet as EVMChainWallet).selectedChainId);
|
||||
|
||||
@override
|
||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
||||
transaction as EVMChainTransactionInfo;
|
||||
@@ -261,20 +282,16 @@ class CWEVM extends EVM {
|
||||
@override
|
||||
Future<PendingTransaction> createTokenApproval(
|
||||
WalletBase wallet,
|
||||
BigInt amount,
|
||||
Money amount,
|
||||
String spender,
|
||||
CryptoCurrency token,
|
||||
TransactionPriority? priority, {
|
||||
bool useBlinkProtection = true,
|
||||
}) {
|
||||
final evmWallet = wallet as EVMChainWallet;
|
||||
final feeCurrency = EVMChainUtils.getFeeCurrency(evmWallet.selectedChainId);
|
||||
return evmWallet.createApprovalTransaction(
|
||||
amount,
|
||||
spender,
|
||||
token,
|
||||
priority as EVMChainTransactionPriority?,
|
||||
feeCurrency,
|
||||
useBlinkProtection: useBlinkProtection,
|
||||
);
|
||||
}
|
||||
@@ -284,7 +301,7 @@ class CWEVM extends EVM {
|
||||
WalletBase wallet,
|
||||
String to,
|
||||
String dataHex,
|
||||
BigInt valueWei,
|
||||
Money valueWei,
|
||||
TransactionPriority? priority, {
|
||||
bool useBlinkProtection = true,
|
||||
String? sourceTokenAddress,
|
||||
@@ -366,7 +383,7 @@ class CWEVM extends EVM {
|
||||
|
||||
// Chain-specific integrations (only for Ethereum)
|
||||
@override
|
||||
Future<BigInt>? getDEuroSavingsBalance(WalletBase wallet) {
|
||||
Future<Money>? getDEuroSavingsBalance(WalletBase wallet) {
|
||||
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
|
||||
return DEuro(wallet).savingsBalance;
|
||||
}
|
||||
@@ -374,7 +391,15 @@ class CWEVM extends EVM {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BigInt>? getDEuroAccruedInterest(WalletBase wallet) {
|
||||
Future<Money>? getDEuroSavingsV1Balance(WalletBase wallet) {
|
||||
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
|
||||
return DEuro(wallet).savingsBalanceV1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Money>? getDEuroAccruedInterest(WalletBase wallet) {
|
||||
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
|
||||
return DEuro(wallet).accruedInterest;
|
||||
}
|
||||
@@ -415,6 +440,15 @@ class CWEVM extends EVM {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction>? withdrawDEuroSavingV1(
|
||||
WalletBase wallet, TransactionPriority priority) {
|
||||
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
|
||||
return DEuro(wallet).withdrawSavingsV1(priority as EVMChainTransactionPriority);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction>? reinvestDEuroInterest(
|
||||
WalletBase wallet, TransactionPriority priority) {
|
||||
@@ -492,14 +526,6 @@ class CWEVM extends EVM {
|
||||
return _registry.getWalletTypeByChainId(chainId);
|
||||
}
|
||||
|
||||
@override
|
||||
BigInt? getERC20AvailableBalance(Object balance) {
|
||||
if (balance is EVMChainERC20Balance) {
|
||||
return balance.balance;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ChainInfo> getAllChains() {
|
||||
final allChains = _registry.getAllChains();
|
||||
@@ -672,29 +698,18 @@ class CWEVM extends EVM {
|
||||
);
|
||||
}
|
||||
|
||||
Future<({double usdValue, bool hasValidFiatPrice})> _getTokenUsdValueAndFiatCheck(
|
||||
Erc20Token token,
|
||||
BigInt balanceWei,
|
||||
) async {
|
||||
Future<double> _fetchFiatApiPriceForToken(Erc20Token token) async {
|
||||
try {
|
||||
final settingsStore = getIt.get<SettingsStore>();
|
||||
final torOnly = settingsStore.fiatApiMode == FiatApiMode.torOnly;
|
||||
|
||||
final price = await FiatConversionService.fetchPrice(
|
||||
return await FiatConversionService.fetchPrice(
|
||||
crypto: token,
|
||||
fiat: FiatCurrency.usd,
|
||||
torOnly: torOnly,
|
||||
);
|
||||
|
||||
final hasValidFiatPrice = price > 0;
|
||||
|
||||
final decimals = token.decimal;
|
||||
final balance = balanceWei.toDouble() / math.pow(10, decimals);
|
||||
final usdValue = balance * price;
|
||||
|
||||
return (usdValue: usdValue, hasValidFiatPrice: hasValidFiatPrice);
|
||||
} catch (e) {
|
||||
return (usdValue: 0.0, hasValidFiatPrice: false);
|
||||
} catch (_) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,21 +725,34 @@ class CWEVM extends EVM {
|
||||
|
||||
final List<Future<void>> tokenChecks = [];
|
||||
|
||||
final whitelistedContracts =
|
||||
wallet.getDefaultTokenContractAddresses.map((a) => a.toLowerCase()).toSet();
|
||||
|
||||
for (final item in result.newTokens) {
|
||||
tokenChecks.add((() async {
|
||||
final token = item.token;
|
||||
|
||||
final isPropertiesSuspicious = wallet.isTokenPropertiesSuspicious(token);
|
||||
final isWhitelisted = whitelistedContracts.contains(token.contractAddress.toLowerCase());
|
||||
|
||||
final fiatResult = await _getTokenUsdValueAndFiatCheck(
|
||||
token,
|
||||
item.balanceWei,
|
||||
);
|
||||
final isSpam = isPropertiesSuspicious || !fiatResult.hasValidFiatPrice;
|
||||
final moralisPrice = item.moralisUsdPrice;
|
||||
final moralisValue = item.moralisUsdValue ?? 0.0;
|
||||
final hasMoralisPrice = moralisPrice != null && moralisPrice > 0;
|
||||
|
||||
final fiatApiPrice = await _fetchFiatApiPriceForToken(token);
|
||||
final hasFiatApiPrice = fiatApiPrice > 0;
|
||||
|
||||
final isImpersonator =
|
||||
hasFiatApiPrice && !hasMoralisPrice && !isWhitelisted && !item.verifiedContract;
|
||||
|
||||
final isSpam = isPropertiesSuspicious ||
|
||||
token.isPotentialScam ||
|
||||
isImpersonator ||
|
||||
(!hasMoralisPrice && !hasFiatApiPrice);
|
||||
|
||||
token.isPotentialScam = isSpam;
|
||||
|
||||
token.enabled = (fiatResult.usdValue >= _minTokenUsdValue) && !isSpam;
|
||||
token.enabled =
|
||||
hasMoralisPrice && hasFiatApiPrice && (moralisValue >= _minTokenUsdValue) && !isSpam;
|
||||
|
||||
await wallet.addErc20Token(token);
|
||||
})());
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'exchange_template.g.dart';
|
||||
part 'exchange_template.part.dart';
|
||||
|
||||
@HiveType(typeId: ExchangeTemplate.typeId)
|
||||
// @HiveType(typeId: ExchangeTemplate.typeId)
|
||||
class ExchangeTemplate extends HiveObject {
|
||||
ExchangeTemplate(
|
||||
{required this.amountRaw,
|
||||
@@ -18,28 +18,28 @@ class ExchangeTemplate extends HiveObject {
|
||||
static const typeId = EXCHANGE_TEMPLATE_TYPE_ID;
|
||||
static const boxName = 'ExchangeTemplate';
|
||||
|
||||
@HiveField(0)
|
||||
// @HiveField(0)
|
||||
String? amountRaw;
|
||||
|
||||
@HiveField(1)
|
||||
// @HiveField(1)
|
||||
String? depositCurrencyRaw;
|
||||
|
||||
@HiveField(2)
|
||||
// @HiveField(2)
|
||||
String? receiveCurrencyRaw;
|
||||
|
||||
@HiveField(3)
|
||||
// @HiveField(3)
|
||||
String? providerRaw;
|
||||
|
||||
@HiveField(4)
|
||||
// @HiveField(4)
|
||||
String? depositAddressRaw;
|
||||
|
||||
@HiveField(5)
|
||||
// @HiveField(5)
|
||||
String? receiveAddressRaw;
|
||||
|
||||
@HiveField(6)
|
||||
// @HiveField(6)
|
||||
String? depositCurrencyTitleRaw;
|
||||
|
||||
@HiveField(7)
|
||||
// @HiveField(7)
|
||||
String? receiveCurrencyTitleRaw;
|
||||
|
||||
String get amount => amountRaw ?? '';
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'exchange_template.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class ExchangeTemplateAdapter extends TypeAdapter<ExchangeTemplate> {
|
||||
@override
|
||||
final int typeId = 7;
|
||||
|
||||
@override
|
||||
ExchangeTemplate read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return ExchangeTemplate(
|
||||
amountRaw: fields[0] as String?,
|
||||
depositCurrencyRaw: fields[1] as String?,
|
||||
receiveCurrencyRaw: fields[2] as String?,
|
||||
providerRaw: fields[3] as String?,
|
||||
depositAddressRaw: fields[4] as String?,
|
||||
receiveAddressRaw: fields[5] as String?,
|
||||
depositCurrencyTitleRaw: fields[6] as String?,
|
||||
receiveCurrencyTitleRaw: fields[7] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ExchangeTemplate obj) {
|
||||
writer
|
||||
..writeByte(8)
|
||||
..writeByte(0)
|
||||
..write(obj.amountRaw)
|
||||
..writeByte(1)
|
||||
..write(obj.depositCurrencyRaw)
|
||||
..writeByte(2)
|
||||
..write(obj.receiveCurrencyRaw)
|
||||
..writeByte(3)
|
||||
..write(obj.providerRaw)
|
||||
..writeByte(4)
|
||||
..write(obj.depositAddressRaw)
|
||||
..writeByte(5)
|
||||
..write(obj.receiveAddressRaw)
|
||||
..writeByte(6)
|
||||
..write(obj.depositCurrencyTitleRaw)
|
||||
..writeByte(7)
|
||||
..write(obj.receiveCurrencyTitleRaw);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ExchangeTemplateAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@@ -19,6 +19,8 @@ abstract class ExchangeProvider {
|
||||
|
||||
bool get supportsOnionAddress => false;
|
||||
|
||||
bool get supportsMemoOrDestinationTag => true;
|
||||
|
||||
@override
|
||||
String toString() => title;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
static const Map<String, String> kNearDummyAddresses = {
|
||||
// UTXO
|
||||
'LTC': 'ltc1qhdwz74m3wuuhppv2mckagqk9e2e49z5j4kucnv',
|
||||
'BTC': 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080',
|
||||
'BTC': 'bc1qzwdt09dgr5nle2fkv7h5s6axgjqpdyp5g5tumz',
|
||||
'DOGE': 'D9t7rGQ9mE3hJ2z1w8pGQxkGmKjYwYc8pQ',
|
||||
'BCH': 'qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||
|
||||
@@ -50,6 +50,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
'AVAXC': '0x1111111111111111111111111111111111111111',
|
||||
'ARB': '0x1111111111111111111111111111111111111111',
|
||||
'BASE': '0x1111111111111111111111111111111111111111',
|
||||
"ROB": "0x1111111111111111111111111111111111111111",
|
||||
|
||||
// Others
|
||||
'SOL': '11111111111111111111111111111111',
|
||||
@@ -88,6 +89,9 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
@override
|
||||
bool get supportsFixedRate => true;
|
||||
|
||||
@override
|
||||
bool get supportsMemoOrDestinationTag => false;
|
||||
|
||||
@override
|
||||
ExchangeProviderDescription get description => ExchangeProviderDescription.nearIntents;
|
||||
|
||||
@@ -242,6 +246,11 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
final quoteObj = quote['quote'] as Map<String, dynamic>;
|
||||
final depositAddress = quoteObj['depositAddress'] as String;
|
||||
final depositMemo = quoteObj['depositMemo'] as String?;
|
||||
final depositAmount = quoteObj['amountInFormatted'] as String?;
|
||||
|
||||
if (depositAmount == null) {
|
||||
throw Exception('Deposit amount is null in quote response');
|
||||
}
|
||||
|
||||
final quoteRequest = quote['quoteRequest'] as Map<String, dynamic>;
|
||||
final fromAssetId = quoteRequest['originAsset'] as String;
|
||||
@@ -264,8 +273,8 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
final trade = Trade(
|
||||
id: depositAddress,
|
||||
// Using deposit address as trade ID
|
||||
from: from,
|
||||
to: to,
|
||||
from: request.fromCurrency,
|
||||
to: request.toCurrency,
|
||||
provider: description,
|
||||
providerName: title,
|
||||
state: TradeState.created,
|
||||
@@ -273,7 +282,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
inputAddress: depositAddress,
|
||||
payoutAddress: request.toAddress,
|
||||
refundAddress: request.refundAddress,
|
||||
amount: request.fromAmount,
|
||||
amount: depositAmount,
|
||||
receiveAmount: quoteObj['amountOutFormatted']?.toString(),
|
||||
memo: depositMemo,
|
||||
isSendAll: isSendAll,
|
||||
@@ -508,6 +517,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
return switch (tag) {
|
||||
'TRX' => 'tron',
|
||||
'AVAXC' => 'avax',
|
||||
"ROB" => "hood",
|
||||
_ => tag?.toLowerCase(),
|
||||
};
|
||||
}
|
||||
@@ -516,6 +526,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
return switch (blockchain) {
|
||||
'tron' => 'TRX',
|
||||
'avax' => 'AVAXC',
|
||||
"hood" => "ROB",
|
||||
_ => blockchain?.toUpperCase(),
|
||||
};
|
||||
}
|
||||
@@ -526,6 +537,13 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
final symbol = currency.title.toUpperCase();
|
||||
final blockchain = _normalizeTagToNearBlockchain(currency.tag);
|
||||
|
||||
// Use the native Bitcoin asset routed through Omni Bridge.
|
||||
if (currency == CryptoCurrency.btc) {
|
||||
return supported.firstWhereOrNull(
|
||||
(t) => t.assetId == '1cs_v1:btc:native:coin',
|
||||
);
|
||||
}
|
||||
|
||||
// Native asset (no contract)
|
||||
final native = supported.firstWhereOrNull((t) =>
|
||||
t.symbol.toUpperCase() == symbol &&
|
||||
@@ -549,9 +567,14 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
final token = supported.firstWhereOrNull((t) => t.assetId == assetId);
|
||||
|
||||
if (token == null) return null;
|
||||
final title = token.symbol;
|
||||
|
||||
final title = token.symbol.toUpperCase().replaceAll(RegExp(r'\s*\([^)]*\)'), '');
|
||||
|
||||
final normalizedNetwork = _normalizeNearBlockchainToTag(token.blockchain);
|
||||
final tag = normalizedNetwork == title.toUpperCase() ? null : normalizedNetwork;
|
||||
|
||||
final isNativeAsset = assetId.contains(':native:coin');
|
||||
|
||||
final tag = isNativeAsset || normalizedNetwork == title ? null : normalizedNetwork;
|
||||
|
||||
return (title, tag);
|
||||
}
|
||||
@@ -559,7 +582,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
|
||||
String _buildDeadline() {
|
||||
return DateTime.now()
|
||||
.toUtc()
|
||||
.add(const Duration(hours: 2))
|
||||
.add(const Duration(hours: 1))
|
||||
.toIso8601String()
|
||||
.replaceFirst(RegExp(r'\.\d+Z$'), 'Z');
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
|
||||
static final apiKey = isMoneroOnly ? secrets.trocadorMoneroApiKey : secrets.trocadorApiKey;
|
||||
static const clearNetAuthority = 'api.trocador.app';
|
||||
static const onionApiAuthority = clearNetAuthority;
|
||||
// static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
|
||||
static const onionApiAuthority = '65bsisadnxvw4kfz7h7a3jwcyenrhluuj3kd5toslfzxbk5q4m3wy6qd.onion';
|
||||
static const markup = secrets.trocadorExchangeMarkup;
|
||||
static const newRatePath = '/new_rate';
|
||||
static const createTradePath = '/new_trade';
|
||||
@@ -235,6 +234,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
if (!isFixedRateMode) 'amount_from': request.fromAmount,
|
||||
if (isFixedRateMode) 'amount_to': request.toAmount,
|
||||
'address': request.toAddress,
|
||||
if (request.toAddressExtraId.isNotEmpty) 'address_memo': request.toAddressExtraId,
|
||||
'refund': request.refundAddress,
|
||||
'refund_memo': '0',
|
||||
// Hash Bags: route trade-status updates through our Cloudflare Worker
|
||||
@@ -398,6 +398,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
payoutAddress: payoutAddress,
|
||||
isSendAll: isSendAll,
|
||||
extraId: addressProviderMemo,
|
||||
toAddressExtraId: request.toAddressExtraId,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class Trade {
|
||||
this.refundAddress,
|
||||
this.walletId,
|
||||
this.payoutAddress,
|
||||
this.toAddressExtraId,
|
||||
this.password,
|
||||
this.providerId,
|
||||
this.providerName,
|
||||
@@ -86,6 +87,9 @@ class Trade {
|
||||
String? refundAddress;
|
||||
String? walletId;
|
||||
String? payoutAddress;
|
||||
|
||||
// holds the receive address memo or destination tag that was passed for this trade
|
||||
String? toAddressExtraId;
|
||||
String? password;
|
||||
String? providerId;
|
||||
String? providerName;
|
||||
@@ -221,6 +225,7 @@ class Trade {
|
||||
'refundAddress': refundAddress,
|
||||
'walletId': walletId,
|
||||
'payoutAddress': payoutAddress,
|
||||
'toAddressExtraId': toAddressExtraId,
|
||||
'password': password,
|
||||
'providerId': providerId,
|
||||
'providerName': providerName,
|
||||
@@ -264,6 +269,7 @@ class Trade {
|
||||
refundAddress: row['refundAddress'] as String?,
|
||||
walletId: row['walletId'] as String?,
|
||||
payoutAddress: row['payoutAddress'] as String?,
|
||||
toAddressExtraId: row['toAddressExtraId'] as String?,
|
||||
password: row['password'] as String?,
|
||||
providerId: row['providerId'] as String?,
|
||||
providerName: row['providerName'] as String?,
|
||||
|
||||
@@ -8,6 +8,7 @@ class TradeRequest {
|
||||
required this.refundAddress,
|
||||
required this.fromAmount,
|
||||
this.toAmount = '',
|
||||
this.toAddressExtraId = '',
|
||||
this.isFixedRate = false});
|
||||
|
||||
final CryptoCurrency fromCurrency;
|
||||
@@ -16,5 +17,6 @@ class TradeRequest {
|
||||
final String refundAddress;
|
||||
final String fromAmount;
|
||||
final String toAmount;
|
||||
final String toAddressExtraId;
|
||||
final bool isFixedRate;
|
||||
}
|
||||
|
||||
@@ -1036,4 +1036,22 @@ class HaCupertinoLocalizations extends GlobalCupertinoLocalizations {
|
||||
@override
|
||||
// TODO: implement cancelButtonLabel
|
||||
String get cancelButtonLabel => "cancelButtonLabel";
|
||||
|
||||
@override
|
||||
String get collapsedHint => "collapsedHint";
|
||||
|
||||
@override
|
||||
String get expandedHint => "expandedHint";
|
||||
|
||||
@override
|
||||
String get expansionTileCollapsedHint => "expansionTileCollapsedHint";
|
||||
|
||||
@override
|
||||
String get expansionTileCollapsedTapHint => "expansionTileCollapsedTapHint";
|
||||
|
||||
@override
|
||||
String get expansionTileExpandedHint => "expansionTileExpandedHint";
|
||||
|
||||
@override
|
||||
String get expansionTileExpandedTapHint => "expansionTileExpandedTapHint";
|
||||
}
|
||||
|
||||
@@ -1036,4 +1036,22 @@ class YoCupertinoLocalizations extends GlobalCupertinoLocalizations {
|
||||
@override
|
||||
// TODO: implement cancelButtonLabel
|
||||
String get cancelButtonLabel => "cancelButtonLabel";
|
||||
|
||||
@override
|
||||
String get collapsedHint => "collapsedHint";
|
||||
|
||||
@override
|
||||
String get expandedHint => "expandedHint";
|
||||
|
||||
@override
|
||||
String get expansionTileCollapsedHint => "expansionTileCollapsedHint";
|
||||
|
||||
@override
|
||||
String get expansionTileCollapsedTapHint => "expansionTileCollapsedTapHint";
|
||||
|
||||
@override
|
||||
String get expansionTileExpandedHint => "expansionTileExpandedHint";
|
||||
|
||||
@override
|
||||
String get expansionTileExpandedTapHint => "expansionTileExpandedTapHint";
|
||||
}
|
||||
|
||||
+31
-39
@@ -4,6 +4,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:hash_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:hash_wallet/app_scroll_behavior.dart';
|
||||
import "package:hash_wallet/ci_build_overlay.dart";
|
||||
import 'package:hash_wallet/core/auth_service.dart';
|
||||
import 'package:hash_wallet/core/background_sync.dart';
|
||||
import 'package:hash_wallet/core/node_switching_service.dart';
|
||||
@@ -16,12 +17,14 @@ import 'package:hash_wallet/entities/default_settings_migration.dart';
|
||||
import 'package:hash_wallet/entities/demo_mode.dart';
|
||||
import 'package:hash_wallet/entities/get_encryption_key.dart';
|
||||
import 'package:hash_wallet/entities/language_service.dart';
|
||||
import 'package:hash_wallet/entities/node_check.dart';
|
||||
import 'package:hash_wallet/entities/template.dart';
|
||||
import 'package:hash_wallet/entities/transaction_description.dart';
|
||||
import 'package:hash_wallet/exchange/exchange_template.dart';
|
||||
import 'package:hash_wallet/exchange/trade_legacy.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/locales/locale.dart';
|
||||
import "package:hash_wallet/new-ui/widgets/money/money_settings_provider.dart";
|
||||
import 'package:hash_wallet/order/order.dart';
|
||||
import 'package:hash_wallet/reactions/bootstrap.dart';
|
||||
import 'package:hash_wallet/router.dart' as Router;
|
||||
@@ -40,15 +43,16 @@ import 'package:hash_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/db/sqlite.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/erc20_token_legacy.dart' show performErc20TokenHiveMigration;
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/key.dart';
|
||||
import 'package:cw_core/mweb_utxo.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/node_legacy.dart' show performNodeHiveMigration;
|
||||
import 'package:cw_core/payjoin_session.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_core/spl_token_legacy.dart' show performSplTokenHiveMigration;
|
||||
import 'package:cw_core/tron_token_legacy.dart' show performTronTokenHiveMigration;
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_logger/memory_proxy_logger.dart';
|
||||
@@ -61,6 +65,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_daemon/flutter_daemon.dart';
|
||||
import "package:flutter_localizations/flutter_localizations.dart";
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:quick_actions/quick_actions.dart';
|
||||
@@ -180,17 +185,22 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
runApp(App(
|
||||
key: topLevelKey,
|
||||
initialQuickAction: initialQuickAction,
|
||||
quickActionsStream: quickActionsStream.stream));
|
||||
runApp(
|
||||
CiBuildOverlay(
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
child: App(
|
||||
key: topLevelKey,
|
||||
initialQuickAction: initialQuickAction,
|
||||
quickActionsStream: quickActionsStream.stream),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
isAppRunning = true;
|
||||
}, (error, stackTrace) async {
|
||||
if (!isAppRunning) {
|
||||
runApp(
|
||||
TopLevelErrorWidget(error: error, stackTrace: stackTrace),
|
||||
CiBuildOverlay(child: TopLevelErrorWidget(error: error, stackTrace: stackTrace)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -213,10 +223,6 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
CakeHive.registerAdapter(ContactAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(Node.typeId)) {
|
||||
CakeHive.registerAdapter(NodeAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(TransactionDescription.typeId)) {
|
||||
CakeHive.registerAdapter(TransactionDescriptionAdapter());
|
||||
}
|
||||
@@ -257,31 +263,22 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
CakeHive.registerAdapter(PayjoinSessionAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
|
||||
CakeHive.registerAdapter(Erc20TokenAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(SPLToken.typeId)) {
|
||||
CakeHive.registerAdapter(SPLTokenAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(TronToken.typeId)) {
|
||||
CakeHive.registerAdapter(TronTokenAdapter());
|
||||
}
|
||||
await performHiveMigration();
|
||||
await performErc20TokenHiveMigration();
|
||||
await performSplTokenHiveMigration();
|
||||
await performTronTokenHiveMigration();
|
||||
|
||||
final secureStorage = secureStorageShared;
|
||||
final transactionDescriptionsBoxKey =
|
||||
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
|
||||
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
|
||||
final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
|
||||
final nodes = await CakeHive.openBox<Node>(Node.boxName);
|
||||
final powNodes =
|
||||
await CakeHive.openBox<Node>(Node.boxName + "pow"); // must be different from Node.boxName
|
||||
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
|
||||
TransactionDescription.boxName,
|
||||
encryptionKey: transactionDescriptionsBoxKey);
|
||||
await performTradeHiveMigration(secureStorage);
|
||||
await performNodeHiveMigration();
|
||||
await validateBuiltinNodes();
|
||||
|
||||
final orders = await CakeHive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
|
||||
final templates = await CakeHive.openBox<Template>(Template.boxName);
|
||||
@@ -293,8 +290,6 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
await initialSetup(
|
||||
loadWallet: loadWallet,
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
contactSource: contacts,
|
||||
ordersSource: orders,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
||||
@@ -305,15 +300,13 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
secureStorage: secureStorage,
|
||||
payjoinSessionSource: payjoinSessionSource,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 65,
|
||||
initialMigrationVersion: 72,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initialSetup({
|
||||
required bool loadWallet,
|
||||
required SharedPreferences sharedPreferences,
|
||||
required Box<Node> nodes,
|
||||
required Box<Node> powNodes,
|
||||
required Box<Contact> contactSource,
|
||||
required Box<Order> ordersSource,
|
||||
// required FiatConvertationService fiatConvertationService,
|
||||
@@ -327,17 +320,14 @@ Future<void> initialSetup({
|
||||
required int initialMigrationVersion,
|
||||
}) async {
|
||||
LanguageService.loadLocaleList();
|
||||
await GlobalMaterialLocalizations.delegate.load(const Locale("en"));
|
||||
await defaultSettingsMigration(
|
||||
secureStorage: secureStorage,
|
||||
version: initialMigrationVersion,
|
||||
sharedPreferences: sharedPreferences,
|
||||
contactSource: contactSource,
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
);
|
||||
await setup(
|
||||
nodeSource: nodes,
|
||||
powNodeSource: powNodes,
|
||||
contactSource: contactSource,
|
||||
ordersSource: ordersSource,
|
||||
templates: templates,
|
||||
@@ -349,11 +339,12 @@ Future<void> initialSetup({
|
||||
navigatorKey: navigatorKey,
|
||||
secureStorage: secureStorage,
|
||||
);
|
||||
final settingsStore = getIt<SettingsStore>();
|
||||
await checkCurrentNodes(sharedPreferences, settingsStore);
|
||||
|
||||
await getIt.get<ResetService>().resetAuthDataOnNewInstall(sharedPreferences);
|
||||
|
||||
await bootstrapOffline();
|
||||
final settingsStore = getIt<SettingsStore>();
|
||||
if (!settingsStore.currentBuiltinTor) {
|
||||
bootstrapOnline(navigatorKey, loadWallet: loadWallet);
|
||||
}
|
||||
@@ -420,14 +411,15 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||
navigatorKey: navigatorKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
|
||||
child: child!),
|
||||
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
|
||||
child: MoneySettingsProvider(settingsStore: settingsStore, child: child!),
|
||||
),
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeMode,
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
locale: Locale(appStore.settingsStore.languageCode),
|
||||
locale: localeFromLanguageCode(appStore.settingsStore.languageCode),
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
initialRoute: initialRoute,
|
||||
scrollBehavior: AppScrollBehavior(),
|
||||
|
||||
+71
-26
@@ -146,6 +146,10 @@ class CWMonero extends Monero {
|
||||
@override
|
||||
MoneroAccountList getAccountList(Object wallet) => CWMoneroAccountList(wallet);
|
||||
|
||||
@override
|
||||
Money getAccountFullBalance(int accountIndex) =>
|
||||
monero_wallet_api.getFullBalance(accountIndex: accountIndex);
|
||||
|
||||
@override
|
||||
MoneroSubaddressList getSubaddressList(Object wallet) => CWMoneroSubaddressList(wallet);
|
||||
|
||||
@@ -235,10 +239,16 @@ class CWMonero extends Monero {
|
||||
required String name,
|
||||
required String password,
|
||||
required int height,
|
||||
required ledger.LedgerConnection ledgerConnection,
|
||||
required HardwareWalletService hardwareWalletService,
|
||||
required String? passphrase,
|
||||
}) =>
|
||||
MoneroRestoreWalletFromHardwareCredentials(
|
||||
name: name, password: password, height: height, ledgerConnection: ledgerConnection);
|
||||
name: name,
|
||||
password: password,
|
||||
height: height,
|
||||
hardwareWalletService: hardwareWalletService,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
|
||||
@@ -288,6 +298,23 @@ class CWMonero extends Monero {
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getNodeHeight(Object wallet) => (wallet as MoneroWallet).getNodeHeight();
|
||||
|
||||
@override
|
||||
String getLegacySeed(Object wallet, String langName) =>
|
||||
(wallet as MoneroWallet).seedLegacy(langName);
|
||||
|
||||
@override
|
||||
bool isBackgroundSyncRunning(Object wallet) => (wallet as MoneroWallet).isBackgroundSyncRunning;
|
||||
|
||||
@override
|
||||
Future<void> startBackgroundSync(Object wallet) => (wallet as MoneroWallet).startBackgroundSync();
|
||||
|
||||
@override
|
||||
Future<void> stopBackgroundSync(Object wallet, String password) =>
|
||||
(wallet as MoneroWallet).stopBackgroundSync(password);
|
||||
|
||||
@override
|
||||
int? getRestoreHeight(Object wallet) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
@@ -300,14 +327,14 @@ class CWMonero extends Monero {
|
||||
MoneroTransactionCreationCredentials(
|
||||
outputs: outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmountMoney,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
))
|
||||
.toList(),
|
||||
priority: priority as MoneroTransactionPriority);
|
||||
|
||||
@@ -409,16 +436,20 @@ class CWMonero extends Monero {
|
||||
}
|
||||
|
||||
@override
|
||||
bool needExportOutputs(Object wallet, int amount) {
|
||||
bool needExportOutputs(Object wallet, Money amount) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.needExportOutputs(amount);
|
||||
}
|
||||
|
||||
@override
|
||||
void monerocCheck() {
|
||||
checkIfMoneroCIsFine();
|
||||
bool hasUnknownKeyImages(Object wallet) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.hasUnknownKeyImages();
|
||||
}
|
||||
|
||||
@override
|
||||
void monerocCheck() => checkIfMoneroCIsFine();
|
||||
|
||||
@override
|
||||
Future<void> setLedgerConnection(Object wallet, ledger.LedgerConnection connection) async {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
@@ -426,29 +457,43 @@ class CWMonero extends Monero {
|
||||
}
|
||||
|
||||
@override
|
||||
void resetLedgerConnection() {
|
||||
disableLedgerExchange();
|
||||
}
|
||||
void resetLedgerConnection() => disableLedgerExchange();
|
||||
|
||||
@override
|
||||
void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
|
||||
gLedger = connection;
|
||||
}
|
||||
void setGlobalLedgerConnection(ledger.LedgerConnection connection) => gLedger = connection;
|
||||
|
||||
@override
|
||||
bool hasGlobalLedgerConnection() => gLedger != null;
|
||||
|
||||
@override
|
||||
String? getLastLedgerCommand() => latestLedgerCommand;
|
||||
|
||||
bool isViewOnly() {
|
||||
return isViewOnlyBySpendKey(null);
|
||||
@override
|
||||
void setHardwareWalletService(Object wallet, HardwareWalletService service) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.trezorService = service as MoneroTrezorService;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, List<int>> debugCallLength() {
|
||||
return monero_wallet_api.debugCallLength();
|
||||
}
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
|
||||
MoneroLedgerService(connection);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> getWalletCacheDebug() {
|
||||
return monero_wallet_api.getWalletCacheDebug();
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client) =>
|
||||
MoneroTrezorService(client);
|
||||
|
||||
@override
|
||||
Future<void> syncTrezor(Object wallet) async {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
|
||||
return moneroWallet.syncTrezor();
|
||||
}
|
||||
|
||||
bool isViewOnly() => isViewOnlyBySpendKey(null);
|
||||
|
||||
@override
|
||||
Map<String, List<int>> debugCallLength() => monero_wallet_api.debugCallLength();
|
||||
|
||||
@override
|
||||
Map<String, dynamic> getWalletCacheDebug() => monero_wallet_api.getWalletCacheDebug();
|
||||
}
|
||||
|
||||
@@ -150,13 +150,12 @@ class CWNano extends Nano {
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
cryptoAmount: out.cryptoAmountMoney,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
@@ -175,11 +174,6 @@ class CWNano extends Nano {
|
||||
return (wallet as NanoWallet).updateTransactions();
|
||||
}
|
||||
|
||||
@override
|
||||
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
|
||||
return (transactionInfo as NanoTransactionInfo).amountRaw;
|
||||
}
|
||||
|
||||
@override
|
||||
String getRepresentative(Object wallet) {
|
||||
return (wallet as NanoWallet).representative;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:hash_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LongPressPopupBuilder extends StatelessWidget {
|
||||
const LongPressPopupBuilder(
|
||||
{super.key, required this.child, required this.popup, this.spacing = 8});
|
||||
|
||||
final Widget child;
|
||||
final Widget popup;
|
||||
final double spacing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
final RenderBox renderBox = context.findRenderObject() as RenderBox;
|
||||
final offset = renderBox.localToGlobal(Offset.zero);
|
||||
final size = renderBox.size;
|
||||
|
||||
showPopUp(
|
||||
context: context,
|
||||
builder: (context) => _buildPopup(context, offset, size),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPopup(BuildContext context, Offset offset, Size size) {
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: offset.dx,
|
||||
top: offset.dy,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: offset.dx * 2,
|
||||
top: offset.dy + size.height + spacing,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: popup,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import "package:hash_wallet/new-ui/model/charts/price_data.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/db/sqlite.dart";
|
||||
import "package:sqflite/sqflite.dart";
|
||||
|
||||
class ChartsAsset {
|
||||
const ChartsAsset({required this.asset, required this.isFavorite});
|
||||
|
||||
factory ChartsAsset.fromJson(Map<String, dynamic> json) => ChartsAsset(
|
||||
asset: currencyFromApiString(json["asset"] as String) as CryptoCurrency,
|
||||
isFavorite: json["isFavorite"] != 0,
|
||||
);
|
||||
|
||||
final CryptoCurrency asset;
|
||||
final bool isFavorite;
|
||||
|
||||
static const tableName = "ChartsAssets";
|
||||
|
||||
Map<String, dynamic> toJson() => {"asset": asset.apiString, "isFavorite": isFavorite ? 1 : 0};
|
||||
|
||||
static Future<List<ChartsAsset>> get() async {
|
||||
final json = await db!.query(tableName);
|
||||
return List.generate(json.length, (index) => ChartsAsset.fromJson(json[index]));
|
||||
}
|
||||
|
||||
Future<void> insert() async {
|
||||
await db!.insert(tableName, toJson(), conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<void> remove() async {
|
||||
await db!.delete(tableName, where: "asset = ?", whereArgs: [asset.apiString]);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user