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 0 : factory RawEvmTransaction.fromUnsignedHex(String hex) {
62 0 : final rawTxHex = hex.replaceFirst("0x", "");
63 :
64 : /// Check if the transaction is a Type 2 transaction
65 0 : if (rawTxHex.startsWith("02")) {
66 0 : return RawEVMTransactionType2.fromUnsignedHex(hex);
67 : }
68 :
69 : /// Check if the transaction is a Type 1 transaction
70 0 : if (rawTxHex.startsWith("01")) {
71 0 : return RawEVMTransactionType1.fromUnsignedHex(hex);
72 : }
73 :
74 0 : 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 1 : 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 1 : }) : r = BigInt.zero,
179 1 : s = BigInt.zero,
180 0 : 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 0 : factory RawEVMTransactionType0.fromUnsignedHex(String messageHex) {
211 0 : final rawTxHex = messageHex.replaceFirst("0x", "");
212 0 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
213 :
214 0 : if (rlpDecoded is! RLPList) {
215 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
216 : }
217 :
218 0 : if (rlpDecoded.length < 6) {
219 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
220 : }
221 :
222 0 : final nonce = rlpDecoded[0].buffer.toUBigInt;
223 0 : final gasPrice = rlpDecoded[1].buffer.toUBigInt;
224 0 : final gasLimit = rlpDecoded[2].buffer.toUBigInt;
225 0 : final to = "0x" + rlpDecoded[3].hex;
226 0 : final value = rlpDecoded[4].buffer.toUBigInt;
227 0 : final data = rlpDecoded[5].buffer;
228 0 : final chainId = rlpDecoded.length > 6 ? rlpDecoded[6].buffer.toUInt : null;
229 :
230 0 : 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 : item.storageKeys
422 3 : .map((key) => RLPString(key, isHex: true))
423 1 : .toList(),
424 : ),
425 : ],
426 : );
427 1 : }).toList(),
428 : ),
429 2 : RLPInt(signatureYParity),
430 2 : RLPBytes(signatureR),
431 2 : RLPBytes(signatureS),
432 : ],
433 : ),
434 : )
435 : ],
436 : );
437 : }
438 :
439 1 : @override
440 : bool get hasSignature =>
441 2 : signatureR.isNotEmpty &&
442 2 : signatureS.isNotEmpty &&
443 2 : (signatureYParity == 0 || signatureYParity == 1);
444 :
445 1 : Uint8List get serializedUnsigned {
446 1 : return Uint8List.fromList(
447 1 : [
448 : 0x01,
449 1 : ...encodeRLP(
450 1 : RLPList(
451 1 : [
452 2 : RLPInt(chainId),
453 2 : RLPBigInt(nonce),
454 2 : RLPBigInt(gasPrice),
455 2 : RLPBigInt(gasLimit),
456 2 : RLPString(to),
457 2 : RLPBigInt(value),
458 2 : RLPBytes(data),
459 1 : RLPList(
460 3 : accessList.map((item) {
461 1 : return RLPList(
462 1 : [
463 1 : RLPString(item.address),
464 1 : RLPList(
465 : item.storageKeys
466 3 : .map((key) => RLPString(key, isHex: true))
467 1 : .toList(),
468 : ),
469 : ],
470 : );
471 1 : }).toList(),
472 : ),
473 : ],
474 : ),
475 : ),
476 : ],
477 : );
478 : }
479 :
480 1 : @override
481 2 : Uint8List get signingTxHash => keccak256(serializedUnsigned);
482 :
483 0 : factory RawEVMTransactionType1.fromUnsignedHex(String rawTxHex) {
484 0 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
485 :
486 0 : if (rawTxHex.startsWith("01") == false) {
487 0 : throw Exception("Invalid Type 1 Transaction");
488 : }
489 :
490 0 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
491 :
492 0 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
493 :
494 0 : if (rlpDecoded is! RLPList) {
495 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
496 : }
497 :
498 0 : if (rlpDecoded.length < 8) {
499 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
500 : }
501 :
502 0 : return RawEVMTransactionType1.unsigned(
503 0 : chainId: rlpDecoded[0].buffer.toUInt,
504 0 : nonce: rlpDecoded[1].buffer.toUBigInt,
505 0 : gasPrice: rlpDecoded[2].buffer.toUBigInt,
506 0 : gasLimit: rlpDecoded[3].buffer.toUBigInt,
507 0 : to: "0x" + rlpDecoded[4].buffer.toHex,
508 0 : value: rlpDecoded[5].buffer.toUBigInt,
509 0 : data: rlpDecoded[6].buffer,
510 0 : accessList: switch (rlpDecoded[7]) {
511 0 : RLPList list => list.value
512 0 : .whereType<RLPList>()
513 0 : .map((item) {
514 0 : final subList = item[1];
515 0 : if (subList is RLPList)
516 : return (
517 0 : address: "0x" + item[0].buffer.toHex,
518 : storageKeys:
519 0 : subList.value.map((key) => key.buffer.toHex).toList(),
520 : );
521 : return null;
522 : })
523 0 : .nonNulls
524 0 : .toList(),
525 0 : _ => [],
526 : },
527 : );
528 : }
529 :
530 1 : factory RawEVMTransactionType1.fromHex(String rawTxHex) {
531 1 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
532 2 : if (rawTxHex.startsWith("01") == false) {
533 0 : throw Exception("Invalid Type 1 Transaction");
534 : }
535 1 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
536 :
537 2 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
538 :
539 1 : if (rlpDecoded is! RLPList) {
540 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
541 : }
542 :
543 2 : if (rlpDecoded.length < 11) {
544 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
545 : }
546 :
547 1 : return RawEVMTransactionType1(
548 3 : chainId: rlpDecoded[0].buffer.toUInt,
549 3 : nonce: rlpDecoded[1].buffer.toUBigInt,
550 3 : gasPrice: rlpDecoded[2].buffer.toUBigInt,
551 3 : gasLimit: rlpDecoded[3].buffer.toUBigInt,
552 4 : to: "0x" + rlpDecoded[4].buffer.toHex,
553 3 : value: rlpDecoded[5].buffer.toUBigInt,
554 2 : data: rlpDecoded[6].buffer,
555 1 : accessList: switch (rlpDecoded[7]) {
556 2 : RLPList list => list.value
557 1 : .whereType<RLPList>()
558 2 : .map((item) {
559 1 : final subList = item[1];
560 1 : if (subList is RLPList)
561 : return (
562 4 : address: "0x" + item[0].buffer.toHex,
563 : storageKeys:
564 6 : subList.value.map((key) => key.buffer.toHex).toList(),
565 : );
566 : return null;
567 : })
568 1 : .nonNulls
569 1 : .toList(),
570 0 : _ => [],
571 : },
572 3 : signatureYParity: rlpDecoded[8].buffer.toUInt,
573 2 : signatureR: rlpDecoded[9].buffer,
574 2 : signatureS: rlpDecoded[10].buffer,
575 : );
576 : }
577 : }
578 :
579 : class RawEVMTransactionType2 extends RawEvmTransaction {
580 : final int chainId;
581 : final BigInt maxFeePerGas;
582 : final BigInt maxPriorityFeePerGas;
583 : final List<AccessListItem> accessList;
584 : final int signatureYParity;
585 : final Uint8List signatureR;
586 : final Uint8List signatureS;
587 :
588 1 : const RawEVMTransactionType2({
589 : required super.nonce,
590 : required super.gasLimit,
591 : required super.to,
592 : required super.value,
593 : required super.data,
594 : required this.chainId,
595 : required this.maxFeePerGas,
596 : required this.maxPriorityFeePerGas,
597 : required this.accessList,
598 : required this.signatureR,
599 : required this.signatureS,
600 : required this.signatureYParity,
601 : });
602 :
603 0 : RawEVMTransactionType2.unsigned({
604 : required super.nonce,
605 : required super.gasLimit,
606 : required super.to,
607 : required super.value,
608 : required super.data,
609 : required this.chainId,
610 : required this.maxFeePerGas,
611 : required this.maxPriorityFeePerGas,
612 : required this.accessList,
613 0 : }) : signatureYParity = -1,
614 0 : signatureR = Uint8List(0),
615 0 : signatureS = Uint8List(0);
616 :
617 0 : RawEVMTransactionType2 addSignature(Signature signature) {
618 0 : return RawEVMTransactionType2(
619 0 : nonce: nonce,
620 0 : gasLimit: gasLimit,
621 0 : to: to,
622 0 : value: value,
623 0 : data: data,
624 0 : chainId: chainId,
625 0 : maxFeePerGas: maxFeePerGas,
626 0 : maxPriorityFeePerGas: maxPriorityFeePerGas,
627 0 : accessList: accessList,
628 0 : signatureYParity: signature.yParity,
629 0 : signatureR: signature.rBytes,
630 0 : signatureS: signature.sBytes,
631 : );
632 : }
633 :
634 0 : RawEVMTransactionType2 sign({
635 : required Uint8List privateKey,
636 : }) {
637 0 : final signature = Signature.createSignature(
638 0 : serializedUnsigned,
639 : privateKey,
640 : txType: TransactionType.Type2,
641 : );
642 :
643 0 : return addSignature(signature);
644 : }
645 :
646 0 : factory RawEVMTransactionType2.fromUnsignedHex(String rawTxHex) {
647 0 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
648 0 : assert(rawTxHex.startsWith("02"), "Invalid Type 1 Transaction");
649 0 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
650 :
651 0 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
652 :
653 0 : if (rlpDecoded is! RLPList) {
654 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
655 : }
656 :
657 0 : if (rlpDecoded.length < 9) {
658 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
659 : }
660 :
661 0 : return RawEVMTransactionType2.unsigned(
662 0 : chainId: rlpDecoded[0].buffer.toUInt,
663 0 : nonce: rlpDecoded[1].buffer.toUBigInt,
664 0 : maxPriorityFeePerGas: rlpDecoded[2].buffer.toUBigInt,
665 0 : maxFeePerGas: rlpDecoded[3].buffer.toUBigInt,
666 0 : gasLimit: rlpDecoded[4].buffer.toUBigInt,
667 0 : to: "0x" + rlpDecoded[5].buffer.toHex,
668 0 : value: rlpDecoded[6].buffer.toUBigInt,
669 0 : data: rlpDecoded[7].buffer,
670 0 : accessList: switch (rlpDecoded[8]) {
671 0 : RLPList list => list.value
672 0 : .whereType<RLPList>()
673 0 : .map((item) {
674 0 : final subList = item[1];
675 0 : if (subList is RLPList)
676 : return (
677 0 : address: "0x" + item[0].buffer.toHex,
678 : storageKeys:
679 0 : subList.value.map((key) => key.buffer.toHex).toList(),
680 : );
681 : return null;
682 : })
683 0 : .nonNulls
684 0 : .toList(),
685 0 : _ => [],
686 : },
687 : );
688 : }
689 :
690 1 : factory RawEVMTransactionType2.fromHex(String rawTxHex) {
691 1 : rawTxHex = rawTxHex.replaceFirst("0x", ""); // Remove the 0x prefix
692 2 : assert(rawTxHex.startsWith("02"), "Invalid Type 1 Transaction");
693 1 : rawTxHex = rawTxHex.substring(2); // Remove the type prefix
694 :
695 2 : final rlpDecoded = decodeRLP(rawTxHex.hexToBytes).$1;
696 :
697 1 : if (rlpDecoded is! RLPList) {
698 0 : throw Exception("Error RLP decoding transaction: $rlpDecoded");
699 : }
700 :
701 2 : if (rlpDecoded.length < 12) {
702 0 : throw Exception("Invalid transaction, missing fields: $rlpDecoded");
703 : }
704 :
705 1 : return RawEVMTransactionType2(
706 3 : chainId: rlpDecoded[0].buffer.toUInt,
707 3 : nonce: rlpDecoded[1].buffer.toUBigInt,
708 3 : maxPriorityFeePerGas: rlpDecoded[2].buffer.toUBigInt,
709 3 : maxFeePerGas: rlpDecoded[3].buffer.toUBigInt,
710 3 : gasLimit: rlpDecoded[4].buffer.toUBigInt,
711 4 : to: "0x" + rlpDecoded[5].buffer.toHex,
712 3 : value: rlpDecoded[6].buffer.toUBigInt,
713 2 : data: rlpDecoded[7].buffer,
714 1 : accessList: switch (rlpDecoded[8]) {
715 2 : RLPList list => list.value
716 1 : .whereType<RLPList>()
717 1 : .map((item) {
718 0 : final subList = item[1];
719 0 : if (subList is RLPList)
720 : return (
721 0 : address: "0x" + item[0].buffer.toHex,
722 : storageKeys:
723 0 : subList.value.map((key) => key.buffer.toHex).toList(),
724 : );
725 : return null;
726 : })
727 1 : .nonNulls
728 1 : .toList(),
729 0 : _ => [],
730 : },
731 3 : signatureYParity: rlpDecoded[9].buffer.toUInt,
732 2 : signatureR: rlpDecoded[10].buffer,
733 2 : signatureS: rlpDecoded[11].buffer,
734 : );
735 : }
736 :
737 1 : @override
738 : String get sender {
739 2 : if (hasSignature == false) {
740 0 : throw Exception("Transaction is not signed, cannot recover sender");
741 : }
742 :
743 1 : final signature = Signature.fromBytes(
744 2 : Uint8List.fromList([
745 1 : ...signatureR,
746 1 : ...signatureS,
747 1 : signatureYParity,
748 : ]),
749 : );
750 :
751 1 : final publicKey = recoverPublicKey(
752 1 : signingTxHash,
753 : signature,
754 : hasSignatureYParity: true,
755 : );
756 :
757 1 : final addressBytes = publicKeyToAddress(publicKey);
758 :
759 1 : final address = addressBytes.toHex;
760 1 : return "0x$address";
761 : }
762 :
763 1 : @override
764 : Uint8List get serialized {
765 2 : if (hasSignature == false) {
766 0 : throw Exception("Transaction is not signed, cannot serialize");
767 : }
768 :
769 1 : return Uint8List.fromList(
770 1 : [
771 : 0x02,
772 1 : ...encodeRLP(
773 1 : RLPList(
774 1 : [
775 2 : RLPInt(chainId),
776 2 : RLPBigInt(nonce),
777 2 : RLPBigInt(maxPriorityFeePerGas),
778 2 : RLPBigInt(maxFeePerGas),
779 2 : RLPBigInt(gasLimit),
780 2 : RLPString(to),
781 2 : RLPBigInt(value),
782 2 : RLPBytes(data),
783 1 : RLPList(
784 2 : accessList.map((item) {
785 0 : return RLPList(
786 0 : [
787 0 : RLPString(item.address),
788 0 : RLPList(
789 : item.storageKeys
790 0 : .map((key) => RLPString(key, isHex: true))
791 0 : .toList(),
792 : ),
793 : ],
794 : );
795 1 : }).toList(),
796 : ),
797 2 : RLPInt(signatureYParity),
798 2 : RLPBytes(signatureR),
799 2 : RLPBytes(signatureS),
800 : ],
801 : ),
802 : ),
803 : ],
804 : );
805 : }
806 :
807 1 : @override
808 : bool get hasSignature =>
809 2 : signatureR.isNotEmpty &&
810 2 : signatureS.isNotEmpty &&
811 4 : (signatureYParity == 0 || signatureYParity == 1);
812 :
813 1 : Uint8List get serializedUnsigned {
814 1 : return Uint8List.fromList(
815 1 : [
816 : 0x02,
817 1 : ...encodeRLP(
818 1 : RLPList(
819 1 : [
820 2 : RLPInt(chainId),
821 2 : RLPBigInt(nonce),
822 2 : RLPBigInt(maxPriorityFeePerGas),
823 2 : RLPBigInt(maxFeePerGas),
824 2 : RLPBigInt(gasLimit),
825 2 : RLPString(to),
826 2 : RLPBigInt(value),
827 2 : RLPBytes(data),
828 1 : RLPList(
829 2 : accessList.map((item) {
830 0 : return RLPList(
831 0 : [
832 0 : RLPString(item.address),
833 0 : RLPList(
834 : item.storageKeys
835 0 : .map((key) => RLPString(key, isHex: true))
836 0 : .toList(),
837 : ),
838 : ],
839 : );
840 1 : }).toList(),
841 : ),
842 : ],
843 : ),
844 : ),
845 : ],
846 : );
847 : }
848 :
849 1 : @override
850 2 : Uint8List get signingTxHash => keccak256(serializedUnsigned);
851 : }
|