| 1 | import 'package:bitcoin_base/bitcoin_base.dart'; |
| 2 | import 'package:cake_wallet/generated/i18n.dart'; |
| 3 | import 'package:cake_wallet/core/validator.dart'; |
| 4 | import 'package:cake_wallet/solana/solana.dart'; |
| 5 | import 'package:cake_wallet/zano/zano.dart'; |
| 6 | import 'package:cake_wallet/zcash/zcash_network_type.dart'; |
| 7 | import 'package:cw_core/crypto_currency.dart'; |
| 8 | import 'package:cw_core/erc20_token.dart'; |
| 9 | |
| 10 | const BEFORE_REGEX = '(^|\\s)'; |
| 11 | const AFTER_REGEX = '(\$|\\s)'; |
| 12 | |
| 13 | class AddressValidator extends TextValidator { |
| 14 | AddressValidator({ |
| 15 | required CryptoCurrency type, |
| 16 | bool isTestnet = false, |
| 17 | String? network, |
| 18 | }) : super( |
| 19 | errorMessage: S.current.error_text_address, |
| 20 | useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type) |
| 21 | ? (String txt) { |
| 22 | final RegExp lightningInvoiceRegex = RegExp( |
| 23 | r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl)[a-z0-9]+$', |
| 24 | caseSensitive: false); |
| 25 | if (lightningInvoiceRegex.hasMatch(txt)) return true; |
| 26 | |
| 27 | return BitcoinAddressUtils.validateAddress( |
| 28 | address: txt, |
| 29 | network: type == CryptoCurrency.btc |
| 30 | ? isTestnet |
| 31 | ? BitcoinNetwork.testnet |
| 32 | : BitcoinNetwork.mainnet |
| 33 | : LitecoinNetwork.mainnet, |
| 34 | ); |
| 35 | } |
| 36 | : type == CryptoCurrency.zano |
| 37 | ? zano?.validateAddress |
| 38 | : null, |
| 39 | pattern: getPattern(type, isTestnet: isTestnet, network: network), |
| 40 | length: getLength(type), |
| 41 | ); |
| 42 | |
| 43 | static const List<CryptoCurrency> reliableValidateCurrencies = [ |
| 44 | CryptoCurrency.xmr, |
| 45 | CryptoCurrency.btc, |
| 46 | CryptoCurrency.ltc, |
| 47 | CryptoCurrency.bch, |
| 48 | CryptoCurrency.trx, |
| 49 | CryptoCurrency.nano, |
| 50 | CryptoCurrency.sol, |
| 51 | CryptoCurrency.zano, |
| 52 | CryptoCurrency.ada, |
| 53 | CryptoCurrency.xrp, |
| 54 | CryptoCurrency.xhv, |
| 55 | CryptoCurrency.zaddr, |
| 56 | CryptoCurrency.zec, |
| 57 | CryptoCurrency.dcr, |
| 58 | CryptoCurrency.rvn, |
| 59 | CryptoCurrency.near, |
| 60 | CryptoCurrency.rune, |
| 61 | CryptoCurrency.scrt, |
| 62 | CryptoCurrency.stx, |
| 63 | CryptoCurrency.kmd, |
| 64 | CryptoCurrency.doge, |
| 65 | CryptoCurrency.btcln, |
| 66 | ]; |
| 67 | |
| 68 | static String? getPattern(CryptoCurrency type, {bool isTestnet = false, String? network}) { |
| 69 | var pattern = ""; |
| 70 | if (type is Erc20Token) { |
| 71 | pattern = '0x[0-9a-zA-Z]+'; |
| 72 | } |
| 73 | switch (type) { |
| 74 | case CryptoCurrency.xmr: |
| 75 | pattern = '4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}'; |
| 76 | case CryptoCurrency.ada: |
| 77 | pattern = '[0-9a-zA-Z]{59}|[0-9a-zA-Z]{92}|[0-9a-zA-Z]{104}' |
| 78 | '|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}'; |
| 79 | case CryptoCurrency.btc: |
| 80 | if (isTestnet) { |
| 81 | pattern = '(^|\s)([mnL][a-km-zA-HJ-NP-Z1-9]{25,34})' |
| 82 | '|([23M][a-km-zA-HJ-NP-Z1-9]{25,34})' |
| 83 | '|(tb1q[ac-hj-np-z02-9]{25,39})' |
| 84 | '|(tb1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))' |
| 85 | '|(tb1q[ac-hj-np-z02-9]{40,80})' |
| 86 | '|(${silentPaymentAddressPatternTestnet})(\$|\s)'; |
| 87 | } else { |
| 88 | pattern = '(^|\s)(1[a-km-zA-HJ-NP-Z1-9]{25,34})' |
| 89 | '|(3[a-km-zA-HJ-NP-Z1-9]{25,34})' |
| 90 | '|(bc1q[ac-hj-np-z02-9]{25,39})' |
| 91 | '|(bc1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))' |
| 92 | '|(bc1q[ac-hj-np-z02-9]{40,80})' |
| 93 | '|(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl)[a-z0-9]+' |
| 94 | '|(${silentPaymentAddressPatternMainnet})(\$|\s)'; |
| 95 | } |
| 96 | case CryptoCurrency.ltc: |
| 97 | pattern = '(^|\s)(L[a-km-zA-HJ-NP-Z1-9]{25,34})' |
| 98 | '|([3M][a-km-zA-HJ-NP-Z1-9]{25,34})' |
| 99 | '|(ltc1q[ac-hj-np-z02-9]{25,39})' |
| 100 | '|(ltc1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))' |
| 101 | '|(ltc1q[ac-hj-np-z02-9]{40,80})' |
| 102 | '|(${MwebAddress.regex.pattern})(\$|\s)'; |
| 103 | case CryptoCurrency.nano: |
| 104 | pattern = '[0-9a-zA-Z_]+'; |
| 105 | case CryptoCurrency.banano: |
| 106 | pattern = '[0-9a-zA-Z_]+'; |
| 107 | case CryptoCurrency.usdc: |
| 108 | case CryptoCurrency.usdcpoly: |
| 109 | case CryptoCurrency.usdtPoly: |
| 110 | case CryptoCurrency.usdcEPoly: |
| 111 | case CryptoCurrency.ape: |
| 112 | case CryptoCurrency.avaxc: |
| 113 | case CryptoCurrency.eth: |
| 114 | case CryptoCurrency.baseEth: |
| 115 | case CryptoCurrency.arbEth: |
| 116 | case CryptoCurrency.mana: |
| 117 | case CryptoCurrency.matic: |
| 118 | case CryptoCurrency.maticpoly: |
| 119 | case CryptoCurrency.mkr: |
| 120 | case CryptoCurrency.oxt: |
| 121 | case CryptoCurrency.paxg: |
| 122 | case CryptoCurrency.uni: |
| 123 | case CryptoCurrency.aave: |
| 124 | case CryptoCurrency.bat: |
| 125 | case CryptoCurrency.comp: |
| 126 | case CryptoCurrency.cro: |
| 127 | case CryptoCurrency.ens: |
| 128 | case CryptoCurrency.ftm: |
| 129 | case CryptoCurrency.frax: |
| 130 | case CryptoCurrency.gusd: |
| 131 | case CryptoCurrency.gtc: |
| 132 | case CryptoCurrency.grt: |
| 133 | case CryptoCurrency.ldo: |
| 134 | case CryptoCurrency.nexo: |
| 135 | case CryptoCurrency.pepe: |
| 136 | case CryptoCurrency.storj: |
| 137 | case CryptoCurrency.tusd: |
| 138 | case CryptoCurrency.wbtc: |
| 139 | case CryptoCurrency.weth: |
| 140 | case CryptoCurrency.zrx: |
| 141 | case CryptoCurrency.dydx: |
| 142 | case CryptoCurrency.steth: |
| 143 | case CryptoCurrency.shib: |
| 144 | pattern = '0x[0-9a-zA-Z]+'; |
| 145 | case CryptoCurrency.xrp: |
| 146 | pattern = '[0-9a-zA-Z]{34}|[0-9a-zA-Z]{33}|X[0-9a-zA-Z]{46}'; |
| 147 | case CryptoCurrency.xhv: |
| 148 | pattern = 'hvx|hvi|hvs[0-9a-zA-Z]+'; |
| 149 | case CryptoCurrency.xag: |
| 150 | case CryptoCurrency.xau: |
| 151 | case CryptoCurrency.xaud: |
| 152 | case CryptoCurrency.xbtc: |
| 153 | case CryptoCurrency.xcad: |
| 154 | case CryptoCurrency.xchf: |
| 155 | case CryptoCurrency.xcny: |
| 156 | case CryptoCurrency.xeur: |
| 157 | case CryptoCurrency.xgbp: |
| 158 | case CryptoCurrency.xjpy: |
| 159 | case CryptoCurrency.xnok: |
| 160 | case CryptoCurrency.xnzd: |
| 161 | case CryptoCurrency.xusd: |
| 162 | case CryptoCurrency.usdt: |
| 163 | case CryptoCurrency.usdterc20: |
| 164 | case CryptoCurrency.xlm: |
| 165 | case CryptoCurrency.trx: |
| 166 | case CryptoCurrency.dai: |
| 167 | case CryptoCurrency.dash: |
| 168 | case CryptoCurrency.eos: |
| 169 | case CryptoCurrency.wow: |
| 170 | pattern = '[0-9a-zA-Z]+'; |
| 171 | case CryptoCurrency.bch: |
| 172 | pattern = '(?:bitcoincash:)?(q|p)[0-9a-zA-Z]{41}' |
| 173 | '|[13][a-km-zA-HJ-NP-Z1-9]{25,34}'; |
| 174 | case CryptoCurrency.hbar: |
| 175 | pattern = '[0-9a-zA-Z.]+'; |
| 176 | case CryptoCurrency.zec: |
| 177 | if (ZcashNetworkType.isDevNetwork(network)) { |
| 178 | return null; |
| 179 | } |
| 180 | pattern = "(?:$zcashAddressPattern|zxviews[a-z0-9]{278})"; |
| 181 | case CryptoCurrency.dcr: |
| 182 | pattern = '(D|T|S)[ksecS]([0-9a-zA-Z])+'; |
| 183 | case CryptoCurrency.rvn: |
| 184 | pattern = '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}'; |
| 185 | case CryptoCurrency.near: |
| 186 | pattern = '[0-9a-f]{64}'; |
| 187 | case CryptoCurrency.rune: |
| 188 | pattern = 'thor1[0-9a-z]{38}'; |
| 189 | case CryptoCurrency.scrt: |
| 190 | pattern = 'secret1[0-9a-z]{38}'; |
| 191 | case CryptoCurrency.stx: |
| 192 | pattern = 'S[MP][0-9a-zA-Z]+'; |
| 193 | case CryptoCurrency.kmd: |
| 194 | pattern = 'R[0-9a-zA-Z]{33}'; |
| 195 | case CryptoCurrency.pivx: |
| 196 | pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}'; |
| 197 | case CryptoCurrency.btcln: |
| 198 | pattern = |
| 199 | r'(lightning:)?(lnbc|lntb|lnbs|lnbcrt|lnurl|LNBC|LNTB|LNBS|LNBCRT|LNURL)[a-zA-Z0-9]+'; |
| 200 | case CryptoCurrency.zano: |
| 201 | pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)'; |
| 202 | case CryptoCurrency.doge: |
| 203 | pattern = r'^D[a-km-zA-HJ-NP-Z1-9]{25,34}'; |
| 204 | default: |
| 205 | return ''; |
| 206 | } |
| 207 | |
| 208 | return '$BEFORE_REGEX($pattern)$AFTER_REGEX'; |
| 209 | } |
| 210 | |
| 211 | static List<int>? getLength(CryptoCurrency type) { |
| 212 | if (type is Erc20Token) { |
| 213 | return [42]; |
| 214 | } |
| 215 | |
| 216 | if (solana != null) { |
| 217 | final length = solana!.getValidationLength(type); |
| 218 | if (length != null) return length; |
| 219 | } |
| 220 | |
| 221 | switch (type) { |
| 222 | case CryptoCurrency.xmr: |
| 223 | case CryptoCurrency.wow: |
| 224 | return null; |
| 225 | case CryptoCurrency.ada: |
| 226 | return null; |
| 227 | case CryptoCurrency.btc: |
| 228 | return null; |
| 229 | case CryptoCurrency.ltc: |
| 230 | return null; |
| 231 | case CryptoCurrency.dash: |
| 232 | return [34]; |
| 233 | case CryptoCurrency.eos: |
| 234 | return [42]; |
| 235 | case CryptoCurrency.eth: |
| 236 | case CryptoCurrency.usdcpoly: |
| 237 | case CryptoCurrency.usdtPoly: |
| 238 | case CryptoCurrency.usdcEPoly: |
| 239 | case CryptoCurrency.mana: |
| 240 | case CryptoCurrency.matic: |
| 241 | case CryptoCurrency.maticpoly: |
| 242 | case CryptoCurrency.mkr: |
| 243 | case CryptoCurrency.oxt: |
| 244 | case CryptoCurrency.paxg: |
| 245 | case CryptoCurrency.uni: |
| 246 | case CryptoCurrency.dai: |
| 247 | case CryptoCurrency.ape: |
| 248 | case CryptoCurrency.usdc: |
| 249 | case CryptoCurrency.usdterc20: |
| 250 | case CryptoCurrency.aave: |
| 251 | case CryptoCurrency.bat: |
| 252 | case CryptoCurrency.comp: |
| 253 | case CryptoCurrency.cro: |
| 254 | case CryptoCurrency.ens: |
| 255 | case CryptoCurrency.ftm: |
| 256 | case CryptoCurrency.frax: |
| 257 | case CryptoCurrency.gusd: |
| 258 | case CryptoCurrency.gtc: |
| 259 | case CryptoCurrency.grt: |
| 260 | case CryptoCurrency.ldo: |
| 261 | case CryptoCurrency.nexo: |
| 262 | case CryptoCurrency.pepe: |
| 263 | case CryptoCurrency.storj: |
| 264 | case CryptoCurrency.tusd: |
| 265 | case CryptoCurrency.wbtc: |
| 266 | case CryptoCurrency.weth: |
| 267 | case CryptoCurrency.zrx: |
| 268 | case CryptoCurrency.dydx: |
| 269 | case CryptoCurrency.steth: |
| 270 | case CryptoCurrency.shib: |
| 271 | case CryptoCurrency.avaxc: |
| 272 | case CryptoCurrency.baseEth: |
| 273 | case CryptoCurrency.arbEth: |
| 274 | return [42]; |
| 275 | case CryptoCurrency.bch: |
| 276 | return [42, 54, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]; |
| 277 | case CryptoCurrency.bnb: |
| 278 | return [42]; |
| 279 | case CryptoCurrency.nano: |
| 280 | return [64, 65]; |
| 281 | case CryptoCurrency.banano: |
| 282 | return [64, 65]; |
| 283 | case CryptoCurrency.sc: |
| 284 | return [76]; |
| 285 | case CryptoCurrency.sol: |
| 286 | case CryptoCurrency.usdtSol: |
| 287 | case CryptoCurrency.usdcsol: |
| 288 | return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]; |
| 289 | case CryptoCurrency.trx: |
| 290 | return [34]; |
| 291 | case CryptoCurrency.usdt: |
| 292 | return [34]; |
| 293 | case CryptoCurrency.usdttrc20: |
| 294 | return [34]; |
| 295 | case CryptoCurrency.xlm: |
| 296 | return [56]; |
| 297 | case CryptoCurrency.xrp: |
| 298 | return null; |
| 299 | case CryptoCurrency.xhv: |
| 300 | case CryptoCurrency.xag: |
| 301 | case CryptoCurrency.xau: |
| 302 | case CryptoCurrency.xaud: |
| 303 | case CryptoCurrency.xbtc: |
| 304 | case CryptoCurrency.xcad: |
| 305 | case CryptoCurrency.xchf: |
| 306 | case CryptoCurrency.xcny: |
| 307 | case CryptoCurrency.xeur: |
| 308 | case CryptoCurrency.xgbp: |
| 309 | case CryptoCurrency.xjpy: |
| 310 | case CryptoCurrency.xnok: |
| 311 | case CryptoCurrency.xnzd: |
| 312 | case CryptoCurrency.xusd: |
| 313 | return [98, 99, 106]; |
| 314 | case CryptoCurrency.btt: |
| 315 | case CryptoCurrency.bttc: |
| 316 | case CryptoCurrency.doge: |
| 317 | case CryptoCurrency.firo: |
| 318 | return [34]; |
| 319 | case CryptoCurrency.hbar: |
| 320 | return [4, 5, 6, 7, 8, 9, 10, 11]; |
| 321 | case CryptoCurrency.xvg: |
| 322 | return [34]; |
| 323 | case CryptoCurrency.zen: |
| 324 | return [35]; |
| 325 | case CryptoCurrency.zec: |
| 326 | return null; |
| 327 | case CryptoCurrency.kmd: |
| 328 | case CryptoCurrency.pivx: |
| 329 | case CryptoCurrency.rvn: |
| 330 | return [34]; |
| 331 | case CryptoCurrency.dcr: |
| 332 | return [35]; |
| 333 | case CryptoCurrency.stx: |
| 334 | return [40, 41, 42]; |
| 335 | case CryptoCurrency.rune: |
| 336 | return [43]; |
| 337 | case CryptoCurrency.scrt: |
| 338 | return [45]; |
| 339 | case CryptoCurrency.near: |
| 340 | return [64]; |
| 341 | case CryptoCurrency.btcln: |
| 342 | case CryptoCurrency.kaspa: |
| 343 | case CryptoCurrency.zano: |
| 344 | default: |
| 345 | return null; |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | static String get silentPaymentAddressPatternMainnet => 'sp1[0-9a-zA-Z]{113}'; |
| 350 | static String get silentPaymentAddressPatternTestnet => '(tsp|sprt)1[0-9a-zA-Z]{113}'; |
| 351 | static String get mWebAddressPattern => MwebAddress.regex.pattern; |
| 352 | static String get zcashAddressPattern => |
| 353 | "t1[0-9A-Za-z]{33}|t3[0-9A-Za-z]{33}|zs[a-z0-9]{76}|u1[a-z0-9]{1,300}"; |
| 354 | |
| 355 | static const String bolt11InvoiceMatcher = r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt)[a-z0-9]+$'; |
| 356 | static const String bolt12OfferMatcher = r'^(lightning:)?(lno1)[a-z0-9]+$'; |
| 357 | static const String lnurlMatcher = r'^(lightning:)?(lnurl)[a-z0-9]+$'; |
| 358 | |
| 359 | // NOTE: not needed to check for network here as it's a general address catcher, validation is separate |
| 360 | static String? getAddressFromStringPattern(CryptoCurrency type) { |
| 361 | String? pattern = null; |
| 362 | |
| 363 | switch (type) { |
| 364 | case CryptoCurrency.xmr: |
| 365 | pattern = '(4[0-9a-zA-Z]{94})' |
| 366 | '|(8[0-9a-zA-Z]{94})' |
| 367 | '|([0-9a-zA-Z]{106})'; |
| 368 | case CryptoCurrency.wow: |
| 369 | pattern = '(W[0-9a-zA-Z]{94})' |
| 370 | '|(W[0-9a-zA-Z]{94})' |
| 371 | '|(W[0-9a-zA-Z]{96})' |
| 372 | '|([0-9a-zA-Z]{106})'; |
| 373 | case CryptoCurrency.btc: |
| 374 | pattern = |
| 375 | '${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}'; |
| 376 | case CryptoCurrency.ltc: |
| 377 | pattern = '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' |
| 378 | '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' |
| 379 | '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)' |
| 380 | '|([^0-9a-zA-Z]|^)((ltc|t)mweb1q[ac-hj-np-z02-9]{90,120})([^0-9a-zA-Z]|\$)'; |
| 381 | case CryptoCurrency.eth: |
| 382 | case CryptoCurrency.maticpoly: |
| 383 | case CryptoCurrency.baseEth: |
| 384 | case CryptoCurrency.arbEth: |
| 385 | case CryptoCurrency.arb: |
| 386 | pattern = '0x[0-9a-zA-Z]+'; |
| 387 | case CryptoCurrency.nano: |
| 388 | pattern = 'nano_[0-9a-zA-Z]{60}'; |
| 389 | case CryptoCurrency.banano: |
| 390 | pattern = 'ban_[0-9a-zA-Z]{60}'; |
| 391 | case CryptoCurrency.bch: |
| 392 | pattern = '(bitcoincash:)?q[0-9a-zA-Z]{41,42}'; |
| 393 | case CryptoCurrency.sol: |
| 394 | pattern = '[1-9A-HJ-NP-Za-km-z]+'; |
| 395 | case CryptoCurrency.trx: |
| 396 | pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}'; |
| 397 | case CryptoCurrency.zano: |
| 398 | pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)'; |
| 399 | case CryptoCurrency.zec: |
| 400 | pattern = "(?:$zcashAddressPattern|zxviews[a-z0-9]{278})"; |
| 401 | default: |
| 402 | if (type.tag == CryptoCurrency.eth.title) { |
| 403 | pattern = '0x[0-9a-zA-Z]+'; |
| 404 | } |
| 405 | if (type.tag == CryptoCurrency.maticpoly.tag) { |
| 406 | pattern = '0x[0-9a-zA-Z]+'; |
| 407 | } |
| 408 | if (type.tag == CryptoCurrency.sol.title) { |
| 409 | pattern = '[1-9A-HJ-NP-Za-km-z]{43,44}'; |
| 410 | } |
| 411 | if (type.tag == CryptoCurrency.trx.title) { |
| 412 | pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}'; |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | return pattern != null ? "($pattern)" : null; |
| 417 | } |
| 418 | } |