mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
fix: bip-353 and fio
This commit is contained in:
parent
a8eb98b612
commit
1daea9bc01
5 changed files with 84 additions and 117 deletions
|
@ -314,8 +314,8 @@ class AddressValidator extends TextValidator {
|
||||||
pattern = 'ban_[0-9a-zA-Z]{60}';
|
pattern = 'ban_[0-9a-zA-Z]{60}';
|
||||||
case CryptoCurrency.bch:
|
case CryptoCurrency.bch:
|
||||||
pattern = '(bitcoincash:)?q[0-9a-zA-Z]{41,42}';
|
pattern = '(bitcoincash:)?q[0-9a-zA-Z]{41,42}';
|
||||||
case CryptoCurrency.sol:
|
// case CryptoCurrency.sol: // TODO - looks like this is not correct
|
||||||
pattern = '[1-9A-HJ-NP-Za-km-z]+';
|
// pattern = '[1-9A-HJ-NP-Za-km-z]+';
|
||||||
case CryptoCurrency.trx:
|
case CryptoCurrency.trx:
|
||||||
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||||
case CryptoCurrency.zano:
|
case CryptoCurrency.zano:
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import 'package:basic_utils/basic_utils.dart';
|
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/crypto_currency.dart';
|
||||||
import 'package:cw_core/utils/print_verbose.dart';
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class Bip353Record {
|
class Bip353Record {
|
||||||
Bip353Record({
|
Bip353Record({
|
||||||
|
@ -81,67 +76,4 @@ class Bip353Record {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> pickBip353AddressChoice(
|
|
||||||
String bip353Name,
|
|
||||||
Map<String, String> addressMap,
|
|
||||||
) async {
|
|
||||||
if (addressMap.length == 1) {
|
|
||||||
return addressMap.values.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
final chosenAddress = await _showAddressChoiceDialog(bip353Name, addressMap);
|
|
||||||
|
|
||||||
return chosenAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<String?> _showAddressChoiceDialog(
|
|
||||||
String bip353Name,
|
|
||||||
Map<String, String> addressMap,
|
|
||||||
) async {
|
|
||||||
final entriesList = addressMap.entries.toList();
|
|
||||||
final List<Map<String, String>> 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<void>(
|
|
||||||
// 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<String, String> 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';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||||
|
|
||||||
class FioAddressProvider {
|
class FioAddressProvider {
|
||||||
|
@ -23,14 +24,13 @@ class FioAddressProvider {
|
||||||
return isFioRegistered;
|
return isFioRegistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
isFioRegistered = responseJSON['is_registered'] as int == 1;
|
isFioRegistered = responseJSON['is_registered'] as int == 1;
|
||||||
|
|
||||||
return isFioRegistered;
|
return isFioRegistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<String> getPubAddress(String fioAddress, String token) async {
|
static Future<String?> getPubAddress(String fioAddress, String token) async {
|
||||||
final headers = {'Content-Type': 'application/json'};
|
final headers = {'Content-Type': 'application/json'};
|
||||||
final body = <String, String>{
|
final body = <String, String>{
|
||||||
"fio_address": fioAddress,
|
"fio_address": fioAddress,
|
||||||
|
@ -45,21 +45,28 @@ class FioAddressProvider {
|
||||||
body: json.encode(body),
|
body: json.encode(body),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
if (response.statusCode == 400) {
|
if (response.statusCode == 400) {
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
final error = responseJSON['error'] as String;
|
final error = responseJSON['error'] as String;
|
||||||
final message = responseJSON['message'] as String;
|
final message = responseJSON['message'] as String;
|
||||||
throw Exception('${error}\n$message');
|
printV('${error}\n$message');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
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<String, dynamic>;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,8 @@ class AddressResolverService {
|
||||||
LookupEntry(
|
LookupEntry(
|
||||||
source: AddressSource.fio,
|
source: AddressSource.fio,
|
||||||
currencies: AddressSource.fio.supportedCurrencies,
|
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
|
// FIO handle example: username@domain
|
||||||
run: _lookupFio,
|
run: _lookupFio,
|
||||||
),
|
),
|
||||||
|
@ -236,7 +237,7 @@ class AddressResolverService {
|
||||||
LookupEntry(
|
LookupEntry(
|
||||||
source: AddressSource.bip353,
|
source: AddressSource.bip353,
|
||||||
currencies: AddressSource.bip353.supportedCurrencies,
|
currencies: AddressSource.bip353.supportedCurrencies,
|
||||||
applies: (q) => settingsStore.lookupsBip353,
|
applies: (q) => settingsStore.lookupsBip353 && q.contains('@') && q.contains('.'),
|
||||||
run: _lookupsBip353,
|
run: _lookupsBip353,
|
||||||
),
|
),
|
||||||
LookupEntry(
|
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({
|
static String? extractAddressByType({
|
||||||
required String raw,
|
required String raw,
|
||||||
|
@ -279,21 +277,21 @@ class AddressResolverService {
|
||||||
}) {
|
}) {
|
||||||
final pat = AddressValidator.getAddressFromStringPattern(type);
|
final pat = AddressValidator.getAddressFromStringPattern(type);
|
||||||
if (pat == null) {
|
if (pat == null) {
|
||||||
throw StateError('Unknown pattern for $type');
|
printV('Unknown pattern for $type');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final text = _cleanInput(raw);
|
final text = _cleanInput(raw);
|
||||||
|
|
||||||
final regex = RegExp(r'(?:^|[^0-9A-Za-z])(' + pat + r')',
|
final regex =
|
||||||
multiLine: true, caseSensitive: false);
|
RegExp(r'(?:^|[^0-9A-Za-z])(' + pat + r')', multiLine: true, caseSensitive: false);
|
||||||
|
|
||||||
final m = regex.firstMatch(text);
|
final m = regex.firstMatch(text);
|
||||||
if (m == null) return null;
|
if (m == null) return null;
|
||||||
|
|
||||||
// 3. Strip BCH / NANO prefixes and punctuation just like before
|
// 3. Strip BCH / NANO prefixes and punctuation just like before
|
||||||
final cleaned = m
|
final cleaned =
|
||||||
.group(1)!
|
m.group(1)!.replaceAllMapped(RegExp(r'[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'), (m) {
|
||||||
.replaceAllMapped(RegExp(r'[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'), (m) {
|
|
||||||
final g = m.group(0)!;
|
final g = m.group(0)!;
|
||||||
return (g.startsWith('bitcoincash:') || g.startsWith('nano_') || g.startsWith('ban_'))
|
return (g.startsWith('bitcoincash:') || g.startsWith('nano_') || g.startsWith('ban_'))
|
||||||
? g
|
? g
|
||||||
|
@ -349,15 +347,21 @@ class AddressResolverService {
|
||||||
|
|
||||||
final Map<CryptoCurrency, String> result = {};
|
final Map<CryptoCurrency, String> result = {};
|
||||||
|
|
||||||
|
try {
|
||||||
for (final cur in currencies) {
|
for (final cur in currencies) {
|
||||||
final addressFromBio = extractAddressByType(
|
final addressFromBio = extractAddressByType(
|
||||||
raw: twitterUser.description, type: CryptoCurrency.fromString(cur.title));
|
raw: twitterUser.description, type: CryptoCurrency.fromString(cur.title));
|
||||||
|
print('Address from bio: $addressFromBio');
|
||||||
|
|
||||||
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||||
result[cur] = addressFromBio;
|
result[cur] = addressFromBio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printV('Error extracting address from Twitter bio: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
final pinnedTweet = twitterUser.pinnedTweet?.text;
|
final pinnedTweet = twitterUser.pinnedTweet?.text;
|
||||||
if (pinnedTweet != null) {
|
if (pinnedTweet != null) {
|
||||||
for (final cur in currencies) {
|
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) {
|
if (result.isNotEmpty) {
|
||||||
return ParsedAddress(
|
return ParsedAddress(
|
||||||
|
@ -472,7 +479,7 @@ class AddressResolverService {
|
||||||
|
|
||||||
for (final cur in currencies) {
|
for (final cur in currencies) {
|
||||||
final address = await FioAddressProvider.getPubAddress(text, cur.title);
|
final address = await FioAddressProvider.getPubAddress(text, cur.title);
|
||||||
if (address.isNotEmpty) {
|
if (address != null && address.isNotEmpty) {
|
||||||
result[cur] = address;
|
result[cur] = address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -556,18 +563,24 @@ class AddressResolverService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ParsedAddress?> _lookupsBip353(
|
Future<ParsedAddress?> _lookupsBip353(
|
||||||
String text, List<CryptoCurrency> currency, WalletBase _) async {
|
String text, List<CryptoCurrency> currencies, WalletBase _) async {
|
||||||
final Map<CryptoCurrency, String> result = {};
|
final Map<CryptoCurrency, String> result = {};
|
||||||
|
|
||||||
for (final cur in currency) {
|
for (final cur in currencies) {
|
||||||
final bip353AddressMap = await Bip353Record.fetchUriByCryptoCurrency(text, cur.title);
|
final bip353AddressMap = await Bip353Record.fetchUriByCryptoCurrency(text, cur.title);
|
||||||
|
|
||||||
if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) {
|
if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) {
|
||||||
final address = bip353AddressMap['address'];
|
if (cur == CryptoCurrency.btc) {
|
||||||
if (address != null && address.isNotEmpty) {
|
bip353AddressMap.forEach((key, value) {
|
||||||
result[cur] = address;
|
final address = bip353AddressMap['sp'] ?? bip353AddressMap['address'];
|
||||||
|
if (address != null && address.isNotEmpty) {
|
||||||
|
result[cur] = address;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.isNotEmpty) {
|
if (result.isNotEmpty) {
|
||||||
return ParsedAddress(
|
return ParsedAddress(
|
||||||
parsedAddressByCurrencyMap: result,
|
parsedAddressByCurrencyMap: result,
|
||||||
|
@ -575,15 +588,6 @@ class AddressResolverService {
|
||||||
handle: text,
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@ import 'package:cw_core/crypto_currency.dart';
|
||||||
const supportedSources = [
|
const supportedSources = [
|
||||||
AddressSource.twitter,
|
AddressSource.twitter,
|
||||||
AddressSource.unstoppableDomains,
|
AddressSource.unstoppableDomains,
|
||||||
AddressSource.ens,
|
|
||||||
AddressSource.mastodon,
|
AddressSource.mastodon,
|
||||||
|
AddressSource.bip353,
|
||||||
|
AddressSource.fio,
|
||||||
];
|
];
|
||||||
|
|
||||||
enum AddressSource {
|
enum AddressSource {
|
||||||
|
@ -14,7 +15,15 @@ enum AddressSource {
|
||||||
label: 'X',
|
label: 'X',
|
||||||
iconPath: 'assets/images/x_social.png',
|
iconPath: 'assets/images/x_social.png',
|
||||||
alias: '@username',
|
alias: '@username',
|
||||||
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]),
|
supportedCurrencies: [
|
||||||
|
CryptoCurrency.xmr,
|
||||||
|
CryptoCurrency.btc,
|
||||||
|
CryptoCurrency.ltc,
|
||||||
|
CryptoCurrency.eth,
|
||||||
|
CryptoCurrency.bnb,
|
||||||
|
CryptoCurrency.sol,
|
||||||
|
CryptoCurrency.maticpoly
|
||||||
|
]),
|
||||||
unstoppableDomains(
|
unstoppableDomains(
|
||||||
label: 'Unstoppable Domains',
|
label: 'Unstoppable Domains',
|
||||||
iconPath: 'assets/images/ud.png',
|
iconPath: 'assets/images/ud.png',
|
||||||
|
@ -30,19 +39,33 @@ enum AddressSource {
|
||||||
iconPath: 'assets/images/yat_mini_logo.png',
|
iconPath: 'assets/images/yat_mini_logo.png',
|
||||||
alias: '🎂🎂🎂',
|
alias: '🎂🎂🎂',
|
||||||
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]),
|
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]),
|
||||||
fio(
|
fio(label: 'FIO', iconPath: 'assets/images/fio.png', alias: 'user@domain', supportedCurrencies: [
|
||||||
label: 'FIO',
|
CryptoCurrency.xmr,
|
||||||
iconPath: 'assets/images/fio.png',
|
CryptoCurrency.btc,
|
||||||
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]),
|
CryptoCurrency.ltc,
|
||||||
|
CryptoCurrency.eth,
|
||||||
|
CryptoCurrency.bnb,
|
||||||
|
CryptoCurrency.sol,
|
||||||
|
CryptoCurrency.maticpoly,
|
||||||
|
]),
|
||||||
ens(
|
ens(
|
||||||
label: 'Ethereum Name Service',
|
label: 'Ethereum Name Service',
|
||||||
iconPath: 'assets/images/ens_icon.png',
|
iconPath: 'assets/images/ens_icon.png',
|
||||||
|
alias: 'domain.eth',
|
||||||
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc, CryptoCurrency.eth]),
|
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc, CryptoCurrency.eth]),
|
||||||
mastodon(
|
mastodon(
|
||||||
label: 'Mastodon',
|
label: 'Mastodon',
|
||||||
iconPath: 'assets/images/mastodon.svg',
|
iconPath: 'assets/images/mastodon.svg',
|
||||||
alias: 'user@domain.tld',
|
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(
|
nostr(
|
||||||
label: 'Nostr',
|
label: 'Nostr',
|
||||||
iconPath: 'assets/images/nostr.png',
|
iconPath: 'assets/images/nostr.png',
|
||||||
|
@ -61,8 +84,9 @@ enum AddressSource {
|
||||||
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]),
|
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]),
|
||||||
bip353(
|
bip353(
|
||||||
label: 'BIP-353',
|
label: 'BIP-353',
|
||||||
iconPath: '',
|
iconPath: 'assets/images/btc.png',
|
||||||
supportedCurrencies: [CryptoCurrency.xmr, CryptoCurrency.btc]),
|
alias: 'user@domain.com',
|
||||||
|
supportedCurrencies: [CryptoCurrency.btc]),
|
||||||
contact(label: 'Contact', iconPath: '', supportedCurrencies: []),
|
contact(label: 'Contact', iconPath: '', supportedCurrencies: []),
|
||||||
notParsed(label: 'Unknown', iconPath: '', supportedCurrencies: []);
|
notParsed(label: 'Unknown', iconPath: '', supportedCurrencies: []);
|
||||||
|
|
||||||
|
@ -93,7 +117,7 @@ extension AddressSourceNameParser on AddressSource {
|
||||||
}
|
}
|
||||||
final needle = text.trim().toLowerCase();
|
final needle = text.trim().toLowerCase();
|
||||||
return AddressSource.values.firstWhere(
|
return AddressSource.values.firstWhere(
|
||||||
(src) => src.label.toLowerCase() == needle,
|
(src) => src.label.toLowerCase() == needle,
|
||||||
orElse: () => AddressSource.notParsed,
|
orElse: () => AddressSource.notParsed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue