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 }