LCOV - code coverage report
Current view: top level - crypto/evm/entities/raw_transaction - raw_evm_transaction.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 64.5 % 431 278
Test Date: 2025-06-28 01:20:13 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.0-1