mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
feat: add support for Zcash names resolution (#3237)
* feat: Add memo support for swap * fix: Error on swap page select receiver bottomsheet when picking receiveing currency that's not a wallet type * feat: exclude providers that do not support memo when receive currency needs it, also show passed memo in confirmation and trade history sheets * fix: overflow for destination tag on swap confirmation * feat: add support for Zcash names resolution * fix conflict because github is shit --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
parent
087c2b9411
commit
baac6eeabc
@@ -9,6 +9,7 @@ import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
|
||||
import 'package:cake_wallet/entities/wellknown_record.dart';
|
||||
import 'package:cake_wallet/entities/zano_alias.dart';
|
||||
import 'package:cake_wallet/entities/zcash_names_record.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/mastodon/mastodon_api.dart';
|
||||
import 'package:cake_wallet/nostr/nostr_api.dart';
|
||||
@@ -458,6 +459,16 @@ class AddressResolver {
|
||||
}
|
||||
}
|
||||
|
||||
final lowerText = text.toLowerCase();
|
||||
if (lowerText.endsWith(".zec") || lowerText.endsWith(".zcash")) {
|
||||
if (settingsStore.lookupsZcashNames) {
|
||||
final address = await ZcashNamesRecord.fetchZcashNamesAddress(text);
|
||||
if (address != null && address.isNotEmpty) {
|
||||
return ParsedAddress.zcashNameAddress(address: address, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text.endsWith(".eth")) {
|
||||
if (settingsStore.lookupsENS) {
|
||||
final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet);
|
||||
|
||||
@@ -16,6 +16,7 @@ enum ParseFrom {
|
||||
wellKnown,
|
||||
zanoAlias,
|
||||
zcashAddress,
|
||||
zcashName,
|
||||
bip353,
|
||||
lnurlpay,
|
||||
}
|
||||
@@ -177,6 +178,14 @@ class ParsedAddress {
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.zcashNameAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.zcashName,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
|
||||
@@ -101,6 +101,7 @@ 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 lookupsWellKnown = 'looks_up_well_known';
|
||||
static const useBlinkProtection = 'use_blink_protection';
|
||||
static const usePayjoin = 'use_payjoin';
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
class ZcashNamesRecord {
|
||||
static final Uri _mainnetEndpoint = Uri.parse('https://main.zcashnames.com');
|
||||
|
||||
static final RegExp _nameRegExp = RegExp(r'^[A-Za-z0-9_-]{1,63}$');
|
||||
|
||||
static Future<String?> fetchZcashNamesAddress(String input) async {
|
||||
final name = _extractName(input);
|
||||
if (name == null) return null;
|
||||
|
||||
try {
|
||||
final response = await ProxyWrapper().post(
|
||||
clearnetUri: _mainnetEndpoint,
|
||||
headers: const {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'jsonrpc': '2.0',
|
||||
'id': 1,
|
||||
'method': 'resolve',
|
||||
'params': {'query': name},
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
printV('ZcashNames: non-200 status ${response.statusCode} for $name');
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(response.body);
|
||||
if (decoded is! Map<String, dynamic>) return null;
|
||||
if (decoded['error'] != null) return null;
|
||||
|
||||
final result = decoded['result'];
|
||||
if (result is! Map<String, dynamic>) return null;
|
||||
|
||||
final address = result['address'];
|
||||
if (address is! String || address.isEmpty) return null;
|
||||
|
||||
if (!_isValidZcashAddress(address)) {
|
||||
printV('ZcashNames: returned address failed validation for $name');
|
||||
return null;
|
||||
}
|
||||
return address;
|
||||
} catch (e) {
|
||||
printV('ZcashNames: lookup failed for $name: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String? _extractName(String input) {
|
||||
final lower = input.toLowerCase().trim();
|
||||
String? raw;
|
||||
if (lower.endsWith('.zcash')) {
|
||||
raw = lower.substring(0, lower.length - '.zcash'.length);
|
||||
} else if (lower.endsWith('.zec')) {
|
||||
raw = lower.substring(0, lower.length - '.zec'.length);
|
||||
}
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
if (!_nameRegExp.hasMatch(raw)) return null;
|
||||
return raw;
|
||||
}
|
||||
|
||||
static bool _isValidZcashAddress(String address) {
|
||||
final pattern =
|
||||
AddressValidator.getAddressFromStringPattern(CryptoCurrency.zec);
|
||||
if (pattern == null) return false;
|
||||
return RegExp('^(?:$pattern)\$').hasMatch(address);
|
||||
}
|
||||
}
|
||||
@@ -256,28 +256,33 @@ class ThorChainExchangeProvider extends ExchangeProvider {
|
||||
|
||||
static Future<Map<String, String>?>? lookupAddressByName(String name) async {
|
||||
final uri = Uri.https(_baseURL, '$_nameLookUpPath$name');
|
||||
final response = await ProxyWrapper().get(clearnetUri: uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final body = json.decode(response.body) as Map<String, dynamic>;
|
||||
final entries = body['entries'] as List<dynamic>?;
|
||||
|
||||
if (entries == null || entries.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, String> chainToAddressMap = {};
|
||||
|
||||
for (final entry in entries) {
|
||||
final chain = entry['chain'] as String;
|
||||
final address = entry['address'] as String;
|
||||
chainToAddressMap[chain] = address;
|
||||
}
|
||||
|
||||
return chainToAddressMap;
|
||||
try {
|
||||
final response = await ProxyWrapper().get(clearnetUri: uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final body = json.decode(response.body) as Map<String, dynamic>;
|
||||
final entries = body['entries'] as List<dynamic>?;
|
||||
|
||||
if (entries == null || entries.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, String> chainToAddressMap = {};
|
||||
|
||||
for (final entry in entries) {
|
||||
final chain = entry['chain'] as String;
|
||||
final address = entry['address'] as String;
|
||||
chainToAddressMap[chain] = address;
|
||||
}
|
||||
|
||||
return chainToAddressMap;
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
|
||||
|
||||
@@ -78,6 +78,11 @@ Future<String> extractAddressFromParsed(
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Zcash.me)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.zcashName:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Zcash Names)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.bip353:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (BIP-353)');
|
||||
|
||||
@@ -45,6 +45,11 @@ class DomainLookupsPage extends BasePage {
|
||||
title: 'Ethereum Name Service',
|
||||
value: _connectionsSyncViewModel.looksUpENS,
|
||||
onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupsENS(value)),
|
||||
SettingsSwitcherCell(
|
||||
title: 'Zcash Names',
|
||||
value: _connectionsSyncViewModel.lookupsZcashNames,
|
||||
onValueChange: (_, bool value) =>
|
||||
_connectionsSyncViewModel.setLookupsZcashNames(value)),
|
||||
SettingsSwitcherCell(
|
||||
title: '.well-known',
|
||||
value: _connectionsSyncViewModel.looksUpWellKnown,
|
||||
|
||||
@@ -131,6 +131,7 @@ abstract class SettingsStoreBase with Store {
|
||||
required this.lookupsUnstoppableDomains,
|
||||
required this.lookupsOpenAlias,
|
||||
required this.lookupsENS,
|
||||
required this.lookupsZcashNames,
|
||||
required this.lookupsWellKnown,
|
||||
required this.usePayjoin,
|
||||
required this.showPayjoinCard,
|
||||
@@ -561,6 +562,11 @@ abstract class SettingsStoreBase with Store {
|
||||
reaction((_) => lookupsENS,
|
||||
(bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS));
|
||||
|
||||
reaction(
|
||||
(_) => lookupsZcashNames,
|
||||
(bool looksUpZcashNames) => _sharedPreferences.setBool(
|
||||
PreferencesKey.lookupsZcashNames, looksUpZcashNames));
|
||||
|
||||
reaction(
|
||||
(_) => lookupsWellKnown,
|
||||
(bool looksUpWellKnown) =>
|
||||
@@ -953,6 +959,9 @@ abstract class SettingsStoreBase with Store {
|
||||
@observable
|
||||
bool lookupsENS;
|
||||
|
||||
@observable
|
||||
bool lookupsZcashNames;
|
||||
|
||||
@observable
|
||||
bool lookupsWellKnown;
|
||||
|
||||
@@ -1260,6 +1269,8 @@ abstract class SettingsStoreBase with Store {
|
||||
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
|
||||
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
final lookupsZcashNames =
|
||||
sharedPreferences.getBool(PreferencesKey.lookupsZcashNames) ?? true;
|
||||
final lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true;
|
||||
final usePayjoin = sharedPreferences.getBool(PreferencesKey.usePayjoin) ?? false;
|
||||
final showPayjoinCard = sharedPreferences.getBool(PreferencesKey.showPayjoinCard) ?? true;
|
||||
@@ -1625,6 +1636,7 @@ abstract class SettingsStoreBase with Store {
|
||||
lookupsUnstoppableDomains: lookupsUnstoppableDomains,
|
||||
lookupsOpenAlias: lookupsOpenAlias,
|
||||
lookupsENS: lookupsENS,
|
||||
lookupsZcashNames: lookupsZcashNames,
|
||||
lookupsWellKnown: lookupsWellKnown,
|
||||
usePayjoin: usePayjoin,
|
||||
showPayjoinCard: showPayjoinCard,
|
||||
@@ -1851,6 +1863,8 @@ abstract class SettingsStoreBase with Store {
|
||||
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
|
||||
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
lookupsZcashNames =
|
||||
sharedPreferences.getBool(PreferencesKey.lookupsZcashNames) ?? true;
|
||||
lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true;
|
||||
customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
||||
silentPaymentsCardDisplay =
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
@@ -50,6 +49,9 @@ abstract class ConnectionSyncViewModelBase with Store {
|
||||
@computed
|
||||
bool get looksUpENS => _settingsStore.lookupsENS;
|
||||
|
||||
@computed
|
||||
bool get lookupsZcashNames => _settingsStore.lookupsZcashNames;
|
||||
|
||||
@computed
|
||||
bool get looksUpWellKnown => _settingsStore.lookupsWellKnown;
|
||||
|
||||
@@ -129,6 +131,9 @@ abstract class ConnectionSyncViewModelBase with Store {
|
||||
@action
|
||||
void setLookupsENS(bool value) => _settingsStore.lookupsENS = value;
|
||||
|
||||
@action
|
||||
void setLookupsZcashNames(bool value) => _settingsStore.lookupsZcashNames = value;
|
||||
|
||||
@action
|
||||
void setLookupsWellKnown(bool value) => _settingsStore.lookupsWellKnown = value;
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||
@computed
|
||||
bool get looksUpENS => _settingsStore.lookupsENS;
|
||||
|
||||
@computed
|
||||
bool get lookupsZcashNames => _settingsStore.lookupsZcashNames;
|
||||
|
||||
@computed
|
||||
bool get looksUpWellKnown => _settingsStore.lookupsWellKnown;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user