11import { inject , injectable } from "@mainsail/container" ;
22import { Contracts , Identifiers } from "@mainsail/contracts" ;
33import { BigNumber } from "@mainsail/utils" ;
4- import { decodeRlp , ethers , getAddress } from "ethers" ;
4+ import { decodeRlp , ethers , getAddress , RlpStructuredData } from "ethers" ;
55
66@injectable ( )
77export class Deserializer implements Contracts . Crypto . TransactionDeserializer {
88 @inject ( Identifiers . Cryptography . Transaction . TypeFactory )
99 private readonly transactionTypeFactory ! : Contracts . Transactions . TransactionTypeFactory ;
1010
11+ @inject ( Identifiers . Cryptography . Configuration )
12+ private readonly configuration ! : Contracts . Crypto . Configuration ;
13+
1114 public async deserialize ( serialized : Buffer | string ) : Promise < Contracts . Crypto . Transaction > {
1215 const data = { } as Contracts . Crypto . TransactionData ;
1316
14- const encodedRlp =
15- "0x" + ( typeof serialized === "string" ? serialized . slice ( 2 ) : serialized . toString ( "hex" ) . slice ( 2 ) ) ;
17+ let rlpBuffer =
18+ typeof serialized === "string"
19+ ? Buffer . from ( serialized . startsWith ( "0x" ) ? serialized . slice ( 2 ) : serialized , "hex" )
20+ : serialized ;
21+
22+ // Remove type prefix (e.g. 02) if it's a EIP1559 tx (`decodeRlp` expects input to be without)
23+ const isPrefixed = rlpBuffer [ 0 ] < 0xc0 ;
24+ if ( isPrefixed ) {
25+ if ( rlpBuffer [ 0 ] !== 0x02 ) {
26+ throw new Error ( "expected EIP1559 transaction" ) ;
27+ }
28+
29+ rlpBuffer = rlpBuffer . subarray ( 1 ) ;
30+ }
31+
32+ const decoded = decodeRlp ( new Uint8Array ( rlpBuffer ) ) ;
33+
34+ if ( isPrefixed ) {
35+ this . #decodeEIP1559Transaction( decoded , data ) ;
36+ } else {
37+ this . #decodeLegacyTransaction( decoded , data ) ;
38+ }
39+
40+ const instance : Contracts . Crypto . Transaction = this . transactionTypeFactory . create ( data ) ;
41+
42+ const eip1559Prefix = 0x02 ; // marker for Type 2 (EIP1559) transaction which is the standard nowadays
43+ instance . serialized = isPrefixed ? Buffer . concat ( [ Buffer . from ( [ eip1559Prefix ] ) , rlpBuffer ] ) : rlpBuffer ;
44+
45+ return instance ;
46+ }
1647
17- const decoded = decodeRlp ( encodedRlp ) ;
48+ #decodeEIP1559Transaction( decoded : RlpStructuredData , data : Contracts . Crypto . TransactionData ) : void {
49+ if ( decoded . length < 9 || decoded . length > 13 ) {
50+ throw new Error ( "RLP data out of range" ) ;
51+ }
1852
1953 const recipientAddressRaw = this . #parseAddress( decoded [ 5 ] . toString ( ) ) ;
2054
@@ -38,18 +72,48 @@ export class Deserializer implements Contracts.Crypto.TransactionDeserializer {
3872 data . r = decoded [ 10 ] . toString ( ) . slice ( 2 ) ;
3973 data . s = decoded [ 11 ] . toString ( ) . slice ( 2 ) ;
4074
41- // Legacy second signature
75+ // Legacy second signature is only supported for EIP-1559 transactions
4276 if ( decoded . length === 13 ) {
4377 data . legacySecondSignature = decoded [ 12 ] . toString ( ) . slice ( 2 ) ;
4478 }
4579 }
80+ }
4681
47- const instance : Contracts . Crypto . Transaction = this . transactionTypeFactory . create ( data ) ;
82+ #decodeLegacyTransaction( decoded : RlpStructuredData , data : Contracts . Crypto . TransactionData ) : void {
83+ if ( decoded . length < 6 || decoded . length > 9 ) {
84+ throw new Error ( "legacy RLP data out of range" ) ;
85+ }
4886
49- const eip1559Prefix = "02" ; // marker for Type 2 (EIP1559) transaction which is the standard nowadays
50- instance . serialized = Buffer . from ( `${ eip1559Prefix } ${ encodedRlp . slice ( 2 ) } ` , "hex" ) ;
87+ // [nonce, gasPrice, gasLimit, to, value, data, v, r, s];
88+ data . nonce = BigNumber . make ( this . #parseNumber( decoded [ 0 ] . toString ( ) ) ) ;
89+ data . gasPrice = this . #parseNumber( decoded [ 1 ] . toString ( ) ) ;
90+ data . gas = this . #parseNumber( decoded [ 2 ] . toString ( ) ) ;
5191
52- return instance ;
92+ const recipientAddressRaw = this . #parseAddress( decoded [ 3 ] . toString ( ) ) ;
93+ data . to = recipientAddressRaw ? getAddress ( recipientAddressRaw ) : undefined ;
94+
95+ data . value = this . #parseBigNumber( decoded [ 4 ] . toString ( ) ) ;
96+ data . data = this . #parseData( decoded [ 5 ] . toString ( ) ) ;
97+
98+ // NOTE:
99+ // The chainId is encoded in 'v' which is part of the optional signature.
100+ // In the case of absence default to the config for the chainId.
101+
102+ // Signature
103+ if ( decoded . length >= 9 ) {
104+ const v = this . #parseNumber( decoded [ 6 ] . toString ( ) ) ;
105+ const chainId = Math . floor ( ( v - 35 ) / 2 ) ;
106+
107+ data . network = chainId ;
108+
109+ const normalizedV = v >= 35 ? ( ( v - 1 ) % 2 ) + 27 : v ;
110+ data . v = normalizedV ;
111+
112+ data . r = decoded [ 7 ] . toString ( ) . slice ( 2 ) ;
113+ data . s = decoded [ 8 ] . toString ( ) . slice ( 2 ) ;
114+ } else {
115+ data . network = this . configuration . get ( "network.chainId" ) ;
116+ }
53117 }
54118
55119 #parseNumber( value : string ) : number {
0 commit comments