diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index c79ac0980..121f984cb 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -314,8 +314,8 @@ class AddressValidator extends TextValidator { pattern = 'ban_[0-9a-zA-Z]{60}'; case CryptoCurrency.bch: pattern = '(bitcoincash:)?q[0-9a-zA-Z]{41,42}'; - case CryptoCurrency.sol: - pattern = '[1-9A-HJ-NP-Za-km-z]+'; + // case CryptoCurrency.sol: // TODO - looks like this is not correct + // pattern = '[1-9A-HJ-NP-Za-km-z]+'; case CryptoCurrency.trx: pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}'; case CryptoCurrency.zano: diff --git a/lib/entities/bip_353_record.dart b/lib/entities/bip_353_record.dart index c6d8cf47c..46b1c9132 100644 --- a/lib/entities/bip_353_record.dart +++ b/lib/entities/bip_353_record.dart @@ -1,11 +1,6 @@ import 'package:basic_utils/basic_utils.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/widgets/alert_with_picker_option.dart'; -import 'package:cake_wallet/src/widgets/picker.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:flutter/material.dart'; class Bip353Record { Bip353Record({ @@ -81,67 +76,4 @@ class Bip353Record { } } - static Future pickBip353AddressChoice( - String bip353Name, - Map addressMap, - ) async { - if (addressMap.length == 1) { - return addressMap.values.first; - } - - final chosenAddress = await _showAddressChoiceDialog(bip353Name, addressMap); - - return chosenAddress; - } - - static Future _showAddressChoiceDialog( - String bip353Name, - Map addressMap, - ) async { - final entriesList = addressMap.entries.toList(); - final List> displayItems = entriesList.map((entry) { - final originalKey = entry.key; - final originalValue = entry.value; - - final extendedKeyName = keyDisplayMap[originalKey] ?? originalKey; - final truncatedValue = _truncate(originalValue, front: 6, back: 6); - - return { - 'displayKey': extendedKeyName, - 'displayValue': truncatedValue, - 'originalKey': originalKey, - 'originalValue': originalValue, - }; - }).toList(); - - String? selectedOriginalValue; - - // if (context.mounted) { - // await showPopUp( - // context: context, - // builder: (dialogContext) { - // return AlertWithPickerOption( - // alertTitle: S.of(context).multiple_addresses_detected + '\n$bip353Name', - // alertTitleTextSize: 14, - // alertSubtitle: S.of(context).please_choose_one + ':', - // options: displayItems, - // onOptionSelected: (Map chosenItem) { - // selectedOriginalValue = chosenItem['originalValue']; - // }, - // alertBarrierDismissible: true, - // ); - // }, - // ); - // } - return selectedOriginalValue = displayItems.first['originalValue']; - } - - static String _truncate(String value, {int front = 6, int back = 6}) { - if (value.length <= front + back) return value; - - final start = value.substring(0, front); - final end = value.substring(value.length - back); - return '$start...$end'; - } - } diff --git a/lib/entities/fio_address_provider.dart b/lib/entities/fio_address_provider.dart index dcfd79c96..d43ebfe4b 100644 --- a/lib/entities/fio_address_provider.dart +++ b/lib/entities/fio_address_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; class FioAddressProvider { @@ -23,14 +24,13 @@ class FioAddressProvider { return isFioRegistered; } - final responseJSON = json.decode(response.body) as Map; isFioRegistered = responseJSON['is_registered'] as int == 1; return isFioRegistered; } - static Future getPubAddress(String fioAddress, String token) async { + static Future getPubAddress(String fioAddress, String token) async { final headers = {'Content-Type': 'application/json'}; final body = { "fio_address": fioAddress, @@ -45,21 +45,28 @@ class FioAddressProvider { body: json.encode(body), ); - + final responseJSON = json.decode(response.body) as Map; + if (response.statusCode == 400) { - final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error'] as String; final message = responseJSON['message'] as String; - throw Exception('${error}\n$message'); + printV('${error}\n$message'); + return null; } if (response.statusCode != 200) { - throw Exception('Unexpected response http status: ${response.statusCode}'); + final String message = responseJSON['message'] as String? ?? 'Unknown error'; + + printV('Error fetching public address for token $token: $message'); + return null; } - final responseJSON = json.decode(response.body) as Map; - final String pubAddress = responseJSON['public_address'] as String; + final String pubAddress = responseJSON['public_address'] as String? ?? ''; - return pubAddress; + if (pubAddress.isNotEmpty) { + return pubAddress; + } + + return null; } } diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index de19a59fd..a399160a8 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -200,7 +200,8 @@ class AddressResolverService { LookupEntry( source: AddressSource.fio, currencies: AddressSource.fio.supportedCurrencies, - applies: (q) => settingsStore.lookupsFio && !q.startsWith('@') && q.contains('@') && !q.contains('.'), + applies: (q) => + settingsStore.lookupsFio && !q.startsWith('@') && q.contains('@') && !q.contains('.'), // FIO handle example: username@domain run: _lookupFio, ), @@ -236,7 +237,7 @@ class AddressResolverService { LookupEntry( source: AddressSource.bip353, currencies: AddressSource.bip353.supportedCurrencies, - applies: (q) => settingsStore.lookupsBip353, + applies: (q) => settingsStore.lookupsBip353 && q.contains('@') && q.contains('.'), run: _lookupsBip353, ), LookupEntry( @@ -267,11 +268,8 @@ class AddressResolverService { ]; } - - - static String _cleanInput(String raw) => raw - .replaceAll(RegExp(r'[\u2028\u2029]'), '\n') - .replaceAll(RegExp(r'<[^>]+>'), ' '); + static String _cleanInput(String raw) => + raw.replaceAll(RegExp(r'[\u2028\u2029]'), '\n').replaceAll(RegExp(r'<[^>]+>'), ' '); static String? extractAddressByType({ required String raw, @@ -279,21 +277,21 @@ class AddressResolverService { }) { final pat = AddressValidator.getAddressFromStringPattern(type); if (pat == null) { - throw StateError('Unknown pattern for $type'); + printV('Unknown pattern for $type'); + return null; } final text = _cleanInput(raw); - final regex = RegExp(r'(?:^|[^0-9A-Za-z])(' + pat + r')', - multiLine: true, caseSensitive: false); + final regex = + RegExp(r'(?:^|[^0-9A-Za-z])(' + pat + r')', multiLine: true, caseSensitive: false); final m = regex.firstMatch(text); if (m == null) return null; // 3. Strip BCH / NANO prefixes and punctuation just like before - final cleaned = m - .group(1)! - .replaceAllMapped(RegExp(r'[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'), (m) { + final cleaned = + m.group(1)!.replaceAllMapped(RegExp(r'[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'), (m) { final g = m.group(0)!; return (g.startsWith('bitcoincash:') || g.startsWith('nano_') || g.startsWith('ban_')) ? g @@ -349,15 +347,21 @@ class AddressResolverService { final Map result = {}; + try { for (final cur in currencies) { final addressFromBio = extractAddressByType( raw: twitterUser.description, type: CryptoCurrency.fromString(cur.title)); + print('Address from bio: $addressFromBio'); if (addressFromBio != null && addressFromBio.isNotEmpty) { result[cur] = addressFromBio; } } + } catch (e) { + printV('Error extracting address from Twitter bio: $e'); + } + try { final pinnedTweet = twitterUser.pinnedTweet?.text; if (pinnedTweet != null) { for (final cur in currencies) { @@ -368,6 +372,9 @@ class AddressResolverService { } } } + } catch (e) { + printV('Error extracting address from Twitter pinned tweet: $e'); + } if (result.isNotEmpty) { return ParsedAddress( @@ -472,7 +479,7 @@ class AddressResolverService { for (final cur in currencies) { final address = await FioAddressProvider.getPubAddress(text, cur.title); - if (address.isNotEmpty) { + if (address != null && address.isNotEmpty) { result[cur] = address; } } @@ -556,18 +563,24 @@ class AddressResolverService { } Future _lookupsBip353( - String text, List currency, WalletBase _) async { + String text, List currencies, WalletBase _) async { final Map result = {}; - for (final cur in currency) { + for (final cur in currencies) { final bip353AddressMap = await Bip353Record.fetchUriByCryptoCurrency(text, cur.title); + if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) { - final address = bip353AddressMap['address']; - if (address != null && address.isNotEmpty) { - result[cur] = address; + if (cur == CryptoCurrency.btc) { + bip353AddressMap.forEach((key, value) { + final address = bip353AddressMap['sp'] ?? bip353AddressMap['address']; + if (address != null && address.isNotEmpty) { + result[cur] = address; + } + }); } } } + if (result.isNotEmpty) { return ParsedAddress( parsedAddressByCurrencyMap: result, @@ -575,15 +588,6 @@ class AddressResolverService { handle: text, ); } - - // - // if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) { - // final chosenAddress = - // await Bip353Record.pickBip353AddressChoice(text, bip353AddressMap); //TODO fix context - // if (chosenAddress != null) { - // return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text); - // } - // } return null; } diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index 70dbef022..1f7458f89 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -5,8 +5,9 @@ import 'package:cw_core/crypto_currency.dart'; const supportedSources = [ AddressSource.twitter, AddressSource.unstoppableDomains, - AddressSource.ens, AddressSource.mastodon, + AddressSource.bip353, + AddressSource.fio, ]; enum AddressSource { @@ -14,7 +15,15 @@ enum AddressSource { label: 'X', iconPath: 'assets/images/x_social.png', alias: '@username', - supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]), + supportedCurrencies: [ + CryptoCurrency.xmr, + CryptoCurrency.btc, + CryptoCurrency.ltc, + CryptoCurrency.eth, + CryptoCurrency.bnb, + CryptoCurrency.sol, + CryptoCurrency.maticpoly + ]), unstoppableDomains( label: 'Unstoppable Domains', iconPath: 'assets/images/ud.png', @@ -30,19 +39,33 @@ enum AddressSource { iconPath: 'assets/images/yat_mini_logo.png', alias: '🎂🎂🎂', supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]), - fio( - label: 'FIO', - iconPath: 'assets/images/fio.png', - supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]), + fio(label: 'FIO', iconPath: 'assets/images/fio.png', alias: 'user@domain', supportedCurrencies: [ + CryptoCurrency.xmr, + CryptoCurrency.btc, + CryptoCurrency.ltc, + CryptoCurrency.eth, + CryptoCurrency.bnb, + CryptoCurrency.sol, + CryptoCurrency.maticpoly, + ]), ens( label: 'Ethereum Name Service', iconPath: 'assets/images/ens_icon.png', + alias: 'domain.eth', supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc, CryptoCurrency.eth]), mastodon( label: 'Mastodon', iconPath: 'assets/images/mastodon.svg', alias: 'user@domain.tld', - supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]), + supportedCurrencies: [ + CryptoCurrency.xmr, + CryptoCurrency.btc, + CryptoCurrency.ltc, + CryptoCurrency.eth, + CryptoCurrency.bnb, + CryptoCurrency.sol, + CryptoCurrency.maticpoly + ]), nostr( label: 'Nostr', iconPath: 'assets/images/nostr.png', @@ -61,8 +84,9 @@ enum AddressSource { supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]), bip353( label: 'BIP-353', - iconPath: '', - supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]), + iconPath: 'assets/images/btc.png', + alias: 'user@domain.com', + supportedCurrencies: [CryptoCurrency.btc]), contact(label: 'Contact', iconPath: '', supportedCurrencies: []), notParsed(label: 'Unknown', iconPath: '', supportedCurrencies: []); @@ -93,7 +117,7 @@ extension AddressSourceNameParser on AddressSource { } final needle = text.trim().toLowerCase(); return AddressSource.values.firstWhere( - (src) => src.label.toLowerCase() == needle, + (src) => src.label.toLowerCase() == needle, orElse: () => AddressSource.notParsed, ); }