1616 List ,
1717 Sequence ,
1818 SupportsBytes ,
19+ Tuple ,
1920 Type ,
2021 TypeAlias ,
2122 TypeVar ,
@@ -980,6 +981,115 @@ def to_list(self) -> List[Address | List[Hash]]:
980981 return [self .address , self .storage_keys ]
981982
982983
984+ class AuthorizationTupleGeneric (CamelModel , Generic [NumberBoundTypeVar ]):
985+ """
986+ Authorization tuple for transactions.
987+ """
988+
989+ chain_id : NumberBoundTypeVar = Field (1 ) # type: ignore
990+ address : Address
991+ nonce : List [NumberBoundTypeVar ] | NumberBoundTypeVar | None = None
992+
993+ v : NumberBoundTypeVar = Field (0 ) # type: ignore
994+ r : NumberBoundTypeVar = Field (0 ) # type: ignore
995+ s : NumberBoundTypeVar = Field (0 ) # type: ignore
996+
997+ magic : ClassVar [int ] = 0x05
998+
999+ def model_post_init (self , __context : Any ) -> None :
1000+ """
1001+ Automatically converts the nonce to a list if it is not already.
1002+ """
1003+ super ().model_post_init (__context )
1004+ if self .nonce is not None and not isinstance (self .nonce , list ):
1005+ self .nonce = [self .nonce ]
1006+
1007+ def nonce_list (self ) -> List [Uint ]:
1008+ """
1009+ Returns the nonce as a list.
1010+ """
1011+ if self .nonce is None :
1012+ return []
1013+ return (
1014+ [Uint (n ) for n in self .nonce ] if isinstance (self .nonce , list ) else [Uint (self .nonce )]
1015+ )
1016+
1017+ def to_list (self ) -> List [Any ]:
1018+ """
1019+ Returns the authorization tuple as a list of serializable elements.
1020+ """
1021+ return [
1022+ Uint (self .chain_id ),
1023+ self .address ,
1024+ self .nonce_list (),
1025+ Uint (self .v ),
1026+ Uint (self .r ),
1027+ Uint (self .s ),
1028+ ]
1029+
1030+ def signing_hash (self ) -> Hash :
1031+ """
1032+ Returns the data to be signed.
1033+ """
1034+ return Hash (
1035+ keccak256 (
1036+ int .to_bytes (self .magic , length = 1 , byteorder = "big" )
1037+ + eth_rlp .encode (
1038+ [
1039+ Uint (self .chain_id ),
1040+ self .address ,
1041+ self .nonce_list (),
1042+ ]
1043+ )
1044+ )
1045+ )
1046+
1047+ def signature (self , private_key : Hash ) -> Tuple [int , int , int ]:
1048+ """
1049+ Returns the signature of the authorization tuple.
1050+ """
1051+ signing_hash = self .signing_hash ()
1052+ signature_bytes = PrivateKey (secret = private_key ).sign_recoverable (
1053+ signing_hash , hasher = None
1054+ )
1055+ return (
1056+ signature_bytes [64 ],
1057+ int .from_bytes (signature_bytes [0 :32 ], byteorder = "big" ),
1058+ int .from_bytes (signature_bytes [32 :64 ], byteorder = "big" ),
1059+ )
1060+
1061+
1062+ class AuthorizationTuple (AuthorizationTupleGeneric [HexNumber ]):
1063+ """
1064+ Authorization tuple for transactions.
1065+ """
1066+
1067+ signer : EOA | None = None
1068+ secret_key : Hash | None = None
1069+
1070+ def model_post_init (self , __context : Any ) -> None :
1071+ """
1072+ Automatically signs the authorization tuple if a secret key or sender are provided.
1073+ """
1074+ super ().model_post_init (__context )
1075+
1076+ if self .secret_key is not None :
1077+ self .sign (self .secret_key )
1078+ elif self .signer is not None :
1079+ assert self .signer .key is not None , "signer must have a key"
1080+ self .sign (self .signer .key )
1081+
1082+ def sign (self , private_key : Hash ) -> None :
1083+ """
1084+ Signs the authorization tuple with a private key.
1085+ """
1086+ signature = self .signature (private_key )
1087+
1088+ self .v = HexNumber (signature [0 ])
1089+ self .r = HexNumber (signature [1 ])
1090+ self .s = HexNumber (signature [2 ])
1091+
1092+
9831093class TransactionGeneric (BaseModel , Generic [NumberBoundTypeVar ]):
9841094 """
9851095 Generic transaction type used as a parent for Transaction and FixtureTransaction (blockchain).
@@ -1070,6 +1180,8 @@ class Transaction(TransactionGeneric[HexNumber], TransactionTransitionToolConver
10701180 to : Address | None = Field (Address (0xAA ))
10711181 data : Bytes = Field (Bytes (b"" ), alias = "input" )
10721182
1183+ authorization_tuples : List [AuthorizationTuple ] | None = None
1184+
10731185 secret_key : Hash | None = None
10741186 error : List [TransactionException ] | TransactionException | None = Field (None , exclude = True )
10751187
@@ -1117,7 +1229,9 @@ def model_post_init(self, __context):
11171229
11181230 if "ty" not in self .model_fields_set :
11191231 # Try to deduce transaction type from included fields
1120- if self .max_fee_per_blob_gas is not None or self .blob_kzg_commitments is not None :
1232+ if self .authorization_tuples is not None :
1233+ self .ty = 4
1234+ elif self .max_fee_per_blob_gas is not None or self .blob_kzg_commitments is not None :
11211235 self .ty = 3
11221236 elif self .max_fee_per_gas is not None or self .max_priority_fee_per_gas is not None :
11231237 self .ty = 2
@@ -1150,6 +1264,9 @@ def model_post_init(self, __context):
11501264 if self .ty == 3 and self .max_fee_per_blob_gas is None :
11511265 self .max_fee_per_blob_gas = 1
11521266
1267+ if self .ty == 4 and self .authorization_tuples is None :
1268+ self .authorization_tuples = []
1269+
11531270 if "nonce" not in self .model_fields_set and self .sender is not None :
11541271 self .nonce = HexNumber (self .sender .get_nonce ())
11551272
@@ -1233,18 +1350,40 @@ def signing_envelope(self) -> List[Any]:
12331350 Returns the list of values included in the envelope used for signing.
12341351 """
12351352 to = self .to if self .to else bytes ()
1236- if self .ty == 3 :
1353+ if self .ty == 4 :
1354+ # EIP-7702: https://eips.ethereum.org/EIPS/eip-7702
1355+ if self .max_priority_fee_per_gas is None :
1356+ raise ValueError (f"max_priority_fee_per_gas must be set for type { self .ty } tx" )
1357+ if self .max_fee_per_gas is None :
1358+ raise ValueError (f"max_fee_per_gas must be set for type { self .ty } tx" )
1359+ if self .access_list is None :
1360+ raise ValueError (f"access_list must be set for type { self .ty } tx" )
1361+ if self .authorization_tuples is None :
1362+ raise ValueError (f"authorization_tuples must be set for type { self .ty } tx" )
1363+ return [
1364+ Uint (self .chain_id ),
1365+ Uint (self .nonce ),
1366+ Uint (self .max_priority_fee_per_gas ),
1367+ Uint (self .max_fee_per_gas ),
1368+ Uint (self .gas_limit ),
1369+ to ,
1370+ Uint (self .value ),
1371+ self .data ,
1372+ [a .to_list () for a in self .access_list ],
1373+ [a .to_list () for a in self .authorization_tuples ],
1374+ ]
1375+ elif self .ty == 3 :
12371376 # EIP-4844: https://eips.ethereum.org/EIPS/eip-4844
12381377 if self .max_priority_fee_per_gas is None :
1239- raise ValueError ("max_priority_fee_per_gas must be set for type 3 tx" )
1378+ raise ValueError (f "max_priority_fee_per_gas must be set for type { self . ty } tx" )
12401379 if self .max_fee_per_gas is None :
1241- raise ValueError ("max_fee_per_gas must be set for type 3 tx" )
1380+ raise ValueError (f "max_fee_per_gas must be set for type { self . ty } tx" )
12421381 if self .max_fee_per_blob_gas is None :
1243- raise ValueError ("max_fee_per_blob_gas must be set for type 3 tx" )
1382+ raise ValueError (f "max_fee_per_blob_gas must be set for type { self . ty } tx" )
12441383 if self .blob_versioned_hashes is None :
1245- raise ValueError ("blob_versioned_hashes must be set for type 3 tx" )
1384+ raise ValueError (f "blob_versioned_hashes must be set for type { self . ty } tx" )
12461385 if self .access_list is None :
1247- raise ValueError ("access_list must be set for type 3 tx" )
1386+ raise ValueError (f "access_list must be set for type { self . ty } tx" )
12481387 return [
12491388 Uint (self .chain_id ),
12501389 Uint (self .nonce ),
@@ -1261,11 +1400,11 @@ def signing_envelope(self) -> List[Any]:
12611400 elif self .ty == 2 :
12621401 # EIP-1559: https://eips.ethereum.org/EIPS/eip-1559
12631402 if self .max_priority_fee_per_gas is None :
1264- raise ValueError ("max_priority_fee_per_gas must be set for type 2 tx" )
1403+ raise ValueError (f "max_priority_fee_per_gas must be set for type { self . ty } tx" )
12651404 if self .max_fee_per_gas is None :
1266- raise ValueError ("max_fee_per_gas must be set for type 2 tx" )
1405+ raise ValueError (f "max_fee_per_gas must be set for type { self . ty } tx" )
12671406 if self .access_list is None :
1268- raise ValueError ("access_list must be set for type 2 tx" )
1407+ raise ValueError (f "access_list must be set for type { self . ty } tx" )
12691408 return [
12701409 Uint (self .chain_id ),
12711410 Uint (self .nonce ),
@@ -1280,9 +1419,9 @@ def signing_envelope(self) -> List[Any]:
12801419 elif self .ty == 1 :
12811420 # EIP-2930: https://eips.ethereum.org/EIPS/eip-2930
12821421 if self .gas_price is None :
1283- raise ValueError ("gas_price must be set for type 1 tx" )
1422+ raise ValueError (f "gas_price must be set for type { self . ty } tx" )
12841423 if self .access_list is None :
1285- raise ValueError ("access_list must be set for type 1 tx" )
1424+ raise ValueError (f "access_list must be set for type { self . ty } tx" )
12861425
12871426 return [
12881427 Uint (self .chain_id ),
@@ -1296,7 +1435,7 @@ def signing_envelope(self) -> List[Any]:
12961435 ]
12971436 elif self .ty == 0 :
12981437 if self .gas_price is None :
1299- raise ValueError ("gas_price must be set for type 0 tx" )
1438+ raise ValueError (f "gas_price must be set for type { self . ty } tx" )
13001439
13011440 if self .protected :
13021441 # EIP-155: https://eips.ethereum.org/EIPS/eip-155
@@ -1338,11 +1477,11 @@ def payload_body(self) -> List[Any]:
13381477 elif self .ty == 3 and self .wrapped_blob_transaction :
13391478 # EIP-4844: https://eips.ethereum.org/EIPS/eip-4844
13401479 if self .blobs is None :
1341- raise ValueError ("blobs must be set for type 3 tx" )
1480+ raise ValueError (f "blobs must be set for type { self . ty } tx" )
13421481 if self .blob_kzg_commitments is None :
1343- raise ValueError ("blob_kzg_commitments must be set for type 3 tx" )
1482+ raise ValueError (f "blob_kzg_commitments must be set for type { self . ty } tx" )
13441483 if self .blob_kzg_proofs is None :
1345- raise ValueError ("blob_kzg_proofs must be set for type 3 tx" )
1484+ raise ValueError (f "blob_kzg_proofs must be set for type { self . ty } tx" )
13461485 return [
13471486 signing_envelope + [Uint (self .v ), Uint (self .r ), Uint (self .s )],
13481487 list (self .blobs ),
0 commit comments