Line data Source code
1 : import 'dart:typed_data';
2 : import 'package:walletkit_dart/src/utils/keccak.dart';
3 : import 'package:walletkit_dart/walletkit_dart.dart';
4 :
5 : sealed class RawEvmTransaction {
6 : final BigInt nonce;
7 : final BigInt gasLimit;
8 : final String to;
9 : final BigInt value;
10 : final Uint8List data;
11 :
12 3 : const RawEvmTransaction({
13 : required this.nonce,
14 : required this.gasLimit,
15 : required this.to,
16 : required this.value,
17 : required this.data,
18 : });
19 :
20 : int? get chainId;
21 :
22 0 : @override
23 0 : int get hashCode => txHash.hashCode;
24 :
25 1 : @override
26 : bool operator ==(Object other) {
27 : if (identical(this, other)) return true;
28 :
29 4 : return other is RawEvmTransaction && other.txHash == txHash;
30 : }
31 :
32 5 : String get txHash => "0x" + keccak256(serialized).toHex;
33 :
34 : Uint8List get signingTxHash;
35 :
36 : bool get hasSignature;
37 :
38 : String get sender;
39 :
40 : /// Used for Signing the transaction
41 : Uint8List get serialized;
42 :
43 0 : String get serializedHex => "0x" + serialized.toHex;
44 :
45 1 : factory RawEvmTransaction.fromHex(String hex) {
46 1 : final rawTxHex = hex.replaceFirst("0x", "");
47 :
48 : /// Check if the transaction is a Type 2 transaction
49 1 : if (rawTxHex.startsWith("02")) {
50 1 : return RawEVMTransactionType2.fromHex(hex);
51 : }
52 :
53 : /// Check if the transaction is a Type 1 transaction
54 1 : if (rawTxHex.startsWith("01")) {
55 1 : return RawEVMTransactionType1.fromHex(hex);
56 : }
57 :
58 1 : return RawEVMTransactionType0.fromHex(hex);
59 : }
60 :
61 1 : factory RawEvmTransaction.fromUnsignedHex(String hex) {
62 1 : final rawTxHex = hex.replaceFirst("0x", "");
63 :
64 : /// Check if the transaction is a Type 2 transaction
65 1 : if (rawTxHex.startsWith("02")) {
66 0 : return RawEVMTransactionType2.fromUnsignedHex(hex);
67 : }
68 :
69 : /// Check if the transaction is a Type 1 transaction
70 1 : if (rawTxHex.startsWith("01")) {
71 0 : return RawEVMTransactionType1.fromUnsignedHex(hex);
72 : }
73 :
74 1 : return RawEVMTransactionType0.fromUnsignedHex(hex);
75 : }
76 :
77 0 : int get txType => switch (this) {
78 0 : RawEVMTransactionType0() => 0,
79 0 : RawEVMTransactionType1() => 1,
80 0 : RawEVMTransactionType2() => 2,
81 : };
82 :
83 : RawEvmTransaction addSignature(Signature signature);
84 :
85 0 : BigInt get gasFee {
86 : return switch (this) {
87 0 : RawEVMTransactionType0 type0 => type0.gasPrice * type0.gasLimit,
88 0 : RawEVMTransactionType1 type1 => type1.gasPrice * type1.gasLimit,
89 0 : RawEVMTransactionType2 type2 => type2.maxFeePerGas * type2.gasLimit,
90 : };
91 : }
92 :
93 0 : @override
94 : String toString() {
95 0 : return "RawEvmTransaction($txType):{nonce: $nonce, gasLimit: $gasLimit, to: $to, value: $value, data: $data}";
96 : }
97 : }
98 :
99 : class RawEVMTransactionType0 extends RawEvmTransaction {
100 : final BigInt gasPrice;
101 : final BigInt r, s;
102 : final int v;
103 :
104 : /// The chainId is optional for transactions with v = 27 or v = 28 (EIP-155)
105 : /// For other transactions, chainId is calculated as (v - 35) / 2 for odd v
106 : /// and (v - 36) / 2 for even v
107 2 : int? get chainId {
108 8 : if (v == 27 || v == 28) {
109 : return null;
110 : }
111 :
112 4 : if (v.isOdd) {
113 3 : return (v - 35) ~/ 2;
114 : }
115 :
116 3 : return (v - 36) ~/ 2;
117 : }
118 :
119 4 : Uint8List get signingTxHash => keccak256(serializedUnsigned(chainId));
120 :
121 1 : String get sender {
122 2 : if (hasSignature == false) {
123 0 : throw Exception("Transaction is not signed, cannot recover sender");
124 : }
125 :
126 4 : final signature = Signature.fromRSV(r, s, v);
127 :
128 2 : final publicKey = recoverPublicKey(signingTxHash, signature);
129 :
130 1 : final addressBytes = publicKeyToAddress(publicKey);
131 :
132 1 : final address = addressBytes.toHex;
133 1 : return "0x$address";
134 : }
135 :
136 2 : Uint8List get serialized {
137 4 : if (hasSignature == false) {
138 0 : throw Exception("Transaction is not signed, cannot serialize");
139 : }
140 :
141 2 : return encodeRLP(
142 2 : RLPList(
143 2 : [
144 4 : RLPBigInt(nonce),
145 4 : RLPBigInt(gasPrice),
146 4 : RLPBigInt(gasLimit),
147 4 : RLPString(to),
148 4 : RLPBigInt(value),
149 4 : RLPBytes(data),
150 4 : RLPInt(v),
151 4 : RLPBigInt(r),
152 4 : RLPBigInt(s),
153 : ],
154 : ),
155 : );
156 : }
157 :
158 3 : const RawEVMTransactionType0({
159 : required super.nonce,
160 : required super.gasLimit,
161 : required super.to,
162 : required super.value,
163 : required super.data,
164 : required this.gasPrice,
165 : required this.r,
166 : required this.s,
167 : required this.v,
168 : });
169 :
170 2 : RawEVMTransactionType0.unsigned({
171 : required super.nonce,
172 : required super.gasLimit,
173 : required super.to,
174 : required super.value,
175 : required super.data,
176 : required this.gasPrice,
177 : int? chainId,
178 2 : }) : r = BigInt.zero,
179 2 : s = BigInt.zero,
180 2 : v = chainId != null ? chainId * 2 + 35 : 27;
181 :
182 1 : RawEVMTransactionType0 addSignature(Signature signature) {
183 1 : return RawEVMTransactionType0(
184 1 : nonce: nonce,
185 1 : gasLimit: gasLimit,
186 1 : to: to,
187 1 : value: value,
188 1 : data: data,
189 1 : gasPrice: gasPrice,
190 1 : r: signature.r,
191 1 : s: signature.s,
192 1 : v: signature.v,
193 : );
194 : }
195 :
196 0 : RawEVMTransactionType0 sign({
197 : required Uint8List privateKey,
198 : required int chainId,
199 : }) {
200 0 : final signature = Signature.createSignature(
201 0 : serializedUnsigned(chainId),
202 : privateKey,
203 : txType: TransactionType.Legacy,
204 : chainId: chainId,
205 : );
206 :
207 0 : return addSignature(signature);
208 : }
209 :
210 1 : factory RawEVMTransactionType0.fromUnsignedHex(String messageHex) {
211 1 : final rawTxHex = messageHex.replaceFirst("0x", "");
212 2 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
213 :
214 1 : if (rlpDecoded is! RLPList) {
215 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
216 : }
217 :
218 2 : if (rlpDecoded.length < 6) {
219 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
220 : }
221 :
222 3 : final nonce = rlpDecoded[0].buffer.toUBigInt;
223 3 : final gasPrice = rlpDecoded[1].buffer.toUBigInt;
224 3 : final gasLimit = rlpDecoded[2].buffer.toUBigInt;
225 3 : final to = "0x" + rlpDecoded[3].hex;
226 3 : final value = rlpDecoded[4].buffer.toUBigInt;
227 2 : final data = rlpDecoded[5].buffer;
228 5 : final chainId = rlpDecoded.length > 6 ? rlpDecoded[6].buffer.toUInt : null;
229 :
230 1 : return RawEVMTransactionType0.unsigned(
231 : nonce: nonce,
232 : gasPrice: gasPrice,
233 : gasLimit: gasLimit,
234 : to: to,
235 : value: value,
236 : data: data,
237 : chainId: chainId,
238 : );
239 : }
240 :
241 3 : factory RawEVMTransactionType0.fromHex(String messageHex) {
242 3 : final rawTxHex = messageHex.replaceFirst("0x", "");
243 6 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
244 :
245 3 : if (rlpDecoded is! RLPList) {
246 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
247 : }
248 :
249 6 : if (rlpDecoded.length < 9) {
250 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
251 : }
252 :
253 9 : final nonce = rlpDecoded[0].buffer.toUBigInt;
254 9 : final gasPrice = rlpDecoded[1].buffer.toUBigInt;
255 9 : final gasLimit = rlpDecoded[2].buffer.toUBigInt;
256 9 : final to = "0x" + rlpDecoded[3].hex;
257 9 : final value = rlpDecoded[4].buffer.toUBigInt;
258 6 : final data = rlpDecoded[5].buffer;
259 :
260 9 : final v = rlpDecoded[6].buffer.toUInt;
261 9 : final r = rlpDecoded[7].buffer.toUBigInt;
262 9 : final s = rlpDecoded[8].buffer.toUBigInt;
263 :
264 3 : return RawEVMTransactionType0(
265 : nonce: nonce,
266 : gasPrice: gasPrice,
267 : gasLimit: gasLimit,
268 : to: to,
269 : value: value,
270 : data: data,
271 : v: v,
272 : r: r,
273 : s: s,
274 : );
275 : }
276 :
277 3 : Uint8List serializedUnsigned([int? chainId]) {
278 3 : return encodeRLP(
279 3 : RLPList(
280 3 : [
281 6 : RLPBigInt(nonce),
282 6 : RLPBigInt(gasPrice),
283 6 : RLPBigInt(gasLimit),
284 6 : RLPString(to),
285 6 : RLPBigInt(value),
286 6 : RLPBytes(data),
287 3 : if (chainId != null) ...[
288 3 : RLPInt(chainId),
289 3 : RLPNull(),
290 3 : RLPNull(),
291 : ]
292 : ],
293 : ),
294 : );
295 : }
296 :
297 0 : BigInt get gasFee {
298 0 : return gasPrice * gasLimit;
299 : }
300 :
301 2 : @override
302 12 : bool get hasSignature => r != BigInt.zero && s != BigInt.zero;
303 : }
304 :
305 : typedef AccessListItem = ({String address, List<String> storageKeys});
306 :
307 : class RawEVMTransactionType1 extends RawEvmTransaction {
308 : final int chainId;
309 : final BigInt gasPrice;
310 : final List<AccessListItem> accessList;
311 : final int signatureYParity;
312 : final Uint8List signatureR;
313 : final Uint8List signatureS;
314 :
315 1 : const RawEVMTransactionType1({
316 : required super.nonce,
317 : required super.gasLimit,
318 : required super.to,
319 : required super.value,
320 : required super.data,
321 : required this.chainId,
322 : required this.gasPrice,
323 : required this.accessList,
324 : required this.signatureYParity,
325 : required this.signatureR,
326 : required this.signatureS,
327 : });
328 :
329 0 : RawEVMTransactionType1.unsigned({
330 : required super.nonce,
331 : required super.gasLimit,
332 : required super.to,
333 : required super.value,
334 : required super.data,
335 : required this.chainId,
336 : required this.gasPrice,
337 : required this.accessList,
338 0 : }) : signatureYParity = -1,
339 0 : signatureR = Uint8List(0),
340 0 : signatureS = Uint8List(0);
341 :
342 0 : RawEVMTransactionType1 addSignature(Signature signature) {
343 0 : return RawEVMTransactionType1(
344 0 : nonce: nonce,
345 0 : gasLimit: gasLimit,
346 0 : to: to,
347 0 : value: value,
348 0 : data: data,
349 0 : chainId: chainId,
350 0 : gasPrice: gasPrice,
351 0 : accessList: accessList,
352 0 : signatureYParity: signature.yParity,
353 0 : signatureR: signature.rBytes,
354 0 : signatureS: signature.sBytes,
355 : );
356 : }
357 :
358 0 : RawEVMTransactionType1 sign({
359 : required Uint8List privateKey,
360 : }) {
361 0 : final signature = Signature.createSignature(
362 0 : serializedUnsigned,
363 : privateKey,
364 : txType: TransactionType.Type1,
365 : );
366 :
367 0 : return addSignature(signature);
368 : }
369 :
370 1 : @override
371 : String get sender {
372 2 : if (hasSignature == false) {
373 0 : throw Exception("Transaction is not signed, cannot recover sender");
374 : }
375 :
376 1 : final signature = Signature.fromBytes(
377 2 : Uint8List.fromList([
378 1 : ...signatureR,
379 1 : ...signatureS,
380 1 : signatureYParity,
381 : ]),
382 : );
383 :
384 1 : final publicKey = recoverPublicKey(
385 1 : signingTxHash,
386 : signature,
387 : hasSignatureYParity: true,
388 : );
389 :
390 1 : final addressBytes = publicKeyToAddress(publicKey);
391 :
392 1 : final address = addressBytes.toHex;
393 1 : return "0x$address";
394 : }
395 :
396 1 : @override
397 : Uint8List get serialized {
398 2 : if (hasSignature == false) {
399 0 : throw Exception("Transaction is not signed, cannot serialize");
400 : }
401 :
402 1 : return Uint8List.fromList(
403 1 : [
404 : 0x01,
405 1 : ...encodeRLP(
406 1 : RLPList(
407 1 : [
408 2 : RLPInt(chainId),
409 2 : RLPBigInt(nonce),
410 2 : RLPBigInt(gasPrice),
411 2 : RLPBigInt(gasLimit),
412 2 : RLPString(to),
413 2 : RLPBigInt(value),
414 2 : RLPBytes(data),
415 1 : RLPList(
416 3 : accessList.map((item) {
417 1 : return RLPList(
418 1 : [
419 1 : RLPString(item.address),
420 1 : RLPList(
421 4 : item.storageKeys.map((key) => RLPString(key, isHex: true)).toList(),
422 : ),
423 : ],
424 : );
425 1 : }).toList(),
426 : ),
427 2 : RLPInt(signatureYParity),
428 2 : RLPBytes(signatureR),
429 2 : RLPBytes(signatureS),
430 : ],
431 : ),
432 : )
433 : ],
434 : );
435 : }
436 :
437 1 : @override
438 : bool get hasSignature =>
439 2 : signatureR.isNotEmpty &&
440 2 : signatureS.isNotEmpty &&
441 2 : (signatureYParity == 0 || signatureYParity == 1);
442 :
443 1 : Uint8List get serializedUnsigned {
444 1 : return Uint8List.fromList(
445 1 : [
446 : 0x01,
447 1 : ...encodeRLP(
448 1 : RLPList(
449 1 : [
450 2 : RLPInt(chainId),
451 2 : RLPBigInt(nonce),
452 2 : RLPBigInt(gasPrice),
453 2 : RLPBigInt(gasLimit),
454 2 : RLPString(to),
455 2 : RLPBigInt(value),
456 2 : RLPBytes(data),
457 1 : RLPList(
458 3 : accessList.map((item) {
459 1 : return RLPList(
460 1 : [
461 1 : RLPString(item.address),
462 1 : RLPList(
463 4 : item.storageKeys.map((key) => RLPString(key, isHex: true)).toList(),
464 : ),
465 : ],
466 : );
467 1 : }).toList(),
468 : ),
469 : ],
470 : ),
471 : ),
472 : ],
473 : );
474 : }
475 :
476 1 : @override
477 2 : Uint8List get signingTxHash => keccak256(serializedUnsigned);
478 :
479 0 : factory RawEVMTransactionType1.fromUnsignedHex(String rawTxHex) {
480 0 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
481 :
482 0 : if (rawTxHex.startsWith("01") == false) {
483 0 : throw Exception("Invalid Type 1 Transaction");
484 : }
485 :
486 0 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
487 :
488 0 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
489 :
490 0 : if (rlpDecoded is! RLPList) {
491 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
492 : }
493 :
494 0 : if (rlpDecoded.length < 8) {
495 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
496 : }
497 :
498 0 : return RawEVMTransactionType1.unsigned(
499 0 : chainId: rlpDecoded[0].buffer.toUInt,
500 0 : nonce: rlpDecoded[1].buffer.toUBigInt,
501 0 : gasPrice: rlpDecoded[2].buffer.toUBigInt,
502 0 : gasLimit: rlpDecoded[3].buffer.toUBigInt,
503 0 : to: "0x" + rlpDecoded[4].buffer.toHex,
504 0 : value: rlpDecoded[5].buffer.toUBigInt,
505 0 : data: rlpDecoded[6].buffer,
506 0 : accessList: switch (rlpDecoded[7]) {
507 0 : RLPList list => list.value
508 0 : .whereType<RLPList>()
509 0 : .map((item) {
510 0 : final subList = item[1];
511 0 : if (subList is RLPList)
512 : return (
513 0 : address: "0x" + item[0].buffer.toHex,
514 0 : storageKeys: subList.value.map((key) => key.buffer.toHex).toList(),
515 : );
516 : return null;
517 : })
518 0 : .nonNulls
519 0 : .toList(),
520 0 : _ => [],
521 : },
522 : );
523 : }
524 :
525 1 : factory RawEVMTransactionType1.fromHex(String rawTxHex) {
526 1 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
527 2 : if (rawTxHex.startsWith("01") == false) {
528 0 : throw Exception("Invalid Type 1 Transaction");
529 : }
530 1 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
531 :
532 2 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
533 :
534 1 : if (rlpDecoded is! RLPList) {
535 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
536 : }
537 :
538 2 : if (rlpDecoded.length < 11) {
539 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
540 : }
541 :
542 1 : return RawEVMTransactionType1(
543 3 : chainId: rlpDecoded[0].buffer.toUInt,
544 3 : nonce: rlpDecoded[1].buffer.toUBigInt,
545 3 : gasPrice: rlpDecoded[2].buffer.toUBigInt,
546 3 : gasLimit: rlpDecoded[3].buffer.toUBigInt,
547 4 : to: "0x" + rlpDecoded[4].buffer.toHex,
548 3 : value: rlpDecoded[5].buffer.toUBigInt,
549 2 : data: rlpDecoded[6].buffer,
550 1 : accessList: switch (rlpDecoded[7]) {
551 2 : RLPList list => list.value
552 1 : .whereType<RLPList>()
553 2 : .map((item) {
554 1 : final subList = item[1];
555 1 : if (subList is RLPList)
556 : return (
557 4 : address: "0x" + item[0].buffer.toHex,
558 6 : storageKeys: subList.value.map((key) => key.buffer.toHex).toList(),
559 : );
560 : return null;
561 : })
562 1 : .nonNulls
563 1 : .toList(),
564 0 : _ => [],
565 : },
566 3 : signatureYParity: rlpDecoded[8].buffer.toUInt,
567 2 : signatureR: rlpDecoded[9].buffer,
568 2 : signatureS: rlpDecoded[10].buffer,
569 : );
570 : }
571 : }
572 :
573 : class RawEVMTransactionType2 extends RawEvmTransaction {
574 : final int chainId;
575 : final BigInt maxFeePerGas;
576 : final BigInt maxPriorityFeePerGas;
577 : final List<AccessListItem> accessList;
578 : final int signatureYParity;
579 : final Uint8List signatureR;
580 : final Uint8List signatureS;
581 :
582 1 : const RawEVMTransactionType2({
583 : required super.nonce,
584 : required super.gasLimit,
585 : required super.to,
586 : required super.value,
587 : required super.data,
588 : required this.chainId,
589 : required this.maxFeePerGas,
590 : required this.maxPriorityFeePerGas,
591 : required this.accessList,
592 : required this.signatureR,
593 : required this.signatureS,
594 : required this.signatureYParity,
595 : });
596 :
597 0 : RawEVMTransactionType2.unsigned({
598 : required super.nonce,
599 : required super.gasLimit,
600 : required super.to,
601 : required super.value,
602 : required super.data,
603 : required this.chainId,
604 : required this.maxFeePerGas,
605 : required this.maxPriorityFeePerGas,
606 : required this.accessList,
607 0 : }) : signatureYParity = -1,
608 0 : signatureR = Uint8List(0),
609 0 : signatureS = Uint8List(0);
610 :
611 0 : RawEVMTransactionType2 addSignature(Signature signature) {
612 0 : return RawEVMTransactionType2(
613 0 : nonce: nonce,
614 0 : gasLimit: gasLimit,
615 0 : to: to,
616 0 : value: value,
617 0 : data: data,
618 0 : chainId: chainId,
619 0 : maxFeePerGas: maxFeePerGas,
620 0 : maxPriorityFeePerGas: maxPriorityFeePerGas,
621 0 : accessList: accessList,
622 0 : signatureYParity: signature.yParity,
623 0 : signatureR: signature.rBytes,
624 0 : signatureS: signature.sBytes,
625 : );
626 : }
627 :
628 0 : RawEVMTransactionType2 sign({
629 : required Uint8List privateKey,
630 : }) {
631 0 : final signature = Signature.createSignature(
632 0 : serializedUnsigned,
633 : privateKey,
634 : txType: TransactionType.Type2,
635 : );
636 :
637 0 : return addSignature(signature);
638 : }
639 :
640 0 : factory RawEVMTransactionType2.fromUnsignedHex(String rawTxHex) {
641 0 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
642 0 : assert(rawTxHex.startsWith("02"), "Invalid Type 1 Transaction");
643 0 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
644 :
645 0 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
646 :
647 0 : if (rlpDecoded is! RLPList) {
648 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
649 : }
650 :
651 0 : if (rlpDecoded.length < 9) {
652 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
653 : }
654 :
655 0 : return RawEVMTransactionType2.unsigned(
656 0 : chainId: rlpDecoded[0].buffer.toUInt,
657 0 : nonce: rlpDecoded[1].buffer.toUBigInt,
658 0 : maxPriorityFeePerGas: rlpDecoded[2].buffer.toUBigInt,
659 0 : maxFeePerGas: rlpDecoded[3].buffer.toUBigInt,
660 0 : gasLimit: rlpDecoded[4].buffer.toUBigInt,
661 0 : to: "0x" + rlpDecoded[5].buffer.toHex,
662 0 : value: rlpDecoded[6].buffer.toUBigInt,
663 0 : data: rlpDecoded[7].buffer,
664 0 : accessList: switch (rlpDecoded[8]) {
665 0 : RLPList list => list.value
666 0 : .whereType<RLPList>()
667 0 : .map((item) {
668 0 : final subList = item[1];
669 0 : if (subList is RLPList)
670 : return (
671 0 : address: "0x" + item[0].buffer.toHex,
672 0 : storageKeys: subList.value.map((key) => key.buffer.toHex).toList(),
673 : );
674 : return null;
675 : })
676 0 : .nonNulls
677 0 : .toList(),
678 0 : _ => [],
679 : },
680 : );
681 : }
682 :
683 1 : factory RawEVMTransactionType2.fromHex(String rawTxHex) {
684 1 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
685 2 : assert(rawTxHex.startsWith("02"), "Invalid Type 1 Transaction");
686 1 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
687 :
688 2 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
689 :
690 1 : if (rlpDecoded is! RLPList) {
691 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
692 : }
693 :
694 2 : if (rlpDecoded.length < 12) {
695 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
696 : }
697 :
698 1 : return RawEVMTransactionType2(
699 3 : chainId: rlpDecoded[0].buffer.toUInt,
700 3 : nonce: rlpDecoded[1].buffer.toUBigInt,
701 3 : maxPriorityFeePerGas: rlpDecoded[2].buffer.toUBigInt,
702 3 : maxFeePerGas: rlpDecoded[3].buffer.toUBigInt,
703 3 : gasLimit: rlpDecoded[4].buffer.toUBigInt,
704 4 : to: "0x" + rlpDecoded[5].buffer.toHex,
705 3 : value: rlpDecoded[6].buffer.toUBigInt,
706 2 : data: rlpDecoded[7].buffer,
707 1 : accessList: switch (rlpDecoded[8]) {
708 2 : RLPList list => list.value
709 1 : .whereType<RLPList>()
710 1 : .map((item) {
711 0 : final subList = item[1];
712 0 : if (subList is RLPList)
713 : return (
714 0 : address: "0x" + item[0].buffer.toHex,
715 0 : storageKeys: subList.value.map((key) => key.buffer.toHex).toList(),
716 : );
717 : return null;
718 : })
719 1 : .nonNulls
720 1 : .toList(),
721 0 : _ => [],
722 : },
723 3 : signatureYParity: rlpDecoded[9].buffer.toUInt,
724 2 : signatureR: rlpDecoded[10].buffer,
725 2 : signatureS: rlpDecoded[11].buffer,
726 : );
727 : }
728 :
729 1 : @override
730 : String get sender {
731 2 : if (hasSignature == false) {
732 0 : throw Exception("Transaction is not signed, cannot recover sender");
733 : }
734 :
735 1 : final signature = Signature.fromBytes(
736 2 : Uint8List.fromList([
737 1 : ...signatureR,
738 1 : ...signatureS,
739 1 : signatureYParity,
740 : ]),
741 : );
742 :
743 1 : final publicKey = recoverPublicKey(
744 1 : signingTxHash,
745 : signature,
746 : hasSignatureYParity: true,
747 : );
748 :
749 1 : final addressBytes = publicKeyToAddress(publicKey);
750 :
751 1 : final address = addressBytes.toHex;
752 1 : return "0x$address";
753 : }
754 :
755 1 : @override
756 : Uint8List get serialized {
757 2 : if (hasSignature == false) {
758 0 : throw Exception("Transaction is not signed, cannot serialize");
759 : }
760 :
761 1 : return Uint8List.fromList(
762 1 : [
763 : 0x02,
764 1 : ...encodeRLP(
765 1 : RLPList(
766 1 : [
767 2 : RLPInt(chainId),
768 2 : RLPBigInt(nonce),
769 2 : RLPBigInt(maxPriorityFeePerGas),
770 2 : RLPBigInt(maxFeePerGas),
771 2 : RLPBigInt(gasLimit),
772 2 : RLPString(to),
773 2 : RLPBigInt(value),
774 2 : RLPBytes(data),
775 1 : RLPList(
776 2 : accessList.map((item) {
777 0 : return RLPList(
778 0 : [
779 0 : RLPString(item.address),
780 0 : RLPList(
781 0 : item.storageKeys.map((key) => RLPString(key, isHex: true)).toList(),
782 : ),
783 : ],
784 : );
785 1 : }).toList(),
786 : ),
787 2 : RLPInt(signatureYParity),
788 2 : RLPBytes(signatureR),
789 2 : RLPBytes(signatureS),
790 : ],
791 : ),
792 : ),
793 : ],
794 : );
795 : }
796 :
797 1 : @override
798 : bool get hasSignature =>
799 2 : signatureR.isNotEmpty &&
800 2 : signatureS.isNotEmpty &&
801 4 : (signatureYParity == 0 || signatureYParity == 1);
802 :
803 1 : Uint8List get serializedUnsigned {
804 1 : return Uint8List.fromList(
805 1 : [
806 : 0x02,
807 1 : ...encodeRLP(
808 1 : RLPList(
809 1 : [
810 2 : RLPInt(chainId),
811 2 : RLPBigInt(nonce),
812 2 : RLPBigInt(maxPriorityFeePerGas),
813 2 : RLPBigInt(maxFeePerGas),
814 2 : RLPBigInt(gasLimit),
815 2 : RLPString(to),
816 2 : RLPBigInt(value),
817 2 : RLPBytes(data),
818 1 : RLPList(
819 2 : accessList.map((item) {
820 0 : return RLPList(
821 0 : [
822 0 : RLPString(item.address),
823 0 : RLPList(
824 0 : item.storageKeys.map((key) => RLPString(key, isHex: true)).toList(),
825 : ),
826 : ],
827 : );
828 1 : }).toList(),
829 : ),
830 : ],
831 : ),
832 : ),
833 : ],
834 : );
835 : }
836 :
837 1 : @override
838 2 : Uint8List get signingTxHash => keccak256(serializedUnsigned);
839 : }
|