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({ required this.uri, required this.domain, }); final String uri; final String domain; static const Map keyDisplayMap = { 'lno': 'BOLT 12 Offer', 'sp': 'Silent Payment', 'address': 'On-Chain Address', }; static Future?> fetchUriByCryptoCurrency( String bip353Name, String asset) async { try { // 1. Parse the user and domain from "user@domain" final parts = bip353Name.split('@'); if (parts.length != 2) return null; final userPart = parts[0]; final domainPart = parts[1]; // 2. Construct the correct subdomain: "user._bitcoin-payment.domain" final bip353Domain = '$userPart.user._bitcoin-payment.$domainPart'; // 3. Lookup the TXT record with DNSSEC final txtRecords = await DnsUtils.lookupRecord( bip353Domain, RRecordType.TXT, dnssec: true, ); if (txtRecords == null) return null; final assetName = CryptoCurrency.fromString(asset).fullName; if (assetName == null) throw Exception('Invalid asset name'); final formattedAssetName = assetName.toLowerCase().replaceAll(' ', '') + ':'; for (final record in txtRecords) { final data = record.data.replaceAll('"', ''); if (data.startsWith(formattedAssetName)) { return _parseAssetUri(data, formattedAssetName); } } } catch (e) { printV('BIP353Record.fetchBitcoinUri error: $e'); } return null; } static Map? _parseAssetUri(String fullUri, String prefix) { final afterPrefix = fullUri.substring(prefix.length); if (afterPrefix.isEmpty) return null; final questionIndex = afterPrefix.indexOf('?'); if (questionIndex == -1) { return {'address': afterPrefix}; } else { final addressPart = afterPrefix.substring(0, questionIndex); final queryPart = afterPrefix.substring(questionIndex + 1); final result = {}; if (addressPart.isNotEmpty) result['address'] = addressPart; final queryMap = Uri.splitQueryString(queryPart); result.addAll(queryMap); return result; } } static Future pickBip353AddressChoice( BuildContext context, String bip353Name, Map addressMap, ) async { if (addressMap.length == 1) { return addressMap.values.first; } final chosenAddress = await _showAddressChoiceDialog(context, bip353Name, addressMap); return chosenAddress; } static Future _showAddressChoiceDialog( BuildContext context, 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; } 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'; } }