.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