diff --git a/cw_core/lib/payment_uris.dart b/cw_core/lib/payment_uris.dart index 2703d5e21..43d452f8d 100644 --- a/cw_core/lib/payment_uris.dart +++ b/cw_core/lib/payment_uris.dart @@ -1,4 +1,4 @@ -import 'package:cw_core/format_fixed.dart'; +import "package:cw_core/format_fixed.dart"; abstract class PaymentURI { PaymentURI({required this.amount, required this.address}); @@ -19,7 +19,7 @@ class MoneroURI extends PaymentURI { @override String toString() { - var base = 'monero:$address'; + var base = "monero:$address"; if (amount.isNotEmpty) { base += '?tx_amount=${amount.replaceAll(',', '.')}'; @@ -34,7 +34,7 @@ class HavenURI extends PaymentURI { @override String toString() { - var base = 'haven:$address'; + var base = "haven:$address"; if (amount.isNotEmpty) { base += '?tx_amount=${amount.replaceAll(',', '.')}'; @@ -45,7 +45,7 @@ class HavenURI extends PaymentURI { } class BitcoinURI extends PaymentURI { - BitcoinURI({required super.amount, required super.address, this.pjUri = ''}); + BitcoinURI({required super.amount, required super.address, this.pjUri = ""}); final String pjUri; @@ -53,13 +53,16 @@ class BitcoinURI extends PaymentURI { String toString() { final qp = {}; - if (amount.isNotEmpty) qp['amount'] = amount.replaceAll(',', '.'); - if (pjUri.isNotEmpty && !address.startsWith("sp")) { - qp['pjos'] = '0'; - qp['pj'] = pjUri; + if (amount.isNotEmpty) { + qp["amount"] = amount.replaceAll(",", "."); } - return Uri(scheme: 'bitcoin', path: address, queryParameters: qp).toString(); + if (pjUri.isNotEmpty && !address.startsWith("sp")) { + qp["pjos"] = "0"; + qp["pj"] = pjUri; + } + + return Uri(scheme: "bitcoin", path: address, queryParameters: qp).toString(); } } @@ -68,7 +71,7 @@ class LitecoinURI extends PaymentURI { @override String toString() { - var base = 'litecoin:$address'; + var base = "litecoin:$address"; if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; @@ -83,52 +86,7 @@ class EthereumURI extends PaymentURI { @override String toString() { - var base = 'ethereum:$address'; - - if (amount.isNotEmpty) { - base += '?amount=${amount.replaceAll(',', '.')}'; - } - - return base; - } -} - -class BaseURI extends PaymentURI { - BaseURI({required super.amount, required super.address}); - - @override - String toString() { - var base = 'base:$address'; - - if (amount.isNotEmpty) { - base += '?amount=${amount.replaceAll(',', '.')}'; - } - - return base; - } -} - -class ArbitrumURI extends PaymentURI { - ArbitrumURI({required super.amount, required super.address}); - - @override - String toString() { - var base = 'arbitrum:$address'; - - if (amount.isNotEmpty) { - base += '?amount=${amount.replaceAll(',', '.')}'; - } - - return base; - } -} - -class BSCURI extends PaymentURI { - BSCURI({required super.amount, required super.address}); - - @override - String toString() { - var base = 'bsc:$address'; + var base = "ethereum:$address"; if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; @@ -158,22 +116,7 @@ class NanoURI extends PaymentURI { @override String toString() { - var base = 'nano:$address'; - if (amount.isNotEmpty) { - base += '?amount=${amount.replaceAll(',', '.')}'; - } - - return base; - } -} - -class PolygonURI extends PaymentURI { - PolygonURI({required super.amount, required super.address}); - - @override - String toString() { - var base = 'polygon:$address'; - + var base = "nano:$address"; if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; } @@ -189,14 +132,14 @@ class SolanaURI extends PaymentURI { @override String toString() { - var base = 'solana:$address'; + var base = "solana:$address"; final params = []; if (amount.isNotEmpty) { params.add('amount=${amount.replaceAll(',', '.')}'); } if (contractAddress != null && contractAddress!.isNotEmpty) { - params.add('spl-token=$contractAddress'); + params.add("spl-token=$contractAddress"); } if (params.isNotEmpty) { base += '?${params.join('&')}'; @@ -213,14 +156,14 @@ class TronURI extends PaymentURI { @override String toString() { - var base = 'tron:$address'; + var base = "tron:$address"; final params = []; if (amount.isNotEmpty) { params.add('amount=${amount.replaceAll(',', '.')}'); } if (contractAddress != null && contractAddress!.isNotEmpty) { - params.add('token=$contractAddress'); + params.add("token=$contractAddress"); } if (params.isNotEmpty) { base += '?${params.join('&')}'; @@ -235,7 +178,7 @@ class WowneroURI extends PaymentURI { @override String toString() { - var base = 'wownero:$address'; + var base = "wownero:$address"; if (amount.isNotEmpty) { base += '?tx_amount=${amount.replaceAll(',', '.')}'; @@ -246,12 +189,11 @@ class WowneroURI extends PaymentURI { } class ZanoURI extends PaymentURI { - ZanoURI({required String amount, required String address}) - : super(amount: amount, address: address); + ZanoURI({required super.amount, required super.address}); @override String toString() { - var base = 'zano:' + address; + var base = "zano:$address"; if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; @@ -262,12 +204,11 @@ class ZanoURI extends PaymentURI { } class DecredURI extends PaymentURI { - DecredURI({required String amount, required String address}) - : super(amount: amount, address: address); + DecredURI({required super.amount, required super.address}); @override String toString() { - var base = 'decred:' + address; + var base = "decred:$address"; if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; @@ -278,12 +219,11 @@ class DecredURI extends PaymentURI { } class DogeURI extends PaymentURI { - DogeURI({required String amount, required String address}) - : super(amount: amount, address: address); + DogeURI({required super.amount, required super.address}); @override String toString() { - var base = 'doge:' + address; + var base = "doge:$address"; if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; @@ -298,7 +238,7 @@ class ZcashURI extends PaymentURI { @override String toString() { - var base = 'zcash:$address'; + var base = "zcash:$address"; if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; @@ -309,9 +249,6 @@ class ZcashURI extends PaymentURI { } class ERC681URI extends PaymentURI { - final int chainId; - final String? contractAddress; - ERC681URI({ required this.chainId, required super.address, @@ -319,74 +256,11 @@ class ERC681URI extends PaymentURI { required this.contractAddress, }); - @override - String toString() { - var uri = 'ethereum:'; - - final targetAddress = contractAddress ?? address; - uri += targetAddress; - - if (chainId != 1) { - uri += '@$chainId'; - } - - if (contractAddress != null) { - uri += '/transfer'; - } - - final params = {}; - - if (contractAddress != null) { - params['address'] = address; - if (amount.isNotEmpty) { - params['uint256'] = _formatAmountForERC20(amount); - } - } else { - if (amount.isNotEmpty) { - params['value'] = _formatAmountForNative(amount); - } - } - - if (params.isNotEmpty) { - uri += '?'; - uri += params.entries.map((e) => '${e.key}=${e.value}').join('&'); - } - - return uri; - } - - /// Formats amount for ERC-20 token transfers (in atomic units) - String _formatAmountForERC20(String amount) { - try { - // Convert decimal amount to BigInt (assuming 18 decimals) - final amountDouble = double.parse(amount.replaceAll(',', '.')); - final amountBigInt = BigInt.from(amountDouble * 1e18); - return amountBigInt.toString(); - } catch (e) { - // Fallback to original amount if parsing fails - return amount.replaceAll(',', '.'); - } - } - - /// Formats amount for native ETH payments (in wei using scientific notation) - String _formatAmountForNative(String amount) { - try { - // Convert decimal amount to double for scientific notation - final amountDouble = double.parse(amount.replaceAll(',', '.')); - - // Use scientific notation as recommended by ERC-681 - return '${amountDouble}e18'; - } catch (e) { - // Fallback to original amount if parsing fails - return amount.replaceAll(',', '.'); - } - } - factory ERC681URI.fromUri(Uri uri) { final (isContract, targetAddress) = _getTargetAddress(uri.path); final chainId = _getChainID(uri.path); - final address = isContract ? uri.queryParameters["address"] ?? '' : targetAddress; + final address = isContract ? uri.queryParameters["address"] ?? "" : targetAddress; final amountParam = isContract ? uri.queryParameters["uint256"] : uri.queryParameters["value"]; var formatedAmount = ""; @@ -406,16 +280,78 @@ class ERC681URI extends PaymentURI { ); } - static int _getChainID(String path) { - return int.parse(RegExp( - r'@\d*', - ).firstMatch(path)?.group(0)?.replaceAll("@", "") ?? - "1"); + final int chainId; + final String? contractAddress; + + @override + String toString() { + var uri = "ethereum:"; + + final targetAddress = contractAddress ?? address; + uri += targetAddress; + + if (chainId != 1) { + uri += "@$chainId"; + } + + if (contractAddress != null) { + uri += "/transfer"; + } + + final params = {}; + + if (contractAddress != null) { + params["address"] = address; + if (amount.isNotEmpty) { + params["uint256"] = _formatAmountForERC20(amount); + } + } else { + if (amount.isNotEmpty) { + params["value"] = _formatAmountForNative(amount); + } + } + + if (params.isNotEmpty) { + uri += "?${params.entries.map((e) => "${e.key}=${e.value}").join("&")}"; + } + + return uri; } + /// Formats amount for ERC-20 token transfers (in atomic units) + String _formatAmountForERC20(String amount) { + try { + // Convert decimal amount to BigInt (assuming 18 decimals) + final amountDouble = double.parse(amount.replaceAll(",", ".")); + final amountBigInt = BigInt.from(amountDouble * 1e18); + return amountBigInt.toString(); + } catch (e) { + // Fallback to original amount if parsing fails + return amount.replaceAll(",", "."); + } + } + + /// Formats amount for native ETH payments (in wei using scientific notation) + String _formatAmountForNative(String amount) { + try { + // Convert decimal amount to double for scientific notation + final amountDouble = double.parse(amount.replaceAll(",", ".")); + + // Use scientific notation as recommended by ERC-681 + return "${amountDouble}e18"; + } catch (e) { + // Fallback to original amount if parsing fails + return amount.replaceAll(",", "."); + } + } + + static int _getChainID(String path) => int.parse( + RegExp(r"@\d*").firstMatch(path)?.group(0)?.replaceAll("@", "") ?? "1", + ); + static (bool, String) _getTargetAddress(String path) { final targetAddress = - RegExp(r'^(0x)?[0-9a-f]{40}', caseSensitive: false).firstMatch(path)!.group(0)!; + RegExp(r"^(0x)?[0-9a-f]{40}", caseSensitive: false).firstMatch(path)!.group(0)!; return (path.contains("/"), targetAddress); } @@ -426,25 +362,27 @@ class ERC681URI extends PaymentURI { /// - Scientific notation: "0.123e18", "1e6" → expanded to integer /// - Decimal ETH: "0.123456" → shifted by 18 decimals static String _normalizeToIntegerWei(String input) { - final raw = input.replaceAll(',', '.').trim(); + final raw = input.replaceAll(",", ".").trim(); // First we check if it's already a plain integer (basically just a number with no dot, no exponent) try { - final isPlainInteger = RegExp(r'^[+-]?\d+$').hasMatch(raw) && - !raw.contains('.') && - !raw.toLowerCase().contains('e'); - if (isPlainInteger) return raw.replaceFirst(RegExp(r'^\+'), ''); + final isPlainInteger = RegExp(r"^[+-]?\d+$").hasMatch(raw) && + !raw.contains(".") && + !raw.toLowerCase().contains("e"); + if (isPlainInteger) { + return raw.replaceFirst(RegExp(r"^\+"), ""); + } // Then we check if it's a scientific notation - final sci = RegExp(r'^[+-]?(\d+\.?\d*|\d*\.?\d+)[eE][+-]?\d+$'); + final sci = RegExp(r"^[+-]?(\d+\.?\d*|\d*\.?\d+)[eE][+-]?\d+$"); if (sci.hasMatch(raw)) { - final mantissaStr = raw.toLowerCase().split('e')[0]; - final exp = int.parse(raw.toLowerCase().split('e')[1]); + final mantissaStr = raw.toLowerCase().split("e")[0]; + final exp = int.parse(raw.toLowerCase().split("e")[1]); return _expandDecimal(mantissaStr, exp); } // Lastly, we check if it's a fixed decimal ETH amount, here we shift by 18 to get wei for the amount - if (raw.contains('.')) { + if (raw.contains(".")) { return _expandDecimal(raw, 18); } return raw; @@ -462,34 +400,34 @@ class ERC681URI extends PaymentURI { /// _expandDecimal('1.2', 3) -> '1200' static String _expandDecimal(String decimalStr, int expShift) { var s = decimalStr.trim(); - var sign = ''; - if (s.startsWith('-') || s.startsWith('+')) { - sign = s[0] == '-' ? '-' : ''; + var sign = ""; + if (s.startsWith("-") || s.startsWith("+")) { + sign = s[0] == "-" ? "-" : ""; s = s.substring(1); } // First we split the integer and fractional parts - final parts = s.split('.'); - final intPart = parts[0].isEmpty ? '0' : parts[0]; - final fracPart = parts.length > 1 ? parts[1] : ''; - final digits = (intPart + fracPart).replaceFirst(RegExp(r'^0+'), ''); + final parts = s.split("."); + final intPart = parts[0].isEmpty ? "0" : parts[0]; + final fracPart = parts.length > 1 ? parts[1] : ""; + final digits = (intPart + fracPart).replaceFirst(RegExp(r"^0+"), ""); final fracLen = fracPart.length; // Then we calculate the effective shift = desired shift minus existing fractional digits final shift = expShift - fracLen; if (shift >= 0) { - final head = digits.isEmpty ? '0' : digits; - final zeros = List.filled(shift, '0').join(); + final head = digits.isEmpty ? "0" : digits; + final zeros = List.filled(shift, "0").join(); final res = head + zeros; - return sign + (res.isEmpty ? '0' : res); + return sign + (res.isEmpty ? "0" : res); } else { // Need to insert a decimal point within digits; return integer by truncating final cut = digits.length + shift; if (cut <= 0) { - return '0'; + return "0"; } final res = digits.substring(0, cut); - return sign + (res.isEmpty ? '0' : res); + return sign + (res.isEmpty ? "0" : res); } } } diff --git a/cw_evm/lib/evm_chain_wallet_addresses.dart b/cw_evm/lib/evm_chain_wallet_addresses.dart index f7cef7513..fad6eb688 100644 --- a/cw_evm/lib/evm_chain_wallet_addresses.dart +++ b/cw_evm/lib/evm_chain_wallet_addresses.dart @@ -1,18 +1,15 @@ -import 'dart:developer'; +import "dart:developer"; -import 'package:cw_core/payment_uris.dart'; -import 'package:cw_core/wallet_addresses.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:mobx/mobx.dart'; +import "package:cw_core/payment_uris.dart"; +import "package:cw_core/wallet_addresses.dart"; +import "package:mobx/mobx.dart"; -part 'evm_chain_wallet_addresses.g.dart'; +part "evm_chain_wallet_addresses.g.dart"; class EVMChainWalletAddresses = EVMChainWalletAddressesBase with _$EVMChainWalletAddresses; abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store { - EVMChainWalletAddressesBase(WalletInfo walletInfo, this._selectedChainId) - : address = '', - super(walletInfo); + EVMChainWalletAddressesBase(super.walletInfo, this._selectedChainId) : address = ""; @override @observable @@ -33,7 +30,7 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store { Future updateAddressesInBox() async { try { addressesMap.clear(); - addressesMap[address] = ''; + addressesMap[address] = ""; await saveAddressesInBox(); } catch (e) { log(e.toString()); @@ -41,20 +38,10 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store { } @override - PaymentURI getPaymentUri(String amount) { - switch (_selectedChainId) { - case 1: - return EthereumURI(amount: amount, address: address); - case 137: - return PolygonURI(amount: amount, address: address); - case 8453: - return BaseURI(amount: amount, address: address); - case 42161: - return ArbitrumURI(amount: amount, address: address); - case 56: - return BSCURI(amount: amount, address: address); - default: - return EthereumURI(amount: amount, address: address); - } - } + PaymentURI getPaymentUri(String amount) => ERC681URI( + address: address, + amount: amount, + chainId: _selectedChainId, + contractAddress: null, + ); } diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 6e039e271..5ca718eb4 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -442,16 +442,11 @@ abstract class ExchangeTradeViewModelBase with Store { case WalletType.dogecoin: return DogeURI(address: inputAddress, amount: amount); case WalletType.ethereum: - return _createERC681URI(fromCurrency, inputAddress, amount); - // TODO: Expand ERC681URI support to Polygon(modify decoding flow for QRs, pay anything, and deep link handling) case WalletType.polygon: - return PolygonURI(amount: amount, address: inputAddress); case WalletType.base: - return BaseURI(amount: amount, address: inputAddress); case WalletType.arbitrum: - return ArbitrumURI(amount: amount, address: inputAddress); case WalletType.bsc: - return BSCURI(amount: amount, address: inputAddress); + return _createERC681URI(fromCurrency, inputAddress, amount); case WalletType.solana: return SolanaURI(amount: amount, address: inputAddress); case WalletType.tron: