thor name to address lookup (#1390)
* thor name to address lookup * minor fix [skip ci] * Addressing code review comments * minor fix
Serhii committed
May 6, 2024 at 22:16 UTC
3f3cd10158245f550f84db129e226a95a13e4c78
6 files changed
+82
-35
lib/entities/parse_address_from_domain.dart
+17
-7
@@ -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/exchange/provider/thorchain_exchange.provider.dart';
9
import 'package:cake_wallet/mastodon/mastodon_api.dart';
10
import 'package:cake_wallet/nostr/nostr_api.dart';
11
import 'package:cake_wallet/store/settings_store.dart';
@@ -71,8 +72,8 @@ class AddressResolver {
72
return emailRegex.hasMatch(address);
73
}
74
74
- // TODO: refactor this to take Crypto currency instead of ticker, or at least pass in the tag as well
75
- Future<ParsedAddress> resolve(BuildContext context, String text, String ticker) async {
75
+ Future<ParsedAddress> resolve(BuildContext context, String text, CryptoCurrency currency) async {
76
+ final ticker = currency.title;
77
try {
78
if (text.startsWith('@') && !text.substring(1).contains('@')) {
79
if (settingsStore.lookupsTwitter) {
@@ -116,8 +117,7 @@ class AddressResolver {
117
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
118
119
if (mastodonUser != null) {
119
- String? addressFromBio = extractAddressByType(
120
- raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker));
120
+ String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
121
122
if (addressFromBio != null) {
123
return ParsedAddress.fetchMastodonAddress(
@@ -131,8 +131,8 @@ class AddressResolver {
131
132
if (pinnedPosts.isNotEmpty) {
133
final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
134
- String? addressFromPinnedPost = extractAddressByType(
135
- raw: userPinnedPostsText, type: CryptoCurrency.fromString(ticker));
134
+ String? addressFromPinnedPost =
135
+ extractAddressByType(raw: userPinnedPostsText, type: currency);
136
137
if (addressFromPinnedPost != null) {
138
return ParsedAddress.fetchMastodonAddress(
@@ -162,6 +162,16 @@ class AddressResolver {
162
}
163
}
164
}
165
+
166
+ final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text);
167
+ if (thorChainAddress != null) {
168
+ String? address =
169
+ thorChainAddress[ticker] ?? (ticker == 'RUNE' ? thorChainAddress['THOR'] : null);
170
+ if (address != null) {
171
+ return ParsedAddress.thorChainAddress(address: address, name: text);
172
+ }
173
+ }
174
+
175
final formattedName = OpenaliasRecord.formatDomainName(text);
176
final domainParts = formattedName.split('.');
177
final name = domainParts.last;
@@ -204,7 +214,7 @@ class AddressResolver {
214
215
if (nostrUserData != null) {
216
String? addressFromBio = extractAddressByType(
207
- raw: nostrUserData.about, type: CryptoCurrency.fromString(ticker));
217
+ raw: nostrUserData.about, type: currency);
218
if (addressFromBio != null) {
219
return ParsedAddress.nostrAddress(
220
address: addressFromBio,
lib/entities/parsed_address.dart
+10
-1
@@ -11,7 +11,8 @@ enum ParseFrom {
11
ens,
12
contact,
13
mastodon,
14
- nostr
14
+ nostr,
15
+ thorChain
16
}
17
18
class ParsedAddress {
@@ -133,6 +134,14 @@ class ParsedAddress {
134
);
135
}
136
137
+ factory ParsedAddress.thorChainAddress({required String address, required String name}) {
138
+ return ParsedAddress(
139
+ addresses: [address],
140
+ name: name,
141
+ parseFrom: ParseFrom.thorChain,
142
+ );
143
+ }
144
+
145
final List<String> addresses;
146
final String name;
147
final String description;
lib/exchange/provider/thorchain_exchange.provider.dart
+32
-3
@@ -34,11 +34,13 @@ class ThorChainExchangeProvider extends ExchangeProvider {
34
35
static final isRefundAddressSupported = [CryptoCurrency.eth];
36
37
- static const _baseURL = 'thornode.ninerealms.com';
37
+ static const _baseNodeURL = 'thornode.ninerealms.com';
38
+ static const _baseURL = 'midgard.ninerealms.com';
39
static const _quotePath = '/thorchain/quote/swap';
40
static const _txInfoPath = '/thorchain/tx/status/';
41
static const _affiliateName = 'cakewallet';
42
static const _affiliateBps = '175';
43
+ static const _nameLookUpPath= 'v2/thorname/lookup/';
44
45
final Box<Trade> tradesStore;
46
@@ -154,7 +156,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
156
Future<Trade> findTradeById({required String id}) async {
157
if (id.isEmpty) throw Exception('Trade id is empty');
158
final formattedId = id.startsWith('0x') ? id.substring(2) : id;
157
- final uri = Uri.https(_baseURL, '$_txInfoPath$formattedId');
159
+ final uri = Uri.https(_baseNodeURL, '$_txInfoPath$formattedId');
160
final response = await http.get(uri);
161
162
if (response.statusCode == 404) {
@@ -206,8 +208,35 @@ class ThorChainExchangeProvider extends ExchangeProvider {
208
);
209
}
210
211
+ static Future<Map<String, String>?>? lookupAddressByName(String name) async {
212
+ final uri = Uri.https(_baseURL, '$_nameLookUpPath$name');
213
+ final response = await http.get(uri);
214
+
215
+ if (response.statusCode != 200) {
216
+ return null;
217
+ }
218
+
219
+ final body = json.decode(response.body) as Map<String, dynamic>;
220
+ final entries = body['entries'] as List<dynamic>?;
221
+
222
+ if (entries == null || entries.isEmpty) {
223
+ return null;
224
+ }
225
+
226
+ Map<String, String> chainToAddressMap = {};
227
+
228
+ for (final entry in entries) {
229
+ final chain = entry['chain'] as String;
230
+ final address = entry['address'] as String;
231
+ chainToAddressMap[chain] = address;
232
+ }
233
+
234
+ return chainToAddressMap;
235
+ }
236
+
237
+
238
Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
210
- Uri uri = Uri.https(_baseURL, _quotePath, params);
239
+ Uri uri = Uri.https(_baseNodeURL, _quotePath, params);
240
241
final response = await http.get(uri);
242
lib/src/screens/exchange/exchange_page.dart
+16
-22
@@ -330,10 +330,12 @@ class ExchangePage extends BasePage {
330
331
void applyTemplate(
332
BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
333
- exchangeViewModel.changeDepositCurrency(
334
- currency: CryptoCurrency.fromString(template.depositCurrency));
335
- exchangeViewModel.changeReceiveCurrency(
336
- currency: CryptoCurrency.fromString(template.receiveCurrency));
333
+
334
+ final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency);
335
+ final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency);
336
+
337
+ exchangeViewModel.changeDepositCurrency(currency: depositCryptoCurrency);
338
+ exchangeViewModel.changeReceiveCurrency(currency: receiveCryptoCurrency);
339
340
exchangeViewModel.changeDepositAmount(amount: template.amount);
341
exchangeViewModel.depositAddress = template.depositAddress;
@@ -342,12 +344,10 @@ class ExchangePage extends BasePage {
344
exchangeViewModel.isFixedRateMode = false;
345
346
var domain = template.depositAddress;
345
- var ticker = template.depositCurrency.toLowerCase();
346
- exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
347
+ exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency);
348
349
domain = template.receiveAddress;
349
- ticker = template.receiveCurrency.toLowerCase();
350
- exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
350
+ exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency);
351
}
352
353
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
@@ -519,16 +519,14 @@ class ExchangePage extends BasePage {
519
_depositAddressFocus.addListener(() async {
520
if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) {
521
final domain = depositAddressController.text;
522
- final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
523
- exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
522
+ exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
523
}
524
});
525
526
_receiveAddressFocus.addListener(() async {
527
if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) {
528
final domain = receiveAddressController.text;
530
- final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
531
- exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
529
+ exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
530
}
531
});
532
@@ -575,8 +573,8 @@ class ExchangePage extends BasePage {
573
}
574
}
575
578
- Future<String> fetchParsedAddress(BuildContext context, String domain, String ticker) async {
579
- final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker);
576
+ Future<String> fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async {
577
+ final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
578
final address = await extractAddressFromParsed(context, parsedAddress);
579
return address;
580
}
@@ -663,15 +661,13 @@ class ExchangePage extends BasePage {
661
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency),
662
onPushPasteButton: (context) async {
663
final domain = exchangeViewModel.depositAddress;
666
- final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
664
exchangeViewModel.depositAddress =
668
- await fetchParsedAddress(context, domain, ticker);
665
+ await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
666
},
667
onPushAddressBookButton: (context) async {
668
final domain = exchangeViewModel.depositAddress;
672
- final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
669
exchangeViewModel.depositAddress =
674
- await fetchParsedAddress(context, domain, ticker);
670
+ await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
671
},
672
));
673
@@ -712,15 +708,13 @@ class ExchangePage extends BasePage {
708
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency),
709
onPushPasteButton: (context) async {
710
final domain = exchangeViewModel.receiveAddress;
715
- final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
711
exchangeViewModel.receiveAddress =
717
- await fetchParsedAddress(context, domain, ticker);
712
+ await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
713
},
714
onPushAddressBookButton: (context) async {
715
final domain = exchangeViewModel.receiveAddress;
721
- final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
716
exchangeViewModel.receiveAddress =
723
- await fetchParsedAddress(context, domain, ticker);
717
+ await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
718
},
719
));
720
lib/src/screens/send/widgets/extract_address_from_parsed.dart
+5
@@ -56,6 +56,11 @@ Future<String> extractAddressFromParsed(
56
profileImageUrl = parsedAddress.profileImageUrl;
57
profileName = parsedAddress.profileName;
58
break;
59
+ case ParseFrom.thorChain:
60
+ title = S.of(context).address_detected;
61
+ content = S.of(context).extracted_address_content('${parsedAddress.name} (ThorChain)');
62
+ address = parsedAddress.addresses.first;
63
+ break;
64
case ParseFrom.yatRecord:
65
if (parsedAddress.name.isEmpty) {
66
title = S.of(context).yat_error;
lib/view_model/send/output.dart
+2
-2
@@ -296,8 +296,8 @@ abstract class OutputBase with Store {
296
297
Future<void> fetchParsedAddress(BuildContext context) async {
298
final domain = address;
299
- final ticker = cryptoCurrencyHandler().title.toLowerCase();
300
- parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker);
299
+ final currency = cryptoCurrencyHandler();
300
+ parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
301
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
302
note = parsedAddress.description;
303
}