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}';
|
||||
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:
|
||||
|
|
|
@ -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<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 '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<String, dynamic>;
|
||||
isFioRegistered = responseJSON['is_registered'] as int == 1;
|
||||
|
||||
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 body = <String, String>{
|
||||
"fio_address": fioAddress,
|
||||
|
@ -45,21 +45,28 @@ class FioAddressProvider {
|
|||
body: json.encode(body),
|
||||
);
|
||||
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode == 400) {
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
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<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(
|
||||
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<CryptoCurrency, String> 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<ParsedAddress?> _lookupsBip353(
|
||||
String text, List<CryptoCurrency> currency, WalletBase _) async {
|
||||
String text, List<CryptoCurrency> currencies, WalletBase _) async {
|
||||
final Map<CryptoCurrency, String> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue