thor name to address lookup (#1390)

* thor name to address lookup

* minor fix [skip ci]

* Addressing code review comments

* minor fix
This commit is contained in:
Serhii 2024-05-06 22:16:25 +03:00 committed by GitHub
parent cd41766e69
commit 3f3cd10158
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 82 additions and 35 deletions

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/openalias_record.dart';
import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
import 'package:cake_wallet/entities/emoji_string_extension.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/mastodon/mastodon_api.dart'; import 'package:cake_wallet/mastodon/mastodon_api.dart';
import 'package:cake_wallet/nostr/nostr_api.dart'; import 'package:cake_wallet/nostr/nostr_api.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
@ -71,8 +72,8 @@ class AddressResolver {
return emailRegex.hasMatch(address); return emailRegex.hasMatch(address);
} }
// TODO: refactor this to take Crypto currency instead of ticker, or at least pass in the tag as well Future<ParsedAddress> resolve(BuildContext context, String text, CryptoCurrency currency) async {
Future<ParsedAddress> resolve(BuildContext context, String text, String ticker) async { final ticker = currency.title;
try { try {
if (text.startsWith('@') && !text.substring(1).contains('@')) { if (text.startsWith('@') && !text.substring(1).contains('@')) {
if (settingsStore.lookupsTwitter) { if (settingsStore.lookupsTwitter) {
@ -116,8 +117,7 @@ class AddressResolver {
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName); await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
if (mastodonUser != null) { if (mastodonUser != null) {
String? addressFromBio = extractAddressByType( String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker));
if (addressFromBio != null) { if (addressFromBio != null) {
return ParsedAddress.fetchMastodonAddress( return ParsedAddress.fetchMastodonAddress(
@ -131,8 +131,8 @@ class AddressResolver {
if (pinnedPosts.isNotEmpty) { if (pinnedPosts.isNotEmpty) {
final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n'); final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
String? addressFromPinnedPost = extractAddressByType( String? addressFromPinnedPost =
raw: userPinnedPostsText, type: CryptoCurrency.fromString(ticker)); extractAddressByType(raw: userPinnedPostsText, type: currency);
if (addressFromPinnedPost != null) { if (addressFromPinnedPost != null) {
return ParsedAddress.fetchMastodonAddress( return ParsedAddress.fetchMastodonAddress(
@ -162,6 +162,16 @@ class AddressResolver {
} }
} }
} }
final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text);
if (thorChainAddress != null) {
String? address =
thorChainAddress[ticker] ?? (ticker == 'RUNE' ? thorChainAddress['THOR'] : null);
if (address != null) {
return ParsedAddress.thorChainAddress(address: address, name: text);
}
}
final formattedName = OpenaliasRecord.formatDomainName(text); final formattedName = OpenaliasRecord.formatDomainName(text);
final domainParts = formattedName.split('.'); final domainParts = formattedName.split('.');
final name = domainParts.last; final name = domainParts.last;
@ -204,7 +214,7 @@ class AddressResolver {
if (nostrUserData != null) { if (nostrUserData != null) {
String? addressFromBio = extractAddressByType( String? addressFromBio = extractAddressByType(
raw: nostrUserData.about, type: CryptoCurrency.fromString(ticker)); raw: nostrUserData.about, type: currency);
if (addressFromBio != null) { if (addressFromBio != null) {
return ParsedAddress.nostrAddress( return ParsedAddress.nostrAddress(
address: addressFromBio, address: addressFromBio,

View file

@ -11,7 +11,8 @@ enum ParseFrom {
ens, ens,
contact, contact,
mastodon, mastodon,
nostr nostr,
thorChain
} }
class ParsedAddress { class ParsedAddress {
@ -133,6 +134,14 @@ class ParsedAddress {
); );
} }
factory ParsedAddress.thorChainAddress({required String address, required String name}) {
return ParsedAddress(
addresses: [address],
name: name,
parseFrom: ParseFrom.thorChain,
);
}
final List<String> addresses; final List<String> addresses;
final String name; final String name;
final String description; final String description;

View file

@ -34,11 +34,13 @@ class ThorChainExchangeProvider extends ExchangeProvider {
static final isRefundAddressSupported = [CryptoCurrency.eth]; static final isRefundAddressSupported = [CryptoCurrency.eth];
static const _baseURL = 'thornode.ninerealms.com'; static const _baseNodeURL = 'thornode.ninerealms.com';
static const _baseURL = 'midgard.ninerealms.com';
static const _quotePath = '/thorchain/quote/swap'; static const _quotePath = '/thorchain/quote/swap';
static const _txInfoPath = '/thorchain/tx/status/'; static const _txInfoPath = '/thorchain/tx/status/';
static const _affiliateName = 'cakewallet'; static const _affiliateName = 'cakewallet';
static const _affiliateBps = '175'; static const _affiliateBps = '175';
static const _nameLookUpPath= 'v2/thorname/lookup/';
final Box<Trade> tradesStore; final Box<Trade> tradesStore;
@ -154,7 +156,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
Future<Trade> findTradeById({required String id}) async { Future<Trade> findTradeById({required String id}) async {
if (id.isEmpty) throw Exception('Trade id is empty'); if (id.isEmpty) throw Exception('Trade id is empty');
final formattedId = id.startsWith('0x') ? id.substring(2) : id; final formattedId = id.startsWith('0x') ? id.substring(2) : id;
final uri = Uri.https(_baseURL, '$_txInfoPath$formattedId'); final uri = Uri.https(_baseNodeURL, '$_txInfoPath$formattedId');
final response = await http.get(uri); final response = await http.get(uri);
if (response.statusCode == 404) { if (response.statusCode == 404) {
@ -206,8 +208,35 @@ class ThorChainExchangeProvider extends ExchangeProvider {
); );
} }
static Future<Map<String, String>?>? lookupAddressByName(String name) async {
final uri = Uri.https(_baseURL, '$_nameLookUpPath$name');
final response = await http.get(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;
}
Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async { Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
Uri uri = Uri.https(_baseURL, _quotePath, params); Uri uri = Uri.https(_baseNodeURL, _quotePath, params);
final response = await http.get(uri); final response = await http.get(uri);

View file

@ -330,10 +330,12 @@ class ExchangePage extends BasePage {
void applyTemplate( void applyTemplate(
BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
exchangeViewModel.changeDepositCurrency(
currency: CryptoCurrency.fromString(template.depositCurrency)); final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency);
exchangeViewModel.changeReceiveCurrency( final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency);
currency: CryptoCurrency.fromString(template.receiveCurrency));
exchangeViewModel.changeDepositCurrency(currency: depositCryptoCurrency);
exchangeViewModel.changeReceiveCurrency(currency: receiveCryptoCurrency);
exchangeViewModel.changeDepositAmount(amount: template.amount); exchangeViewModel.changeDepositAmount(amount: template.amount);
exchangeViewModel.depositAddress = template.depositAddress; exchangeViewModel.depositAddress = template.depositAddress;
@ -342,12 +344,10 @@ class ExchangePage extends BasePage {
exchangeViewModel.isFixedRateMode = false; exchangeViewModel.isFixedRateMode = false;
var domain = template.depositAddress; var domain = template.depositAddress;
var ticker = template.depositCurrency.toLowerCase(); exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency);
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
domain = template.receiveAddress; domain = template.receiveAddress;
ticker = template.receiveCurrency.toLowerCase(); exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency);
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
} }
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) { void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
@ -519,16 +519,14 @@ class ExchangePage extends BasePage {
_depositAddressFocus.addListener(() async { _depositAddressFocus.addListener(() async {
if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) { if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) {
final domain = depositAddressController.text; final domain = depositAddressController.text;
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
} }
}); });
_receiveAddressFocus.addListener(() async { _receiveAddressFocus.addListener(() async {
if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) { if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) {
final domain = receiveAddressController.text; final domain = receiveAddressController.text;
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
} }
}); });
@ -575,8 +573,8 @@ class ExchangePage extends BasePage {
} }
} }
Future<String> fetchParsedAddress(BuildContext context, String domain, String ticker) async { Future<String> fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async {
final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker); final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
final address = await extractAddressFromParsed(context, parsedAddress); final address = await extractAddressFromParsed(context, parsedAddress);
return address; return address;
} }
@ -663,15 +661,13 @@ class ExchangePage extends BasePage {
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency), addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency),
onPushPasteButton: (context) async { onPushPasteButton: (context) async {
final domain = exchangeViewModel.depositAddress; final domain = exchangeViewModel.depositAddress;
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
exchangeViewModel.depositAddress = exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, ticker); await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
}, },
onPushAddressBookButton: (context) async { onPushAddressBookButton: (context) async {
final domain = exchangeViewModel.depositAddress; final domain = exchangeViewModel.depositAddress;
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
exchangeViewModel.depositAddress = exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, ticker); await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
}, },
)); ));
@ -712,15 +708,13 @@ class ExchangePage extends BasePage {
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency), addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency),
onPushPasteButton: (context) async { onPushPasteButton: (context) async {
final domain = exchangeViewModel.receiveAddress; final domain = exchangeViewModel.receiveAddress;
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
exchangeViewModel.receiveAddress = exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, ticker); await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
}, },
onPushAddressBookButton: (context) async { onPushAddressBookButton: (context) async {
final domain = exchangeViewModel.receiveAddress; final domain = exchangeViewModel.receiveAddress;
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
exchangeViewModel.receiveAddress = exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, ticker); await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
}, },
)); ));

