import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:hash_wallet/generated/i18n.dart'; import 'package:hash_wallet/core/validator.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; const BEFORE_REGEX = '(^|\\s)'; const AFTER_REGEX = '(\$|\\s)'; class AddressValidator extends TextValidator { AddressValidator({required CryptoCurrency type, bool isTestnet = false}) : super( errorMessage: S.current.error_text_address, useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type) ? (String txt) { final RegExp lightningInvoiceRegex = RegExp( r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl)[a-z0-9]+$', caseSensitive: false); if (lightningInvoiceRegex.hasMatch(txt)) return true; return BitcoinAddressUtils.validateAddress( address: txt, network: type == CryptoCurrency.btc ? isTestnet ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet : LitecoinNetwork.mainnet, ); } : null, pattern: getPattern(type, isTestnet: isTestnet), length: getLength(type), ); static String getPattern(CryptoCurrency type, {bool isTestnet = false}) { var pattern = ""; if (type is Erc20Token) { pattern = '0x[0-9a-zA-Z]+'; } switch (type) { case CryptoCurrency.xmr: pattern = '4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}'; case CryptoCurrency.ada: pattern = '[0-9a-zA-Z]{59}|[0-9a-zA-Z]{92}|[0-9a-zA-Z]{104}' '|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}'; case CryptoCurrency.btc: if (isTestnet) { pattern = '(^|\s)([mnL][a-km-zA-HJ-NP-Z1-9]{25,34})' '|([23M][a-km-zA-HJ-NP-Z1-9]{25,34})' '|(tb1q[ac-hj-np-z02-9]{25,39})' '|(tb1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))' '|(tb1q[ac-hj-np-z02-9]{40,80})' '|(${silentPaymentAddressPatternTestnet})(\$|\s)'; } else { pattern = '(^|\s)(1[a-km-zA-HJ-NP-Z1-9]{25,34})' '|(3[a-km-zA-HJ-NP-Z1-9]{25,34})' '|(bc1q[ac-hj-np-z02-9]{25,39})' '|(bc1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))' '|(bc1q[ac-hj-np-z02-9]{40,80})' '|(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl)[a-z0-9]+' '|(${silentPaymentAddressPatternMainnet})(\$|\s)'; } case CryptoCurrency.ltc: pattern = '(^|\s)(L[a-km-zA-HJ-NP-Z1-9]{25,34})' '|([3M][a-km-zA-HJ-NP-Z1-9]{25,34})' '|(ltc1q[ac-hj-np-z02-9]{25,39})' '|(ltc1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))' '|(ltc1q[ac-hj-np-z02-9]{40,80})' '|(${MwebAddress.regex.pattern})(\$|\s)'; case CryptoCurrency.nano: pattern = '[0-9a-zA-Z_]+'; case CryptoCurrency.banano: pattern = '[0-9a-zA-Z_]+'; case CryptoCurrency.usdc: case CryptoCurrency.usdcpoly: case CryptoCurrency.usdtPoly: case CryptoCurrency.usdcEPoly: case CryptoCurrency.ape: case CryptoCurrency.avaxc: case CryptoCurrency.eth: case CryptoCurrency.baseEth: case CryptoCurrency.arbEth: case CryptoCurrency.mana: case CryptoCurrency.matic: case CryptoCurrency.maticpoly: case CryptoCurrency.mkr: case CryptoCurrency.oxt: case CryptoCurrency.paxg: case CryptoCurrency.uni: case CryptoCurrency.aave: case CryptoCurrency.bat: case CryptoCurrency.comp: case CryptoCurrency.cro: case CryptoCurrency.ens: case CryptoCurrency.ftm: case CryptoCurrency.frax: case CryptoCurrency.gusd: case CryptoCurrency.gtc: case CryptoCurrency.grt: case CryptoCurrency.ldo: case CryptoCurrency.nexo: case CryptoCurrency.pepe: case CryptoCurrency.storj: case CryptoCurrency.tusd: case CryptoCurrency.wbtc: case CryptoCurrency.weth: case CryptoCurrency.zrx: case CryptoCurrency.dydx: case CryptoCurrency.steth: case CryptoCurrency.shib: pattern = '0x[0-9a-zA-Z]+'; case CryptoCurrency.xrp: pattern = '[0-9a-zA-Z]{34}|[0-9a-zA-Z]{33}|X[0-9a-zA-Z]{46}'; case CryptoCurrency.xhv: pattern = 'hvx|hvi|hvs[0-9a-zA-Z]+'; case CryptoCurrency.xag: case CryptoCurrency.xau: case CryptoCurrency.xaud: case CryptoCurrency.xbtc: case CryptoCurrency.xcad: case CryptoCurrency.xchf: case CryptoCurrency.xcny: case CryptoCurrency.xeur: case CryptoCurrency.xgbp: case CryptoCurrency.xjpy: case CryptoCurrency.xnok: case CryptoCurrency.xnzd: case CryptoCurrency.xusd: case CryptoCurrency.usdt: case CryptoCurrency.usdterc20: case CryptoCurrency.xlm: case CryptoCurrency.trx: case CryptoCurrency.dai: case CryptoCurrency.dash: case CryptoCurrency.eos: case CryptoCurrency.wow: pattern = '[0-9a-zA-Z]+'; case CryptoCurrency.bch: pattern = '(?:bitcoincash:)?(q|p)[0-9a-zA-Z]{41}' '|[13][a-km-zA-HJ-NP-Z1-9]{25,34}'; case CryptoCurrency.hbar: pattern = '[0-9a-zA-Z.]+'; case CryptoCurrency.zec: pattern = '(?:' 't1[0-9A-Za-z]{33}' '|t3[0-9A-Za-z]{33}' '|zs[a-z0-9]{76}' '|zxviews[a-z0-9]{278}' '|u1[a-z0-9]{1,300}' ')'; case CryptoCurrency.dcr: pattern = '(D|T|S)[ksecS]([0-9a-zA-Z])+'; case CryptoCurrency.rvn: pattern = '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}'; case CryptoCurrency.near: pattern = '[0-9a-f]{64}'; case CryptoCurrency.rune: pattern = 'thor1[0-9a-z]{38}'; case CryptoCurrency.scrt: pattern = 'secret1[0-9a-z]{38}'; case CryptoCurrency.stx: pattern = 'S[MP][0-9a-zA-Z]+'; case CryptoCurrency.kmd: pattern = 'R[0-9a-zA-Z]{33}'; case CryptoCurrency.pivx: pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}'; case CryptoCurrency.btcln: pattern = r'(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl|LNBC|LNTB|LNBS|LNBCRT|LNURL)[a-zA-Z0-9]+'; case CryptoCurrency.zano: pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)'; case CryptoCurrency.doge: pattern = r'^D[a-km-zA-HJ-NP-Z1-9]{25,34}'; default: return ''; } return '$BEFORE_REGEX($pattern)$AFTER_REGEX'; } static List? getLength(CryptoCurrency type) { if (type is Erc20Token) { return [42]; } switch (type) { case CryptoCurrency.xmr: case CryptoCurrency.wow: return null; case CryptoCurrency.ada: return null; case CryptoCurrency.btc: return null; case CryptoCurrency.ltc: return null; case CryptoCurrency.dash: return [34]; case CryptoCurrency.eos: return [42]; case CryptoCurrency.eth: case CryptoCurrency.usdcpoly: case CryptoCurrency.usdtPoly: case CryptoCurrency.usdcEPoly: case CryptoCurrency.mana: case CryptoCurrency.matic: case CryptoCurrency.maticpoly: case CryptoCurrency.mkr: case CryptoCurrency.oxt: case CryptoCurrency.paxg: case CryptoCurrency.uni: case CryptoCurrency.dai: case CryptoCurrency.ape: case CryptoCurrency.usdc: case CryptoCurrency.usdterc20: case CryptoCurrency.aave: case CryptoCurrency.bat: case CryptoCurrency.comp: case CryptoCurrency.cro: case CryptoCurrency.ens: case CryptoCurrency.ftm: case CryptoCurrency.frax: case CryptoCurrency.gusd: case CryptoCurrency.gtc: case CryptoCurrency.grt: case CryptoCurrency.ldo: case CryptoCurrency.nexo: case CryptoCurrency.pepe: case CryptoCurrency.storj: case CryptoCurrency.tusd: case CryptoCurrency.wbtc: case CryptoCurrency.weth: case CryptoCurrency.zrx: case CryptoCurrency.dydx: case CryptoCurrency.steth: case CryptoCurrency.shib: case CryptoCurrency.avaxc: case CryptoCurrency.baseEth: case CryptoCurrency.arbEth: return [42]; case CryptoCurrency.bch: return [42, 54, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]; case CryptoCurrency.bnb: return [42]; case CryptoCurrency.nano: return [64, 65]; case CryptoCurrency.banano: return [64, 65]; case CryptoCurrency.sc: return [76]; case CryptoCurrency.sol: case CryptoCurrency.usdtSol: case CryptoCurrency.usdcsol: return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]; case CryptoCurrency.trx: return [34]; case CryptoCurrency.usdt: return [34]; case CryptoCurrency.usdttrc20: return [34]; case CryptoCurrency.xlm: return [56]; case CryptoCurrency.xrp: return null; case CryptoCurrency.xhv: case CryptoCurrency.xag: case CryptoCurrency.xau: case CryptoCurrency.xaud: case CryptoCurrency.xbtc: case CryptoCurrency.xcad: case CryptoCurrency.xchf: case CryptoCurrency.xcny: case CryptoCurrency.xeur: case CryptoCurrency.xgbp: case CryptoCurrency.xjpy: case CryptoCurrency.xnok: case CryptoCurrency.xnzd: case CryptoCurrency.xusd: return [98, 99, 106]; case CryptoCurrency.btt: case CryptoCurrency.bttc: case CryptoCurrency.doge: case CryptoCurrency.firo: return [34]; case CryptoCurrency.hbar: return [4, 5, 6, 7, 8, 9, 10, 11]; case CryptoCurrency.xvg: return [34]; case CryptoCurrency.zen: return [35]; case CryptoCurrency.zec: return null; case CryptoCurrency.kmd: case CryptoCurrency.pivx: case CryptoCurrency.rvn: return [34]; case CryptoCurrency.dcr: return [35]; case CryptoCurrency.stx: return [40, 41, 42]; case CryptoCurrency.rune: return [43]; case CryptoCurrency.scrt: return [45]; case CryptoCurrency.near: return [64]; case CryptoCurrency.btcln: case CryptoCurrency.kaspa: case CryptoCurrency.zano: default: return null; } } static String get silentPaymentAddressPatternMainnet => 'sp1[0-9a-zA-Z]{113}'; static String get silentPaymentAddressPatternTestnet => '(tsp|sprt)1[0-9a-zA-Z]{113}'; static String get mWebAddressPattern => MwebAddress.regex.pattern; static const String bolt11InvoiceMatcher = r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt)[a-z0-9]+$'; static const String bolt12OfferMatcher = r'^(lightning:)?(lno1)[a-z0-9]+$'; static const String lnurlMatcher = r'^(lightning:)?(lnurl)[a-z0-9]+$'; // NOTE: not needed to check for network here as it's a general address catcher, validation is separate static String? getAddressFromStringPattern(CryptoCurrency type) { String? pattern = null; switch (type) { case CryptoCurrency.xmr: pattern = '(4[0-9a-zA-Z]{94})' '|(8[0-9a-zA-Z]{94})' '|([0-9a-zA-Z]{106})'; case CryptoCurrency.wow: pattern = '(W[0-9a-zA-Z]{94})' '|(W[0-9a-zA-Z]{94})' '|(W[0-9a-zA-Z]{96})' '|([0-9a-zA-Z]{106})'; case CryptoCurrency.btc: pattern = '${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}'; case CryptoCurrency.ltc: pattern = '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)((ltc|t)mweb1q[ac-hj-np-z02-9]{90,120})([^0-9a-zA-Z]|\$)'; case CryptoCurrency.eth: case CryptoCurrency.maticpoly: case CryptoCurrency.baseEth: case CryptoCurrency.arbEth: case CryptoCurrency.arb: pattern = '0x[0-9a-zA-Z]+'; case CryptoCurrency.nano: pattern = 'nano_[0-9a-zA-Z]{60}'; case CryptoCurrency.banano: 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.trx: pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}'; case CryptoCurrency.zano: pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)'; case CryptoCurrency.zec: pattern = '(?:' 't1[0-9A-Za-z]{33}' '|t3[0-9A-Za-z]{33}' '|zs[a-z0-9]{76}' '|zxviews[a-z0-9]{278}' '|u1[a-z0-9]{1,300}' ')'; default: if (type.tag == CryptoCurrency.eth.title) { pattern = '0x[0-9a-zA-Z]+'; } if (type.tag == CryptoCurrency.maticpoly.tag) { pattern = '0x[0-9a-zA-Z]+'; } if (type.tag == CryptoCurrency.sol.title) { pattern = '[1-9A-HJ-NP-Za-km-z]{43,44}'; } if (type.tag == CryptoCurrency.trx.title) { pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}'; } } return pattern != null ? "($pattern)" : null; } }