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:
jwinterm
2026-10-24 23:49:29 -04:00
1406 changed files with 81062 additions and 48972 deletions
-171
View File
@@ -1,171 +0,0 @@
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({
required this.uri,
required this.domain,
});
final String uri;
final String domain;
static const Map<String, String> keyDisplayMap = {
'lno': 'BOLT 12 Offer',
'sp': 'Silent Payment',
'address': 'On-Chain Address',
};
static Future<String?> fetchDnsProof(String bip353Name) async {
if (bip353Name.startsWith('₿')) {
bip353Name = bip353Name.substring(1);
}
final parts = bip353Name.split('@');
if (parts.length != 2) return null;
final userPart = parts[0];
final domainPart = parts[1];
final bip353Domain = '$userPart.user._bitcoin-payment.$domainPart.';
final proof = await Isolate.run(() => DnsProver.getTxtProof(bip353Domain));
return base64.encode(proof);
}
static Future<Map<String, String>?> fetchUriByCryptoCurrency(
String bip353Name, String asset) async {
try {
// 1. Parse the user and domain from "user@domain"
final parts = bip353Name.split('@');
if (parts.length != 2) return null;
String userPart = parts[0];
if (userPart.startsWith('₿')) {
userPart = userPart.substring(1);
}
final domainPart = parts[1];
// 2. Construct the correct subdomain: "user._bitcoin-payment.domain"
final bip353Domain = '$userPart.user._bitcoin-payment.$domainPart';
// 3. Lookup the TXT record with DNSSEC
final txtRecords = await DnsUtils.lookupRecord(
bip353Domain,
RRecordType.TXT,
dnssec: true,
);
final proof = await fetchDnsProof(bip353Name);
if (proof == null) {
throw Exception('DNSSEC proof not found');
}
if (txtRecords == null) return null;
final assetName = CryptoCurrency.fromString(asset).fullName;
if (assetName == null) throw Exception('Invalid asset name');
final formattedAssetName = assetName.toLowerCase().replaceAll(' ', '') + ':';
for (final record in txtRecords) {
final data = record.data.replaceAll('"', '');
if (data.startsWith(formattedAssetName)) {
return _parseAssetUri(data, formattedAssetName);
}
}
} catch (e) {
printV('BIP353Record.fetchBitcoinUri error: $e');
}
return null;
}
static Map<String, String>? _parseAssetUri(String fullUri, String prefix) {
final afterPrefix = fullUri.substring(prefix.length);
if (afterPrefix.isEmpty) return null;
final questionIndex = afterPrefix.indexOf('?');
if (questionIndex == -1) {
return {'address': afterPrefix};
} else {
final addressPart = afterPrefix.substring(0, questionIndex);
final queryPart = afterPrefix.substring(questionIndex + 1);
final result = <String, String>{};
if (addressPart.isNotEmpty) result['address'] = addressPart;
final queryMap = Uri.splitQueryString(queryPart);
result.addAll(queryMap);
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';
}
}
+5 -2
View File
@@ -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';
}
+7 -7
View File
@@ -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);
+50
View File
@@ -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;
}
+189 -314
View File
@@ -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();
}
-52
View File
@@ -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
View File
@@ -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;
}
}
}
+15 -5
View File
@@ -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;
-76
View File
@@ -1,76 +0,0 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/lnurl.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
class LNUrlPayRecord {
LNUrlPayRecord({
required this.address,
required this.name,
});
final String name;
final String address;
static Future<String?> checkWellKnownUsername(String username, CryptoCurrency currency,
{bool allowRethrow = false}) async {
if (![CryptoCurrency.btc, CryptoCurrency.btcln].contains(currency)) 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 = "_";
}
final expectedUrl = "https://$domain/.well-known/lnurlp/$name";
final response = await ProxyWrapper().get(
clearnetUri: Uri.parse(expectedUrl),
headers: <String, String>{"Accept": "application/json"},
);
if (response.statusCode == 200) {
return LNURL.encode(expectedUrl);
}
} catch (e) {
if (allowRethrow) rethrow;
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<LNUrlPayRecord?> fetchAddressAndName({
required String formattedName,
required CryptoCurrency currency,
}) async {
final name = formattedName;
printV("formattedName: $formattedName");
final address = await checkWellKnownUsername(formattedName, currency);
if (address == null) {
return null;
}
return LNUrlPayRecord(address: address, name: name);
}
}
+1 -1
View File
@@ -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);
},
);
}
+25 -24
View File
@@ -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;
}
}
+64
View File
@@ -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);
}
}
-119
View File
@@ -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);
}
-88
View File
@@ -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);
}
}
-138
View File
@@ -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]);
}
}
-203
View File
@@ -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;
}
+19 -2
View File
@@ -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)}";
}
+23 -9
View File
@@ -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();
},
+116
View File
@@ -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(":") : "";
}
}
+46 -41
View File
@@ -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
View File
@@ -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;
+60
View File
@@ -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;
}
+6 -6
View File
@@ -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;
}
+9 -28
View File
@@ -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);
}
-93
View File
@@ -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);
}
}
-13
View File
@@ -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;
}