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: 60.0 % 435 261
Test Date: 2025-01-30 01:10:00 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            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              : }
        

Generated by: LCOV version 2.0-1