.well-known domain support (#1956)

* add well-known setting [wip] * should work * fix * minor fix (tested and working)

Matthew Fosse committed Jan 15, 2025 at 17:03 UTC 60e7dcffa957278866829b3d9f3279d452da3e67
9 files changed +147 -1
lib/core/backup_service.dart
+6
@@ -293,6 +293,7 @@ class BackupService {
293 final lookupsUnstoppableDomains = data[PreferencesKey.lookupsUnstoppableDomains] as bool?;
294 final lookupsOpenAlias = data[PreferencesKey.lookupsOpenAlias] as bool?;
295 final lookupsENS = data[PreferencesKey.lookupsENS] as bool?;
296 + final lookupsWellKnown = data[PreferencesKey.lookupsWellKnown] as bool?;
297 final syncAll = data[PreferencesKey.syncAllKey] as bool?;
298 final syncMode = data[PreferencesKey.syncModeKey] as int?;
299 final autoGenerateSubaddressStatus =
@@ -403,6 +404,9 @@ class BackupService {
404
405 if (lookupsENS != null) await _sharedPreferences.setBool(PreferencesKey.lookupsENS, lookupsENS);
406
407 + if (lookupsWellKnown != null)
408 + await _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, lookupsWellKnown);
409 +
410 if (syncAll != null) await _sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll);
411
412 if (syncMode != null) await _sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode);
@@ -542,6 +546,8 @@ class BackupService {
546 _sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains),
547 PreferencesKey.lookupsOpenAlias: _sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias),
548 PreferencesKey.lookupsENS: _sharedPreferences.getBool(PreferencesKey.lookupsENS),
549 + PreferencesKey.lookupsWellKnown:
550 + _sharedPreferences.getBool(PreferencesKey.lookupsWellKnown),
551 PreferencesKey.syncModeKey: _sharedPreferences.getInt(PreferencesKey.syncModeKey),
552 PreferencesKey.syncAllKey: _sharedPreferences.getBool(PreferencesKey.syncAllKey),
553 PreferencesKey.autoGenerateSubaddressStatusKey:
lib/entities/parse_address_from_domain.dart
+12
@@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/openalias_record.dart';
5 import 'package:cake_wallet/entities/parsed_address.dart';
6 import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
7 import 'package:cake_wallet/entities/emoji_string_extension.dart';
8 +import 'package:cake_wallet/entities/wellknown_record.dart';
9 import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
10 import 'package:cake_wallet/mastodon/mastodon_api.dart';
11 import 'package:cake_wallet/nostr/nostr_api.dart';
@@ -208,6 +209,17 @@ class AddressResolver {
209 }
210 }
211
212 + // .well-known scheme:
213 + if (text.contains('.') && text.contains('@')) {
214 + if (settingsStore.lookupsWellKnown) {
215 + final record =
216 + await WellKnownRecord.fetchAddressAndName(formattedName: text, currency: currency);
217 + if (record != null) {
218 + return ParsedAddress.fetchWellKnownAddress(address: record.address, name: text);
219 + }
220 + }
221 + }
222 +
223 if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) {
224 final bool isFioRegistered = await FioAddressProvider.checkAvail(text);
225 if (isFioRegistered) {
lib/entities/parsed_address.dart
+10 -1
@@ -12,7 +12,8 @@ enum ParseFrom {
12 contact,
13 mastodon,
14 nostr,
15 - thorChain
15 + thorChain,
16 + wellKnown
17 }
18
19 class ParsedAddress {
@@ -142,6 +143,14 @@ class ParsedAddress {
143 );
144 }
145
146 + factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) {
147 + return ParsedAddress(
148 + addresses: [address],
149 + name: name,
150 + parseFrom: ParseFrom.wellKnown,
151 + );
152 + }
153 +
154 final List<String> addresses;
155 final String name;
156 final String description;
lib/entities/preferences_key.dart
+1
@@ -76,6 +76,7 @@ class PreferencesKey {
76 static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain';
77 static const lookupsOpenAlias = 'looks_up_open_alias';
78 static const lookupsENS = 'looks_up_ens';
79 + static const lookupsWellKnown = 'looks_up_well_known';
80 static const showCameraConsent = 'show_camera_consent';
81
82 static String moneroWalletUpdateV1Key(String name) =>
lib/entities/wellknown_record.dart new
+92
@@ -0,0 +1,92 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cw_core/crypto_currency.dart';
4 +import 'package:cw_core/utils/print_verbose.dart';
5 +import 'package:http/http.dart' as http;
6 +
7 +class WellKnownRecord {
8 + WellKnownRecord({
9 + required this.address,
10 + required this.name,
11 + });
12 +
13 + final String name;
14 + final String address;
15 +
16 + static Future<String?> checkWellKnownUsername(String username, CryptoCurrency currency) async {
17 + String jsonLocation = "";
18 + switch (currency) {
19 + case CryptoCurrency.nano:
20 + jsonLocation = "nano-currency";
21 + break;
22 + // TODO: add other currencies
23 + default:
24 + return null;
25 + }
26 +
27 + // split the string by the @ symbol:
28 + try {
29 + final List<String> splitStrs = username.split("@");
30 + String name = splitStrs.first.toLowerCase();
31 + final String domain = splitStrs.last;
32 +
33 + if (splitStrs.length == 3) {
34 + // for username like @alice@domain.org instead of alice@domain.org
35 + name = splitStrs[1];
36 + }
37 +
38 + if (name.isEmpty) {
39 + name = "_";
40 + }
41 +
42 + // lookup domain/.well-known/nano-currency.json and check if it has a nano address:
43 + final http.Response response = await http.get(
44 + Uri.parse("https://$domain/.well-known/$jsonLocation.json?names=$name"),
45 + headers: <String, String>{"Accept": "application/json"},
46 + );
47 +
48 + if (response.statusCode != 200) {
49 + return null;
50 + }
51 + final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
52 +
53 + // Access the first element in the names array and retrieve its address
54 + final List<dynamic> names = decoded["names"] as List<dynamic>;
55 + for (final dynamic item in names) {
56 + if (item["name"].toLowerCase() == name) {
57 + return item["address"] as String;
58 + }
59 + }
60 + } catch (e) {
61 + printV("error checking well-known username: $e");
62 + }
63 + return null;
64 + }
65 +
66 + static String formatDomainName(String name) {
67 + String formattedName = name;
68 +
69 + if (name.contains("@")) {
70 + formattedName = name.replaceAll("@", ".");
71 + }
72 +
73 + return formattedName;
74 + }
75 +
76 + static Future<WellKnownRecord?> fetchAddressAndName({
77 + required String formattedName,
78 + required CryptoCurrency currency,
79 + }) async {
80 + String name = formattedName;
81 +
82 + print("formattedName: $formattedName");
83 +
84 + final address = await checkWellKnownUsername(formattedName, currency);
85 +
86 + if (address == null) {
87 + return null;
88 + }
89 +
90 + return WellKnownRecord(address: address, name: name);
91 + }
92 +}
lib/src/screens/send/widgets/extract_address_from_parsed.dart
+5
@@ -30,6 +30,11 @@ Future<String> extractAddressFromParsed(
30 content = S.of(context).extracted_address_content('${parsedAddress.name} (OpenAlias)');
31 address = parsedAddress.addresses.first;
32 break;
33 + case ParseFrom.wellKnown:
34 + title = S.of(context).address_detected;
35 + content = S.of(context).extracted_address_content('${parsedAddress.name} (Well-Known)');
36 + address = parsedAddress.addresses.first;
37 + break;
38 case ParseFrom.fio:
39 title = S.of(context).address_detected;
40 content = S.of(context).extracted_address_content('${parsedAddress.name} (FIO)');
lib/src/screens/settings/domain_lookups_page.dart
+4
@@ -45,6 +45,10 @@ class DomainLookupsPage extends BasePage {
45 title: 'Ethereum Name Service',
46 value: _privacySettingsViewModel.looksUpENS,
47 onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsENS(value)),
48 + SettingsSwitcherCell(
49 + title: '.well-known',
50 + value: _privacySettingsViewModel.looksUpWellKnown,
51 + onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsWellKnown(value)),
52
53 //if (!isHaven) it does not work correctly
54 ],
lib/store/settings_store.dart
+11
@@ -115,6 +115,7 @@ abstract class SettingsStoreBase with Store {
115 required this.lookupsUnstoppableDomains,
116 required this.lookupsOpenAlias,
117 required this.lookupsENS,
118 + required this.lookupsWellKnown,
119 required this.customBitcoinFeeRate,
120 required this.silentPaymentsCardDisplay,
121 required this.silentPaymentsAlwaysScan,
@@ -459,6 +460,11 @@ abstract class SettingsStoreBase with Store {
460 reaction((_) => lookupsENS,
461 (bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS));
462
463 + reaction(
464 + (_) => lookupsWellKnown,
465 + (bool looksUpWellKnown) =>
466 + _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown));
467 +
468 // secure storage keys:
469 reaction(
470 (_) => allowBiometricalAuthentication,
@@ -772,6 +778,8 @@ abstract class SettingsStoreBase with Store {
778 @observable
779 bool lookupsENS;
780
781 + @observable
782 + bool lookupsWellKnown;
783 @observable
784 SyncMode currentSyncMode;
785
@@ -967,6 +975,7 @@ abstract class SettingsStoreBase with Store {
975 sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
976 final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
977 final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
978 + final lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true;
979 final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
980 final silentPaymentsCardDisplay =
981 sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
@@ -1245,6 +1254,7 @@ abstract class SettingsStoreBase with Store {
1254 lookupsUnstoppableDomains: lookupsUnstoppableDomains,
1255 lookupsOpenAlias: lookupsOpenAlias,
1256 lookupsENS: lookupsENS,
1257 + lookupsWellKnown: lookupsWellKnown,
1258 customBitcoinFeeRate: customBitcoinFeeRate,
1259 silentPaymentsCardDisplay: silentPaymentsCardDisplay,
1260 silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
@@ -1414,6 +1424,7 @@ abstract class SettingsStoreBase with Store {
1424 sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
1425 lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
1426 lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
1427 + lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true;
1428 customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
1429 silentPaymentsCardDisplay =
1430 sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
lib/view_model/settings/privacy_settings_view_model.dart
+6
@@ -94,6 +94,9 @@ abstract class PrivacySettingsViewModelBase with Store {
94 @computed
95 bool get looksUpENS => _settingsStore.lookupsENS;
96
97 + @computed
98 + bool get looksUpWellKnown => _settingsStore.lookupsWellKnown;
99 +
100 bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
101
102 bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
@@ -130,6 +133,9 @@ abstract class PrivacySettingsViewModelBase with Store {
133 @action
134 void setLookupsENS(bool value) => _settingsStore.lookupsENS = value;
135
136 + @action
137 + void setLookupsWellKnown(bool value) => _settingsStore.lookupsWellKnown = value;
138 +
139 @action
140 void setLookupsYatService(bool value) => _settingsStore.lookupsYatService = value;
141