View file

@ -56,6 +56,11 @@ Future<String> extractAddressFromParsed(
profileImageUrl = parsedAddress.profileImageUrl; profileImageUrl = parsedAddress.profileImageUrl;
profileName = parsedAddress.profileName; profileName = parsedAddress.profileName;
break; break;
case ParseFrom.thorChain:
title = S.of(context).address_detected;
content = S.of(context).extracted_address_content('${parsedAddress.name} (ThorChain)');
address = parsedAddress.addresses.first;
break;
case ParseFrom.yatRecord: case ParseFrom.yatRecord:
if (parsedAddress.name.isEmpty) { if (parsedAddress.name.isEmpty) {
title = S.of(context).yat_error; title = S.of(context).yat_error;

View file

@ -296,8 +296,8 @@ abstract class OutputBase with Store {
Future<void> fetchParsedAddress(BuildContext context) async { Future<void> fetchParsedAddress(BuildContext context) async {
final domain = address; final domain = address;
final ticker = cryptoCurrencyHandler().title.toLowerCase(); final currency = cryptoCurrencyHandler();
parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker); parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
extractedAddress = await extractAddressFromParsed(context, parsedAddress); extractedAddress = await extractAddressFromParsed(context, parsedAddress);
note = parsedAddress.description; note = parsedAddress.description;
} }