mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-19 00:54:33 +00:00
Merge pull request #935 from cypherstack/uri
Filter unrecognized from extra URI parameters IAW BIP21
This commit is contained in:
commit
dbc5fb7907
9 changed files with 280 additions and 79 deletions
|
@ -304,7 +304,7 @@ class _AddressCardState extends ConsumerState<AddressCard> {
|
||||||
key: _qrKey,
|
key: _qrKey,
|
||||||
child: QR(
|
child: QR(
|
||||||
data: AddressUtils.buildUriString(
|
data: AddressUtils.buildUriString(
|
||||||
widget.coin,
|
widget.coin.uriScheme,
|
||||||
address.value,
|
address.value,
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
|
|
@ -97,7 +97,7 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
||||||
key: _qrKey,
|
key: _qrKey,
|
||||||
child: QR(
|
child: QR(
|
||||||
data: AddressUtils.buildUriString(
|
data: AddressUtils.buildUriString(
|
||||||
ref.watch(pWalletCoin(widget.walletId)),
|
ref.watch(pWalletCoin(widget.walletId)).uriScheme,
|
||||||
address.value,
|
address.value,
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
@ -289,7 +289,7 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
||||||
key: _qrKey,
|
key: _qrKey,
|
||||||
child: QR(
|
child: QR(
|
||||||
data: AddressUtils.buildUriString(
|
data: AddressUtils.buildUriString(
|
||||||
coin,
|
coin.uriScheme,
|
||||||
address.value,
|
address.value,
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
|
|
@ -142,7 +142,7 @@ class _AddressQrPopupState extends State<AddressQrPopup> {
|
||||||
key: _qrKey,
|
key: _qrKey,
|
||||||
child: QR(
|
child: QR(
|
||||||
data: AddressUtils.buildUriString(
|
data: AddressUtils.buildUriString(
|
||||||
widget.coin,
|
widget.coin.uriScheme,
|
||||||
widget.addressString,
|
widget.addressString,
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
|
|
@ -170,7 +170,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final uriString = AddressUtils.buildUriString(
|
final uriString = AddressUtils.buildUriString(
|
||||||
widget.coin,
|
widget.coin.uriScheme,
|
||||||
receivingAddress,
|
receivingAddress,
|
||||||
queryParams,
|
queryParams,
|
||||||
);
|
);
|
||||||
|
@ -263,7 +263,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_uriString = AddressUtils.buildUriString(
|
_uriString = AddressUtils.buildUriString(
|
||||||
widget.coin,
|
widget.coin.uriScheme,
|
||||||
receivingAddress,
|
receivingAddress,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
|
@ -577,7 +577,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
||||||
children: [
|
children: [
|
||||||
QR(
|
QR(
|
||||||
data: AddressUtils.buildUriString(
|
data: AddressUtils.buildUriString(
|
||||||
coin,
|
coin.uriScheme,
|
||||||
address,
|
address,
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
|
|
@ -163,25 +163,23 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
level: LogLevel.Info,
|
level: LogLevel.Info,
|
||||||
);
|
);
|
||||||
|
|
||||||
final results = AddressUtils.parseUri(qrResult.rawContent);
|
final paymentData = AddressUtils.parsePaymentUri(qrResult.rawContent);
|
||||||
|
|
||||||
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
|
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||||
|
|
||||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
|
||||||
// auto fill address
|
// auto fill address
|
||||||
_address = (results["address"] ?? "").trim();
|
_address = paymentData.address.trim();
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
|
||||||
// autofill notes field
|
// autofill notes field
|
||||||
if (results["message"] != null) {
|
if (paymentData.message != null) {
|
||||||
noteController.text = results["message"]!;
|
noteController.text = paymentData.message!;
|
||||||
} else if (results["label"] != null) {
|
} else if (paymentData.label != null) {
|
||||||
noteController.text = results["label"]!;
|
noteController.text = paymentData.label!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// autofill amount field
|
// autofill amount field
|
||||||
if (results["amount"] != null) {
|
if (paymentData.amount != null) {
|
||||||
final Amount amount = Decimal.parse(results["amount"]!).toAmount(
|
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||||
fractionDigits: coin.fractionDigits,
|
fractionDigits: coin.fractionDigits,
|
||||||
);
|
);
|
||||||
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
||||||
|
@ -1071,7 +1069,7 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
_address = _address!.substring(0, _address!.indexOf("\n"));
|
_address = _address!.substring(0, _address!.indexOf("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToController.text = formatAddress(_address!);
|
sendToController.text = AddressUtils().formatAddress(_address!);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1402,7 +1400,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
|
|
||||||
if (coin is Epiccash) {
|
if (coin is Epiccash) {
|
||||||
// strip http:// and https:// if content contains @
|
// strip http:// and https:// if content contains @
|
||||||
content = formatAddress(
|
content = AddressUtils()
|
||||||
|
.formatAddress(
|
||||||
content,
|
content,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2421,22 +2420,3 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String formatAddress(String epicAddress) {
|
|
||||||
// strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an epicbox address)
|
|
||||||
if ((epicAddress.startsWith("http://") ||
|
|
||||||
epicAddress.startsWith("https://")) &&
|
|
||||||
epicAddress.contains("@")) {
|
|
||||||
epicAddress = epicAddress.replaceAll("http://", "");
|
|
||||||
epicAddress = epicAddress.replaceAll("https://", "");
|
|
||||||
}
|
|
||||||
// strip mailto: prefix
|
|
||||||
if (epicAddress.startsWith("mailto:")) {
|
|
||||||
epicAddress = epicAddress.replaceAll("mailto:", "");
|
|
||||||
}
|
|
||||||
// strip / suffix if the address contains an @ symbol (and is thus an epicbox address)
|
|
||||||
if (epicAddress.endsWith("/") && epicAddress.contains("@")) {
|
|
||||||
epicAddress = epicAddress.substring(0, epicAddress.length - 1);
|
|
||||||
}
|
|
||||||
return epicAddress;
|
|
||||||
}
|
|
||||||
|
|
|
@ -448,7 +448,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
||||||
Center(
|
Center(
|
||||||
child: QR(
|
child: QR(
|
||||||
data: AddressUtils.buildUriString(
|
data: AddressUtils.buildUriString(
|
||||||
coin,
|
coin.uriScheme,
|
||||||
address,
|
address,
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
|
|
@ -730,13 +730,15 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
|
|
||||||
void _processQrCodeData(String qrCodeData) {
|
void _processQrCodeData(String qrCodeData) {
|
||||||
try {
|
try {
|
||||||
var results = AddressUtils.parseUri(qrCodeData);
|
final paymentData = AddressUtils.parsePaymentUri(qrCodeData);
|
||||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||||
_address = (results["address"] ?? "").trim();
|
// Auto fill address.
|
||||||
|
_address = paymentData.address.trim();
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
|
||||||
if (results["amount"] != null) {
|
// Amount.
|
||||||
final Amount amount = Decimal.parse(results["amount"]!).toAmount(
|
if (paymentData.amount != null) {
|
||||||
|
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||||
fractionDigits: coin.fractionDigits,
|
fractionDigits: coin.fractionDigits,
|
||||||
);
|
);
|
||||||
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
||||||
|
@ -746,6 +748,13 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
ref.read(pSendAmount.notifier).state = amount;
|
ref.read(pSendAmount.notifier).state = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note/message.
|
||||||
|
if (paymentData.message != null) {
|
||||||
|
_note = paymentData.message;
|
||||||
|
} else if (paymentData.label != null) {
|
||||||
|
_note = paymentData.label;
|
||||||
|
}
|
||||||
|
|
||||||
_setValidAddressProviders(_address);
|
_setValidAddressProviders(_address);
|
||||||
setState(() {
|
setState(() {
|
||||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
|
@ -796,18 +805,65 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
content = content.substring(0, content.indexOf("\n"));
|
content = content.substring(0, content.indexOf("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coin is Epiccash) {
|
try {
|
||||||
// strip http:// and https:// if content contains @
|
final paymentData = AddressUtils.parsePaymentUri(content);
|
||||||
content = formatAddress(content);
|
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||||
|
// auto fill address
|
||||||
|
_address = paymentData.address;
|
||||||
|
sendToController.text = _address!;
|
||||||
|
|
||||||
|
// autofill notes field.
|
||||||
|
if (paymentData.message != null) {
|
||||||
|
_note = paymentData.message;
|
||||||
|
} else if (paymentData.label != null) {
|
||||||
|
_note = paymentData.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// autofill amoutn field
|
||||||
|
if (paymentData.amount != null) {
|
||||||
|
final amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||||
|
fractionDigits: coin.fractionDigits,
|
||||||
|
);
|
||||||
|
cryptoAmountController.text = ref
|
||||||
|
.read(pAmountFormatter(coin))
|
||||||
|
.format(amount, withUnitName: false);
|
||||||
|
ref.read(pSendAmount.notifier).state = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger validation after pasting.
|
||||||
|
_setValidAddressProviders(_address);
|
||||||
|
setState(() {
|
||||||
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (coin is Epiccash) {
|
||||||
|
content = AddressUtils().formatAddress(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToController.text = content;
|
||||||
|
_address = content;
|
||||||
|
|
||||||
|
_setValidAddressProviders(_address);
|
||||||
|
setState(() {
|
||||||
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, treat it as a plain address.
|
||||||
|
if (coin is Epiccash) {
|
||||||
|
// strip http:// and https:// if content contains @
|
||||||
|
content = AddressUtils().formatAddress(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToController.text = content;
|
||||||
|
_address = content;
|
||||||
|
|
||||||
|
// Trigger validation after pasting.
|
||||||
|
_setValidAddressProviders(_address);
|
||||||
|
setState(() {
|
||||||
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToController.text = content;
|
|
||||||
_address = content;
|
|
||||||
|
|
||||||
_setValidAddressProviders(_address);
|
|
||||||
setState(() {
|
|
||||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,21 @@
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'logger.dart';
|
import '../app_config.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
|
||||||
class AddressUtils {
|
class AddressUtils {
|
||||||
|
static final Set<String> recognizedParams = {
|
||||||
|
'amount',
|
||||||
|
'label',
|
||||||
|
'message',
|
||||||
|
'tx_amount', // For Monero/Wownero.
|
||||||
|
'tx_payment_id',
|
||||||
|
'recipient_name',
|
||||||
|
'tx_description',
|
||||||
|
// TODO [prio=med]: Add more recognized params for other coins.
|
||||||
|
};
|
||||||
|
|
||||||
static String condenseAddress(String address) {
|
static String condenseAddress(String address) {
|
||||||
return '${address.substring(0, 5)}...${address.substring(address.length - 5)}';
|
return '${address.substring(0, 5)}...${address.substring(address.length - 5)}';
|
||||||
}
|
}
|
||||||
|
@ -170,42 +181,150 @@ class AddressUtils {
|
||||||
// // }
|
// // }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/// parse an address uri
|
/// Return only recognized parameters.
|
||||||
/// returns an empty map if the input string does not begin with "firo:"
|
static Map<String, String> filterParams(Map<String, String> params) {
|
||||||
|
return Map.fromEntries(params.entries
|
||||||
|
.where((entry) => recognizedParams.contains(entry.key.toLowerCase())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a URI string and returns a map with parsed components.
|
||||||
static Map<String, String> parseUri(String uri) {
|
static Map<String, String> parseUri(String uri) {
|
||||||
final Map<String, String> result = {};
|
final Map<String, String> result = {};
|
||||||
try {
|
try {
|
||||||
final u = Uri.parse(uri);
|
final u = Uri.parse(uri);
|
||||||
if (u.hasScheme) {
|
if (u.hasScheme) {
|
||||||
result["scheme"] = u.scheme.toLowerCase();
|
result["scheme"] = u.scheme.toLowerCase();
|
||||||
result["address"] = u.path;
|
|
||||||
result.addAll(u.queryParameters);
|
// Handle different URI formats.
|
||||||
|
if (result["scheme"] == "bitcoin" ||
|
||||||
|
result["scheme"] == "bitcoincash") {
|
||||||
|
result["address"] = u.path;
|
||||||
|
} else if (result["scheme"] == "monero") {
|
||||||
|
// Monero addresses can contain '?' which Uri.parse interprets as query start.
|
||||||
|
final addressEnd =
|
||||||
|
uri.indexOf('?', 7); // 7 is the length of "monero:".
|
||||||
|
if (addressEnd != -1) {
|
||||||
|
result["address"] = uri.substring(7, addressEnd);
|
||||||
|
} else {
|
||||||
|
result["address"] = uri.substring(7);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default case, treat path as address.
|
||||||
|
result["address"] = u.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse query parameters.
|
||||||
|
result.addAll(_parseQueryParameters(u.queryParameters));
|
||||||
|
|
||||||
|
// Handle Monero-specific fragment (tx_description).
|
||||||
|
if (u.fragment.isNotEmpty && result["scheme"] == "monero") {
|
||||||
|
result["tx_description"] = Uri.decodeComponent(u.fragment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logging.instance
|
print("Exception caught in parseUri($uri): $e");
|
||||||
.log("Exception caught in parseUri($uri): $e", level: LogLevel.Error);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// builds a uri string with the given address and query parameters if any
|
/// Helper method to parse and normalize query parameters.
|
||||||
|
static Map<String, String> _parseQueryParameters(Map<String, String> params) {
|
||||||
|
final Map<String, String> result = {};
|
||||||
|
params.forEach((key, value) {
|
||||||
|
final lowerKey = key.toLowerCase();
|
||||||
|
if (recognizedParams.contains(lowerKey)) {
|
||||||
|
switch (lowerKey) {
|
||||||
|
case 'amount':
|
||||||
|
case 'tx_amount':
|
||||||
|
result['amount'] = _normalizeAmount(value);
|
||||||
|
break;
|
||||||
|
case 'label':
|
||||||
|
case 'recipient_name':
|
||||||
|
result['label'] = Uri.decodeComponent(value);
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
case 'tx_description':
|
||||||
|
result['message'] = Uri.decodeComponent(value);
|
||||||
|
break;
|
||||||
|
case 'tx_payment_id':
|
||||||
|
result['tx_payment_id'] = Uri.decodeComponent(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result[lowerKey] = Uri.decodeComponent(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Include unrecognized parameters as-is.
|
||||||
|
result[key] = Uri.decodeComponent(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes amount value to a standard format.
|
||||||
|
static String _normalizeAmount(String amount) {
|
||||||
|
// Remove any non-numeric characters except for '.'
|
||||||
|
final sanitized = amount.replaceAll(RegExp(r'[^\d.]'), '');
|
||||||
|
// Ensure only one decimal point
|
||||||
|
final parts = sanitized.split('.');
|
||||||
|
if (parts.length > 2) {
|
||||||
|
return '${parts[0]}.${parts.sublist(1).join()}';
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Centralized method to handle various cryptocurrency URIs and return a common object.
|
||||||
|
static PaymentUriData parsePaymentUri(String uri) {
|
||||||
|
final Map<String, String> parsedData = parseUri(uri);
|
||||||
|
|
||||||
|
// Normalize the URI scheme.
|
||||||
|
final String scheme = parsedData['scheme'] ?? '';
|
||||||
|
parsedData.remove('scheme');
|
||||||
|
|
||||||
|
// Determine the coin type based on the URI scheme.
|
||||||
|
final CryptoCurrency coin = _getCryptoCurrencyByScheme(scheme);
|
||||||
|
|
||||||
|
// Filter out unrecognized parameters.
|
||||||
|
final filteredParams = filterParams(parsedData);
|
||||||
|
|
||||||
|
return PaymentUriData(
|
||||||
|
coin: coin,
|
||||||
|
address: parsedData['address'] ?? '',
|
||||||
|
amount: filteredParams['amount'] ?? filteredParams['tx_amount'],
|
||||||
|
label: filteredParams['label'] ?? filteredParams['recipient_name'],
|
||||||
|
message: filteredParams['message'] ?? filteredParams['tx_description'],
|
||||||
|
paymentId: filteredParams['tx_payment_id'], // Specific to Monero
|
||||||
|
additionalParams: filteredParams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a uri string with the given address and query parameters (if any)
|
||||||
static String buildUriString(
|
static String buildUriString(
|
||||||
CryptoCurrency coin,
|
String scheme,
|
||||||
String address,
|
String address,
|
||||||
Map<String, String> params,
|
Map<String, String> params,
|
||||||
) {
|
) {
|
||||||
// TODO: other sanitation as well ?
|
// Filter unrecognized parameters.
|
||||||
String sanitizedAddress = address;
|
final filteredParams = filterParams(params);
|
||||||
if (coin is Bitcoincash || coin is Ecash) {
|
String uriString = "$scheme:$address";
|
||||||
final prefix = "${coin.uriScheme}:";
|
|
||||||
if (address.startsWith(prefix)) {
|
if (scheme.toLowerCase() == "monero") {
|
||||||
sanitizedAddress = address.replaceFirst(prefix, "");
|
// Handle Monero-specific formatting.
|
||||||
|
if (filteredParams.containsKey("tx_description")) {
|
||||||
|
final description = filteredParams.remove("tx_description")!;
|
||||||
|
if (filteredParams.isNotEmpty) {
|
||||||
|
uriString += Uri(queryParameters: filteredParams).toString();
|
||||||
|
}
|
||||||
|
uriString += "#${Uri.encodeComponent(description)}";
|
||||||
|
} else if (filteredParams.isNotEmpty) {
|
||||||
|
uriString += Uri(queryParameters: filteredParams).toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// General case for other cryptocurrencies.
|
||||||
|
if (filteredParams.isNotEmpty) {
|
||||||
|
uriString += Uri(queryParameters: filteredParams).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String uriString = "${coin.uriScheme}:$sanitizedAddress";
|
|
||||||
if (params.isNotEmpty) {
|
|
||||||
uriString += Uri(queryParameters: params).toString();
|
|
||||||
}
|
|
||||||
return uriString;
|
return uriString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,10 +334,7 @@ class AddressUtils {
|
||||||
try {
|
try {
|
||||||
result = Map<String, dynamic>.from(jsonDecode(data) as Map);
|
result = Map<String, dynamic>.from(jsonDecode(data) as Map);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logging.instance.log(
|
print("Exception caught in parseQRSeedData($data): $e");
|
||||||
"Exception caught in parseQRSeedData($data): $e",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -227,4 +343,53 @@ class AddressUtils {
|
||||||
static String encodeQRSeedData(List<String> words) {
|
static String encodeQRSeedData(List<String> words) {
|
||||||
return jsonEncode({"mnemonic": words});
|
return jsonEncode({"mnemonic": words});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Method to get CryptoCurrency based on URI scheme.
|
||||||
|
static CryptoCurrency _getCryptoCurrencyByScheme(String scheme) {
|
||||||
|
if (AppConfig.coins.map((e) => e.uriScheme).toSet().contains(scheme)) {
|
||||||
|
return AppConfig.coins.firstWhere((e) => e.uriScheme == scheme);
|
||||||
|
} else {
|
||||||
|
throw UnsupportedError('Unsupported URI scheme: $scheme');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats an address string to remove any unnecessary prefixes or suffixes.
|
||||||
|
String formatAddress(String epicAddress) {
|
||||||
|
// strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an epicbox address)
|
||||||
|
if ((epicAddress.startsWith("http://") ||
|
||||||
|
epicAddress.startsWith("https://")) &&
|
||||||
|
epicAddress.contains("@")) {
|
||||||
|
epicAddress = epicAddress.replaceAll("http://", "");
|
||||||
|
epicAddress = epicAddress.replaceAll("https://", "");
|
||||||
|
}
|
||||||
|
// strip mailto: prefix
|
||||||
|
if (epicAddress.startsWith("mailto:")) {
|
||||||
|
epicAddress = epicAddress.replaceAll("mailto:", "");
|
||||||
|
}
|
||||||
|
// strip / suffix if the address contains an @ symbol (and is thus an epicbox address)
|
||||||
|
if (epicAddress.endsWith("/") && epicAddress.contains("@")) {
|
||||||
|
epicAddress = epicAddress.substring(0, epicAddress.length - 1);
|
||||||
|
}
|
||||||
|
return epicAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentUriData {
|
||||||
|
final CryptoCurrency coin;
|
||||||
|
final String address;
|
||||||
|
final String? amount;
|
||||||
|
final String? label;
|
||||||
|
final String? message;
|
||||||
|
final String? paymentId; // Specific to Monero.
|
||||||
|
final Map<String, String> additionalParams;
|
||||||
|
|
||||||
|
PaymentUriData({
|
||||||
|
required this.coin,
|
||||||
|
required this.address,
|
||||||
|
this.amount,
|
||||||
|
this.label,
|
||||||
|
this.message,
|
||||||
|
this.paymentId,
|
||||||
|
required this.additionalParams,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue