dev
dart 297 lines 8.76 KB
Raw
1 import 'package:cake_wallet/utils/payment_request.dart';
2 import 'package:cake_wallet/core/address_validator.dart';
3 import 'package:cw_core/crypto_currency.dart';
4 import 'package:cw_core/lnurl.dart';
5 import 'package:cw_core/payment_uris.dart';
6 import 'package:cw_core/wallet_type.dart';
7 import 'package:cw_core/currency_for_wallet_type.dart';
8
9 class AddressDetectionResult {
10 AddressDetectionResult({
11 required this.address,
12 this.detectedCurrency,
13 this.detectedWalletType,
14 this.amount,
15 this.note,
16 this.scheme,
17 this.pjUri,
18 this.callbackUrl,
19 this.callbackMessage,
20 required this.isValid,
21 this.errorMessage,
22 this.chainId,
23 });
24
25 final String address;
26 final CryptoCurrency? detectedCurrency;
27 final WalletType? detectedWalletType;
28 final String? amount;
29 final String? note;
30 final String? scheme;
31 final String? pjUri;
32 final String? callbackUrl;
33 final String? callbackMessage;
34 final bool isValid;
35 final String? errorMessage;
36 final int? chainId;
37 }
38
39 /// Universal address detector that can identify cryptocurrency addresses from various formats
40 class UniversalAddressDetector {
41 /// Detects cryptocurrency address from various input formats
42 /// Supports: raw addresses, URIs, and QR codes
43 static AddressDetectionResult detectAddress(String input) {
44 if (input.trim().isEmpty) {
45 return AddressDetectionResult(
46 address: '',
47 detectedCurrency: null,
48 isValid: false,
49 errorMessage: 'Empty input provided',
50 );
51 }
52
53 // Try to parse as URI first
54 final uriResult = _detectFromUri(input);
55 if (uriResult.isValid) return uriResult;
56
57 // Try to detect from raw address patterns
58 final rawAddressResult = _detectFromRawAddress(input);
59 if (rawAddressResult.isValid) return rawAddressResult;
60
61 return AddressDetectionResult(
62 address: input,
63 detectedCurrency: null,
64 isValid: false,
65 errorMessage: 'Unable to detect valid cryptocurrency address',
66 );
67 }
68
69 /// Detects address from URI format (e.g., bitcoin:address?amount=0.001)
70 static AddressDetectionResult _detectFromUri(String input) {
71 try {
72 final uri = Uri.parse(input);
73
74 final paymentRequest = PaymentRequest.fromUri(uri);
75
76 CryptoCurrency currency;
77 int? chainId;
78 if (uri.scheme.toLowerCase() == 'ethereum') {
79 final erc681 = ERC681URI.fromUri(uri);
80 chainId = erc681.chainId;
81 currency = getCryptoCurrencyByChainId(chainId);
82 } else {
83 currency = CryptoCurrency.fromString(uri.scheme.toLowerCase());
84 chainId = getChainIdByCryptoCurrency(currency);
85 }
86
87 final walletType = cryptoCurrencyOrTokenToWalletType(currency);
88
89 return AddressDetectionResult(
90 address: paymentRequest.address,
91 detectedCurrency: currency,
92 detectedWalletType: walletType,
93 amount: paymentRequest.amount,
94 note: paymentRequest.note,
95 scheme: paymentRequest.scheme,
96 pjUri: paymentRequest.pjUri,
97 callbackUrl: paymentRequest.callbackUrl,
98 callbackMessage: paymentRequest.callbackMessage,
99 isValid: true,
100 chainId: chainId,
101 );
102 } catch (e) {
103 return AddressDetectionResult(
104 address: input,
105 detectedCurrency: null,
106 isValid: false,
107 errorMessage: 'Failed to parse URI: $e',
108 );
109 }
110 }
111
112 /// Detect address from raw address patterns
113 static AddressDetectionResult _detectFromRawAddress(String input) {
114 final cleanInput = input.trim();
115
116 // Detection patterns ordered by specificity (most specific first)
117 final detectionPatterns = [
118 // Lightning Network (Bolt11 Invoice format)
119 _DetectionPattern(
120 pattern: RegExp(r'^(lightning:)?(lnbc|lntb|lnbs|lnbcrt)[a-z0-9]+$', caseSensitive: false),
121 currency: CryptoCurrency.btcln,
122 ),
123
124 // Lightning Network (Bolt12 Offer format)
125 _DetectionPattern(
126 pattern: RegExp(r'^(lightning:)?(lno1)[a-z0-9]+$', caseSensitive: false),
127 currency: CryptoCurrency.btcln,
128 ),
129
130 // Lightning Address (email format)
131 _DetectionPattern(
132 pattern: RegExp(r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'),
133 currency: CryptoCurrency.btcln,
134 ),
135
136 // Lightning Address (LNURL format)
137 _DetectionPattern(
138 pattern: RegExp(r'^(lightning:)?(lnurl)[a-z0-9]+$', caseSensitive: false),
139 currency: CryptoCurrency.btcln,
140 ),
141
142 // Bitcoin (P2PKH, P2SH, Bech32, Silent Payments)
143 _DetectionPattern(
144 pattern: RegExp('^(?:'
145 '1[a-km-zA-HJ-NP-Z1-9]{25,34}'
146 '|3[a-km-zA-HJ-NP-Z1-9]{25,34}'
147 '|(?:bc|tb)1q[ac-hj-np-z02-9]{25,39}'
148 '|(?:bc|tb)1p(?:[ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89})'
149 '|(?:bc|tb)1q[ac-hj-np-z02-9]{40,80}'
150 '|${AddressValidator.silentPaymentAddressPatternMainnet}'
151 '|${AddressValidator.silentPaymentAddressPatternTestnet}'
152 ')\$'),
153 currency: CryptoCurrency.btc,
154 ),
155
156 // Litecoin (Legacy, Bech32, MWEB)
157 _DetectionPattern(
158 pattern: RegExp('^(?:'
159 'L[a-km-zA-HJ-NP-Z1-9]{25,34}'
160 '|[3M][a-km-zA-HJ-NP-Z1-9]{25,34}'
161 '|ltc1q[ac-hj-np-z02-9]{25,39}'
162 '|ltc1p(?:[ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89})'
163 '|ltc1q[ac-hj-np-z02-9]{40,80}'
164 '|${AddressValidator.mWebAddressPattern}'
165 ')\$'),
166 currency: CryptoCurrency.ltc,
167 ),
168
169 // Bitcoin Cash
170 _DetectionPattern(
171 pattern: RegExp(r'^([qp])[a-z0-9]{41}$'),
172 currency: CryptoCurrency.bch,
173 ),
174
175 // Ethereum
176 _DetectionPattern(
177 pattern: RegExp(r'^0x[a-fA-F0-9]{40}$'),
178 currency: CryptoCurrency.eth,
179 ),
180
181 // Nano
182 _DetectionPattern(
183 pattern: RegExp(r'^nano_[a-km-zA-HJ-NP-Z1-9]{60}$'),
184 currency: CryptoCurrency.nano,
185 ),
186
187 // Banano
188 _DetectionPattern(
189 pattern: RegExp(r'^ban_[a-km-zA-HJ-NP-Z1-9]{60}$'),
190 currency: CryptoCurrency.banano,
191 ),
192
193 // Tron
194 _DetectionPattern(
195 pattern: RegExp(r'^T[a-km-zA-HJ-NP-Z1-9]{33}$'),
196 currency: CryptoCurrency.trx,
197 ),
198
199 // Zano alias
200 _DetectionPattern(
201 pattern: RegExp(r'^@[\w\d.-]+$'),
202 currency: CryptoCurrency.zano,
203 ),
204
205 // Monero (standard) - 95 characters
206 _DetectionPattern(
207 pattern: RegExp(r'^4[a-km-zA-HJ-NP-Z1-9]{94}$'),
208 currency: CryptoCurrency.xmr,
209 ),
210
211 // Monero (integrated) - 95 characters
212 _DetectionPattern(
213 pattern: RegExp(r'^8[a-km-zA-HJ-NP-Z1-9]{94}$'),
214 currency: CryptoCurrency.xmr,
215 ),
216
217 // Wownero - 97 characters
218 _DetectionPattern(
219 pattern: RegExp(r'^W[a-km-zA-HJ-NP-Z1-9]{96}$'),
220 currency: CryptoCurrency.wow,
221 ),
222
223 // Zano (long addresses) - 97 characters
224 _DetectionPattern(
225 pattern: RegExp(r'^Z[a-km-zA-HJ-NP-Z1-9]{96}$'),
226 currency: CryptoCurrency.zano,
227 ),
228
229 // Decred
230 _DetectionPattern(
231 pattern: RegExp(r'^([DTS])[ksecS][a-km-zA-HJ-NP-Z1-9]+$'),
232 currency: CryptoCurrency.dcr,
233 ),
234
235 // Dogecoin P2PKH
236 _DetectionPattern(
237 pattern: RegExp(r'^D[a-km-zA-HJ-NP-Z1-9]{25,34}$'),
238 currency: CryptoCurrency.doge,
239 ),
240
241 // Zcash (transparent, Sapling shielded, unified)
242 _DetectionPattern(
243 pattern: RegExp("^(?:${AddressValidator.zcashAddressPattern})\$"),
244 currency: CryptoCurrency.zec,
245 ),
246
247 // Solana (Base58 format)
248 _DetectionPattern(
249 pattern: RegExp(r'^[1-9A-HJ-NP-Za-km-z]{43,44}$'),
250 currency: CryptoCurrency.sol,
251 ),
252 ];
253
254 // Test each pattern in order of specificity
255 for (final pattern in detectionPatterns) {
256 if (pattern.pattern.hasMatch(cleanInput)) {
257 final walletType = cryptoCurrencyOrTokenToWalletType(pattern.currency);
258 final chainId = getChainIdByCryptoCurrency(pattern.currency);
259 final amount = _getAmountFromInvoice(cleanInput, pattern.currency);
260
261 return AddressDetectionResult(
262 address: cleanInput,
263 detectedCurrency: pattern.currency,
264 detectedWalletType: walletType,
265 isValid: true,
266 chainId: chainId,
267 amount: amount,
268 );
269 }
270 }
271
272 return AddressDetectionResult(
273 address: cleanInput,
274 detectedCurrency: null,
275 isValid: false,
276 );
277 }
278
279 static String? _getAmountFromInvoice(String input, CryptoCurrency currency) {
280 if (currency != CryptoCurrency.btcln) return null;
281 try {
282 return getBolt11Amount(input).toString();
283 } catch (e) {
284 return null;
285 }
286 }
287 }
288
289 class _DetectionPattern {
290 const _DetectionPattern({
291 required this.pattern,
292 required this.currency,
293 });
294
295 final RegExp pattern;
296 final CryptoCurrency currency;
297 }