diff --git a/src/ethproto/aa_bundler.py b/src/ethproto/aa_bundler.py index 0145bb1..065d9c2 100644 --- a/src/ethproto/aa_bundler.py +++ b/src/ethproto/aa_bundler.py @@ -10,7 +10,7 @@ from eth_abi.packed import encode_packed from eth_account import Account from eth_account.messages import encode_defunct -from eth_typing import HexAddress +from eth_typing import ChecksumAddress, HexAddress from eth_utils import add_0x_prefix, function_signature_to_4byte_selector from hexbytes import HexBytes from requests import HTTPError @@ -50,6 +50,7 @@ AA_BUNDLER_NONCE_KEY = env.int("AA_BUNDLER_NONCE_KEY", 0) AA_BUNDLER_MAX_GETNONCE_RETRIES = env.int("AA_BUNDLER_MAX_GETNONCE_RETRIES", 3) AA_BUNDLER_ALCHEMY_GAS_POLICY_ID = env.str("AA_BUNDLER_ALCHEMY_GAS_POLICY_ID", None) +AA_BUNDLER_USE_EXECUTE_USER_OP = env.bool("AA_BUNDLER_USE_EXECUTE_USER_OP", False) GET_NONCE_ABI = [ { @@ -148,6 +149,12 @@ class UserOperation: EXECUTE_ARG_TYPES = ["address", "uint256", "bytes"] EXECUTE_SELECTOR = function_signature_to_4byte_selector(f"execute({','.join(EXECUTE_ARG_TYPES)})") + EXECUTE_USEROP_ARG_TYPES = ["address", "address", "uint256", "bytes"] + EXECUTE_USEROP_SELECTOR = function_signature_to_4byte_selector( + # PackedUserOperation struct + "executeUserOp((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes),bytes32)" + ) + sender: HexBytes nonce: int call_data: HexBytes @@ -169,13 +176,24 @@ class UserOperation: paymaster_post_op_gas_limit: int = 0 @classmethod - def from_tx(cls, tx: Tx, nonce): + def from_tx(cls, tx: Tx, nonce, execute_user_op_context=None): + if execute_user_op_context is not None: + call_data = add_0x_prefix( + ( + cls.EXECUTE_USEROP_SELECTOR + + encode( + cls.EXECUTE_USEROP_ARG_TYPES, [execute_user_op_context, tx.target, tx.value, tx.data] + ) + ).hex() + ) + else: + call_data = add_0x_prefix( + (cls.EXECUTE_SELECTOR + encode(cls.EXECUTE_ARG_TYPES, tx.as_execute_args())).hex() + ) return cls( sender=get_sender(tx), nonce=nonce, - call_data=add_0x_prefix( - (cls.EXECUTE_SELECTOR + encode(cls.EXECUTE_ARG_TYPES, tx.as_execute_args())).hex() - ), + call_data=call_data, ) def as_reduced_dict(self): @@ -386,6 +404,7 @@ def __init__( executor_pk: HexBytes = AA_BUNDLER_EXECUTOR_PK, overrides: StateOverride = AA_BUNDLER_STATE_OVERRIDES, alchemy_gas_policy_id: str = AA_BUNDLER_ALCHEMY_GAS_POLICY_ID, + use_execute_user_op: bool = AA_BUNDLER_USE_EXECUTE_USER_OP, ): self.w3 = w3 self.bundler = Web3(Web3.HTTPProvider(bundler_url), middleware=[]) if bundler_url else w3 @@ -397,7 +416,7 @@ def __init__( self.gas_limit_factor = gas_limit_factor self.priority_gas_price_factor = priority_gas_price_factor self.base_gas_price_factor = base_gas_price_factor - self.executor_pk = executor_pk + self.account = Account.from_key(executor_pk) if executor_pk else None self.max_fee_per_gas = max_fee_per_gas # stateOverrideSet mapping to use when calling eth_estimateUserOperationGas @@ -407,15 +426,23 @@ def __init__( if alchemy_gas_policy_id is None and bundler_type == "alchemy": raise BundlerError("Must provide alchemy_gas_policy_id when using alchemy bundler_type") self.alchemy_gas_policy_id = alchemy_gas_policy_id + self.use_execute_user_op = use_execute_user_op def __str__(self): return ( f"Bundler(type={self.bundler_type}, entrypoint={self.entrypoint}, nonce_mode={self.nonce_mode}, " f"fixed_nonce_key={self.fixed_nonce_key}, verification_gas_factor={self.verification_gas_factor}, " f"gas_limit_factor={self.gas_limit_factor}, priority_gas_price_factor={self.priority_gas_price_factor}, " - f"base_gas_price_factor={self.base_gas_price_factor}, max_fee_per_gas={self.max_fee_per_gas})" + f"base_gas_price_factor={self.base_gas_price_factor}, max_fee_per_gas={self.max_fee_per_gas}), " + f"use_execute_user_op={self.use_execute_user_op}, signer={self.account.address if self.account else None}" ) + @property + def execute_user_op_context(self) -> ChecksumAddress: + if self.use_execute_user_op and self.account: + return self.account.address + return None + def get_nonce_and_key(self, tx: Tx, fetch=False): nonce_key = tx.nonce_key nonce = tx.nonce @@ -567,7 +594,9 @@ def build_user_operation(self, tx: Tx, retry_nonce=None) -> UserOperation: # Consume the nonce, even if the userop may fail later consume_nonce(nonce_key, nonce) - user_operation = UserOperation.from_tx(tx, make_nonce(nonce_key, nonce)) + user_operation = UserOperation.from_tx( + tx, make_nonce(nonce_key, nonce), execute_user_op_context=self.execute_user_op_context + ) if self.bundler_type == "alchemy": estimation_and_paymaster = self.alchemy_estimation(user_operation) @@ -603,7 +632,7 @@ def build_user_operation(self, tx: Tx, retry_nonce=None) -> UserOperation: def send_transaction(self, tx: Tx, retry_nonce=None): user_operation = self.build_user_operation(tx, retry_nonce).sign( - self.executor_pk, tx.chain_id, self.entrypoint + self.account.key, tx.chain_id, self.entrypoint ) resp = self.bundler.provider.make_request( diff --git a/src/ethproto/test_utils/vcr_utils.py b/src/ethproto/test_utils/vcr_utils.py index a14aef4..c3f6dcd 100644 --- a/src/ethproto/test_utils/vcr_utils.py +++ b/src/ethproto/test_utils/vcr_utils.py @@ -18,6 +18,12 @@ def json_rpc_matcher(r1, r2): r1_body = json.loads(r1.body) r2_body = json.loads(r2.body) - assert r1_body["jsonrpc"] == r2_body["jsonrpc"] == "2.0" - assert r1_body["method"] == r2_body["method"] - assert r1_body["params"] == r2_body["params"] + assert ( + r1_body["jsonrpc"] == r2_body["jsonrpc"] == "2.0" + ), f"JSON-RPC mismatch: {r1_body['jsonrpc']} != {r2_body['jsonrpc']}" + assert ( + r1_body["method"] == r2_body["method"] + ), f"Method mismatch: {r1_body['method']} != {r2_body['method']}" + assert ( + r1_body["params"] == r2_body["params"] + ), f"Param mismatch: {r1_body['params']} != {r2_body['params']}" diff --git a/tests/cassettes/test_aa_bundler/test_build_user_operation.yaml b/tests/cassettes/test_aa_bundler/test_build_user_operation.yaml index 92f60db..2f63ebe 100644 --- a/tests/cassettes/test_aa_bundler/test_build_user_operation.yaml +++ b/tests/cassettes/test_aa_bundler/test_build_user_operation.yaml @@ -1,48 +1,48 @@ interactions: -- request: - body: '{"jsonrpc": "2.0", "method": "alchemy_requestGasAndPaymasterAndData", "params": - [{"policyId": "d80ed67a-d8bc-4cd1-90ad-b50e0a58c93e", "entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", - "dummySignature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", - "userOperation": {"sender": "0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9", "nonce": - "0xae85c374ae0606ed34d0ee009a9ca43a757a8a46a324510000000000000000", "callData": - "0xb61d27f60000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000007ace242f32208d836a2245df957c08547059bf45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000", - "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"}, - "overrides": {"maxFeePerGas": {"multiplier": 1}, "maxPriorityFeePerGas": {"multiplier": - 1}, "callGasLimit": {"multiplier": 1}, "verificationGasLimit": {"multiplier": - 1}}, "stateOverrideSet": {}}], "id": 0}' - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '1328' - Content-Type: - - application/json - User-Agent: - - web3.py/7.14.0/web3.providers.rpc.rpc.HTTPProvider - method: POST - uri: https://polygon-mainnet.g.alchemy.com - response: - body: - string: '{"jsonrpc":"2.0","id":0,"result":{"callGasLimit":"0xd912","paymasterVerificationGasLimit":"0x9b37","paymasterPostOpGasLimit":"0x0","verificationGasLimit":"0xac76","maxPriorityFeePerGas":"0x7aef40a00","paymaster":"0x2cc0c7981D846b9F2a16276556f6e8cb52BfB633","maxFeePerGas":"0xbaad142eb6","paymasterData":"0x00000000000000006982f2961b02e25cb873537d0a2383b9665992667628a8d22f8223e7951b2f6d2aeae55e03af6a6ec9774cf4303980e7e3b2ecab9b65a8bcce8aabe4fd89b81c0facdb4f1b","preVerificationGas":"0xbd54"}}' - headers: - content-length: - - '493' - content-type: - - application/json - date: - - Wed, 04 Feb 2026 07:07:42 GMT - server: - - istio-envoy - x-alchemy-trace-id: - - c905c727e8b9bd9f1495b4112264ea51 - - 2a016524-94c2-4e82-bdb5-7a4c7035b07e - x-envoy-upstream-service-time: - - '122' - status: - code: 200 - message: OK + - request: + body: '{"jsonrpc": "2.0", "method": "alchemy_requestGasAndPaymasterAndData", "params": + [{"policyId": "d80ed67a-d8bc-4cd1-90ad-b50e0a58c93e", "entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "dummySignature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + "userOperation": {"sender": "0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9", "nonce": + "0xae85c374ae0606ed34d0ee009a9ca43a757a8a46a324510000000000000000", "callData": + "0xb61d27f60000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000007ace242f32208d836a2245df957c08547059bf45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"}, + "overrides": {"maxFeePerGas": {"multiplier": 1}, "maxPriorityFeePerGas": {"multiplier": + 1}, "callGasLimit": {"multiplier": 1}, "verificationGasLimit": {"multiplier": + 1}}, "stateOverrideSet": {}}], "id": 0}' + headers: + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - "1328" + Content-Type: + - application/json + User-Agent: + - web3.py/7.14.0/web3.providers.rpc.rpc.HTTPProvider + method: POST + uri: https://bundler.example.com + response: + body: + string: '{"jsonrpc":"2.0","id":0,"result":{"callGasLimit":"0xd912","paymasterVerificationGasLimit":"0x9b37","paymasterPostOpGasLimit":"0x0","verificationGasLimit":"0xac76","maxPriorityFeePerGas":"0x7aef40a00","paymaster":"0x2cc0c7981D846b9F2a16276556f6e8cb52BfB633","maxFeePerGas":"0xbaad142eb6","paymasterData":"0x00000000000000006982f2961b02e25cb873537d0a2383b9665992667628a8d22f8223e7951b2f6d2aeae55e03af6a6ec9774cf4303980e7e3b2ecab9b65a8bcce8aabe4fd89b81c0facdb4f1b","preVerificationGas":"0xbd54"}}' + headers: + content-length: + - "493" + content-type: + - application/json + date: + - Wed, 04 Feb 2026 07:07:42 GMT + server: + - istio-envoy + x-alchemy-trace-id: + - c905c727e8b9bd9f1495b4112264ea51 + - 2a016524-94c2-4e82-bdb5-7a4c7035b07e + x-envoy-upstream-service-time: + - "122" + status: + code: 200 + message: OK version: 1 diff --git a/tests/cassettes/test_aa_bundler/test_build_user_operation_execute_user_op.yaml b/tests/cassettes/test_aa_bundler/test_build_user_operation_execute_user_op.yaml new file mode 100644 index 0000000..b143ee6 --- /dev/null +++ b/tests/cassettes/test_aa_bundler/test_build_user_operation_execute_user_op.yaml @@ -0,0 +1,145 @@ +interactions: + - request: + body: '{"jsonrpc": "2.0", "method": "eth_estimateUserOperationGas", "params": + [{"sender": "0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9", "nonce": "0xae85c374ae0606ed34d0ee009a9ca43a757a8a46a324520000000000000000", + "callData": "0x8dd7712f00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000007ace242f32208d836a2245df957c08547059bf45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000", + "callGasLimit": "0x0", "verificationGasLimit": "0x0", "preVerificationGas": + "0x0", "maxPriorityFeePerGas": "0x0", "maxFeePerGas": "0x0", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + "paymaster": null, "paymasterData": "0x", "paymasterVerificationGasLimit": "0x0", + "paymasterPostOpGasLimit": "0x0"}, "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + {}], "id": 0}' + headers: + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - "1142" + Content-Type: + - application/json + User-Agent: + - web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider + method: POST + uri: https://bundler.example.com + response: + body: + string: '{"jsonrpc":"2.0","id":0,"result":{"preVerificationGas":"0xc0fe","verificationGasLimit":"0x16471","callGasLimit":"0x10282","paymasterVerificationGasLimit":"0x0","paymasterPostOpGasLimit":"0x0"}}' + headers: + Access-Control-Allow-Origin: + - "*" + CF-RAY: + - 9d9cbd935996199f-EZE + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 09 Mar 2026 20:13:29 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - Origin + X-Ratelimit-Limit: + - "500" + X-Ratelimit-Remaining: + - "499" + X-Ratelimit-Reset: + - "60" + X-Request-Id: + - 7a1b3d8ced87e1a3dcc23a965e1ddcde + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + status: + code: 200 + message: OK + - request: + body: '{"jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": ["latest", + false], "id": 0}' + headers: + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - "90" + Content-Type: + - application/json + User-Agent: + - web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider + method: POST + uri: http://example.org + response: + body: + string: '{"jsonrpc":"2.0","id":0,"result":{"baseFeePerGas":"0x13f86bb293","difficulty":"0x1","extraData":"0xd78301100883626f7288676f312e32362e30856c696e75780000000000000000f9019480f90190c0c0c101c102c180c104c105c106c107c108c109c10ac0c0c0c10ec10fc110c102c10bc0c114c111c115c117c115c0c0c11bc11ac11dc11ec113c120c121c103c112c119c0c126c127c128c122c12ac12bc12cc12dc12ec12fc130c22423c125c132c134c131c136c133c138c3293918c13ac13bc13cc2373dc13ec13fc140c141c142c143c144c145c146c147c148c149c14ac14bc14cc14dc14ec14fc135c150c152c153c154c155c156c157c158c159c15ac15bc15cc15dc15ec15fc160c161c162c163c164c165c166c167c168c169c16ac16bc16cc16dc16ec16fc170c171c172c173c174c175c176c177c23251c178c17ac17bc17cc17dc17ec17fc28180c28181c28182c28183c28184c28185c28186c28187c28188c28189c179c2818ac2818cc2818dc2818ec2818fc0c54a3e4d4f5bc28190c0c0c0c116c28192c0c28199c2819ac2819bc2819cc0c0c0c28198c0c281a1c281a3c4819781a4c0c281a5c0cb4c5a818f498181818c3d4ec481a98193c2818bc0c781a30d81a581aac281adc281aec0c281aac281b1c281b2e86ba6f942b45fbb80cbad2a1f6415b852acdc1b66be009842c008040d35cb7a64243595c7592b9585c5968146d06641d706048693018dceff7518c415a484a501","gasLimit":"0x6679122","gasUsed":"0x2a9e13d","hash":"0xf83cca6bb72ef19942916f9f8904253f2ccd1e1095c41f5e540b17441d1fce14","logsBloom":"0xeff9d76bf3fbdb92cffa64a3ffef72ddff2f7bb7dfbaa6fafdbf7fb5dc7d7e6eefedf84dffb3fcdc6fdfdbdf7c8da7c95f7bf73f4a5fefbcfbf57bfbd36eeff9ef96ef8fffbeffeafdee4c6efffffeb1fddf9fcf7bf73dafffebaff7fee2fffa7ff53dfcfffef9bbf95bb319fe76fb7fd7bf3ffffff7f6f9d4ff7f769fbfdfbfff5fdfbbffef9ffff7b6cf2ff573b3ff5f3fecaffff0fedd63f2f962f5fef5efafefd77ffffdbfff6cfbdbbff6c5fff3fbbfdfdf7a9a7df1b6eaf6ff7f7bfff7beecffd397fe63cb6fb97ffeefe33e5d6e4eff7bff4dffffff5bceabfb2fec7a7fbdf5ebff7fd5f8ffefcd3bfef3fdbffc73efeadf07bf6bffe77d75ff3ffdbd","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x5017ce4","parentHash":"0x5247d1c9bd425e54921e060f482c4d3c51249a18705cc566a09359ab9ee0662d","receiptsRoot":"0x512d8d584fd2e6d3e68b4e2e9842cec14b6f4de9aaeb54ba9e20ae5694eaf31c","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x35133","stateRoot":"0x58bb5d2af652309718b50bdc7923b1fa7d07a9c6f9d1171c215d1734bfd6db80","timestamp":"0x69af29e9","transactions":["0x78f348510a3f130c38858ce2ac16c138a9798675d1e2ca1fa9f264c9a9106fdc","0x4a1c96c7543b6b4a4bca5ce3042489f524f0e91b4dceadcd1e06d28d688ce358","0x04ff780bc77f2a306b0e922216166d94c721b24bc421e7832d0ba7b963c59c4b","0xfd27c4e08a66024e5bf188d2843da11020bede5ecdfb97bd5088ed1252a1b52a","0xe16145eb9886fb467c8dc796a84181348e7901c79d60d37f1f407920c9f83137","0xfa52bae697ed6bf8f2a0a06ddb7bf07ac8c225264f1426af8331323d2e26b90f","0x2ff5148cb54dc23e4bfc5eab9cb723fd8ee99ddd1bc2f598190a2bff3a0652c6","0x69c5344eda2c2ebf3acb20247b48d77b7e1a4dfb296a40d9e2d79b41f872cec4","0x02f62e541e1cc011fbff2688e95aa211907af4f26b16dda2ef75eb36fd865005","0x470d6889cbe7c9d2bb46bf62362e68d9c89f8153903c041bfba167184260c70b","0xa04156fad73c835a6077e8c30a20b033b8a46eec29c0430b4d932769227b2179","0xe7e932178275614fb4944c4c344b78684686dc7eadba68c937cc4e86c1b1c5b0","0x131927f1d6087f755c601536ac04d60f48ffcb407fd3f446b62ccffaa884c881","0x0ec77359fd8531fe2480c2f5f1a513809c61d3ebc049c119af0f0104d28b73ae","0xd522956638cb789cb77f4d4a5301022956207435c8f10a0817bf1760676d9cbd","0x26d88df750e0f6ac7016dc2ba2804a70d76030a9c1bcd07e266df1dba54df133","0xb5c7a8aa4844bcd9e30e7960c83cf6b25938d7d418685ccb21a34a4be6d68f5f","0xc8d96200ab2b4bfb8833be03090154851f952978bee5ace5b4c737247918ab0c","0xca6ec0bf6abea8e2b07fe839141fbdd0f41e115e94a806bb93eec192d7fea011","0x267a15a38acea012ad41ac8d2a599d84c9d958932c969420c29ffda173ef435d","0x9d208d89fffc4474158ae08b6493b3b8a0258acc1292aa720dfe3b9105c9f96f","0x259c8755d72e2962db1ffd05468a6df8a30666c3fdb3568c6f9af35dbfa9d907","0x97f132a85b9a69bf9138695c4cb050654ed23082f1b806a9dd53ac3e389baa30","0xee1c3a1cb8aadc80b7e58b5ba01e6e69410dc65b1b535de6d8afb72209b5dee0","0x0ccebcf383f09457bf0beb96a86ace25bc7fa41d193730d5878aca1d464a0283","0xd39f77e418380107688d024b1f3752744122032069a54d9247f1e89e5a33daf8","0x8ef08e565c8ce6368e90db40eaaee5605cc6e31625a469ab2fb09f3e9e2cfef7","0x891d4de48e0fef3bbef3165e5e5bcf6320a4578e84e63970be5a81868b822ca8","0x8144be28808073cd9e3c62769ce2f1a4bc25cd27d3c75c581fbab86bf4279c84","0x2bd913e9a50ba721ec4f1c0b943fb9156d1fd23fd356ed921de84bfcd93609d5","0x8d857b39dc459f330145e1c2f5d9929013210137d36b4ab1bd7f61af8d95b88e","0xab34328061c0926edb51779b6d8505fc7dda02b047d6c2da1a86a6996343bbb2","0x41ac901fe60c65ab115b44dea4262dab5766b016991cdee22b601f01c3ae1bc8","0xb8f80ba5e260dc4a5fc5c0d77876b85b495c8316e068297aa3853328a5bdd267","0xf7d63a354d962502fe4df994f0e165d32fb711b282bbcdb20e5db74360da12bc","0xd900e275a31a97f5f0d66caefab743992fc5cd281622edee50ca446a367eac55","0xd0d2d8242a04c78f739be2c899c4d17dc964c4daf0a0b76ee8bb3d964f17d831","0x391d6d3d83231a3dfbe1d377cf581744c9ad6dabdb32ba8ff786d359be1bdfa1","0x79ffa7f5adc8e80d9ccd7fabccbe5432400b7678355ef92a44083ae73680c5b3","0xc29fde5f7e7003c299c3b86d3fdf308cfcfcbc79c9e3f13df25704431b0f5455","0xdeb90f17a972f688bd343426d454e05be03325672bcaaeb71fbdfec1a4b913a4","0x023cf36f8acbcf7e4fe893a85f09fd8ae484510195557fca9afd3f7696258607","0x5e7ef923c594abbf97f02d1ac33635311c2bc2d4189093d9f68eb8826ab4df81","0x53fe5b02ed01c959322adde8375f52dc3009409b30d03bee59be502318a3d7e1","0xbd1567cf06d5b62723a5cd18f1ec4d59c2db1f9156080deae13cc97c9e3b70eb","0x9af1223d3f49ce55961ce7ed20874acd92bebbab55b3ebd03c5957875b4ac3ed","0x3a08a920ebd1a7e53a6087cb119e89dffee2782844d262afe442a00f9280bdb5","0xec79aa9268e6a0d0de429ec4e071dde2ada47a8bec6039e7d9122a99216b6cd9","0x3082f7e8102bd4c0eaae2455d203d4eca1bab69dd6a221720df9493392a66876","0xc624a5b76ec49269c1ac3406ff965cf612f10d4f3a4837b41b0ec041edf33e2a","0x4bd20915c9f72a85490a6ee373f13a067b31d96ba97a1e850b16fe566ec8d525","0x4e18a85c0819cdbcacd83ab093bc7b8634665f660d6f9bcb7ab3b41b738ec595","0xddebc670f2d1f427aa628a140d266c6efedc1f68156b06cb6d22c0ab2358df21","0xb9ced02960f025fc834d6a46bcbf09f826f7076e3fe9d09c86bac8fb0fb7c4c4","0x9d730e115c3da9a95142348389a694665072c2ca20e2e7f7307b1151034a8a9b","0x4ae2a59e87b26a6bf440ec30b2b0aa82a58f16994dd01187336baa1bba66ae37","0xdb59ee82c977d3f558d2dccb8e4454be4a0a2a78065ff39cd5068694794dfdce","0x41500c036ac90fb8ec72b925bf67b6476004404490a5d043b241b98d715e9791","0x3a3a9bc3896023a43e31a3347efe7ee4e72d3cfa2def51c1133332abbceb6fa1","0x0e145757883daf0e4ea0adce9ff0405a7b1ba5ffe722158ecaab915a51b8401f","0xcdab875b0b59e7ccf5e232eeb27ed347041cfac019876525dc92fd5e21d26d8a","0x43adf7ab781deca5266fb0cf414fe5d03a061b397b791bd184199176fef8c2d3","0x22f55a0e2009d61c683290ee2049fd5e5597efd18728234fed930af352aab733","0x83641ba81683c0b7c2d0ae370c33bf5d057ad12cef7971ef3178c1924f8139ff","0x362a6fc24e6f5b32bec342f88b7c75b9890a947e7a52a311862a2f696aef3c1b","0x84e14da0b004593b02058e1512efd29a59c7607e4a16377ff44fec39bad548ae","0x0ef846ea59891938d84f474c466314c7b8b2eb1483d8b60ed2022d858bb014db","0xf91e04043aa180545b1d81f4c3f073964338c5bbaccf2bba93ce1d691e5cdab0","0x325a1b4e4b1597f36a160110c11a4ceac4d1f406f05697533e52ad6e91d82452","0x52b56a4ac5a1075c9dd529ce166be8470dc8239e18c44e7c5a311317ba4d86d0","0xd557ccfb66fed4a6498c27ba9e9b58baff7e28e947572b768c6fb5a5272c87b6","0xb4425b5e20c870854068b29936085bfd28fecd806a9e5cc773245b7f83b21c12","0xc45a0ef8e9ab93580b4c1fd2ba4e5ede7c908acd2b8a0a84e422b065bd31d597","0x954a319fd59430262d0f162d59351f5a9c8eb3795850406a55d29775dcfac2ab","0x696ced7c5e713e276a921585b279f3b9b79f2a7947249a8baad2c2ff95fa4e94","0x7226f989ba542b6452bafe9027bb79d6dd231ebf33c7c1ec89bb507f96ab4710","0xffef08c497469e800ab0ddb7cb436099db360a087a9ce54d3a00174fd6954fbb","0x88b8451f159b14307b9b2dd9638515751f58575072442f777dd790ad8cd8bc9b","0xe176e5466f6b74867c883b06cb44e89f68427f0750f6d3da875b2fe3b16fd94f","0xf7abdab8f0f9c0ca5d5f37ac4ccf56e719a8a709b7aadd4fdba799aefce953dd","0x808fc8fb9b2a975dda2a6fb1c267353aa3ca8ff68906a786252cb0c26cef2f51","0x5abf8deabb0eaf018fb98942bb1fa5abe3bde0b4ea052ec0d65c176533df326c","0xe9aa5214d0af6a6423e0db881b8bf476054aaa3e9f4c440420bf429508c7f1e2","0x55a93d6283d64c9c1c68f6d89a22127c2c1c4b1d273cca31fc8fdb3e7366ffc1","0x1464f8bec0a28323c4260f7e281ac50a9b14eec8d8dfb6eadd3999601bcfe4e6","0xbf058bf1b118b9e0a666a11d6e996777a4df6dc4e946f9be938afaf45ecda4bf","0x30df95fff326ae0b9bf35ad6326569a6457049d89cdf018b1f7ff647f2298e35","0xd6f35f0ffd7cc0fa0987eed4ce1a2e5894388d3745d11d5b37a0af7ba5861a7b","0x2db309f7a8d211a0c83626b73e3f3cf92b3a94ac853dc22d4a8a2fab439ec1d3","0x2b744f371f48b9e50042e5d777acd086b4a6ddfcf7fd9498a92c66899b959efa","0x59f1cbb116e2c6f6cfb44df6c493210edf7158c8219b94413ef5c4e5ea5b1cd5","0x175b9b4170a8e21597a2be9f0929e82a3e919d163b347f0a71436ab0bfef8159","0x9b4335f2f0cf42fcaa9f6e07a19ae24b87dab0244b3f33a5e0277df58fc9a5db","0x6df065193352e54128987aaffb9d0bf7684e3c299a8b63678468f3ab935dadfb","0xb9d27db50ed7b340f6778fc3e5adb4e6bbcc7b88dbf7cb804341f39dddfc0c2a","0xcf833dcdc1371bd3396b49f5a779cb53d76a40abe450a84314ac7faf5b1672d1","0x1a6dceb23130f08a12480bf100982d3ebba86edad9e171e721e3caa10d0dbea6","0x3ce1bdbbcd2b5442cf1135705f962831f5cfe7eb30d1ca38bbaeff1f093da733","0x091addd21ed2d8f353c214ab829bbbc36beb8cb6f9105ca4d6044259a63cbe54","0xa8fb046cfbc0eff4cd8751b642636e019a96a5804408672ddc11872ff3767634","0xe9a01497d6f5c7cb8eb671432a00a3d1241cbb15ae67475370678866e174639e","0xe72b4cabf1b5f440b97bb5b08af7f5c4e076bc5bb892b041aa717ab4f7ad940f","0xee2c84461922a9c87b91fe977241336c4e83444df7945a6a32d2efcc65301ef6","0x99d2af9f2cf33989b8bb61058888fdc92daf5e5915f5e7f20e0822aebd85e7a0","0x74a0729ff29d309d2be1c1454314a01ef59b02c16c5ebb1e18ec2b281db7168c","0x4abbae051f9dfacc9f40bc914ef9c2405028fd9b53118f374cc6fe4e0d7e69f3","0x4708e152441d546a0bca6bf008c0557be545ffbecee07f72cb579d0939d7e719","0x79e3885e38c80bc3976bc68b3f256dfe30b7a4137d77e76e7ca57a7940008da0","0xc5e889e112303f0610a1c51bd946338c86eec9186bf227e7f676e16b0bab2606","0x71fc377d24c84e91672e654e4d1f992edb8595bd228aee278aed837b76360b1c","0x3b1b78dcd86424435b191d0a239371bd568d23a300acd63d722bd16149404f05","0x40af5242fbb80539032bc434fd04b6c72123b7fc78a834e16754174b5bece393","0x281d215c0c68c8ab036f3889f13c8bd8a39cc1a40f61e71029cfc7b35c1c4169","0x8839502dfbeae7edad0d838cebd6f85ed96d68c4555f52f0dfa73b79369cc01d","0x6c90cdd18882acc5dbc8ebb112e23acfe5e59b8030910a48cf63c64b29232f65","0x9a6d28c9131e66d441492b73f4c1bde1918bad5cbf13e306aab030b035c569ba","0x5e8fabc0521728f9507b7b785e0108cb59b2524eff0ee8cf5216b494f3a8210f","0x251bd3466bb8964137948790611d70c831f41188f2249758ab07b2d6743555ef","0x28e4bf2dad6738ae27f6bd46093c12b1a72cea5405a2abcd425ce2796764468d","0x1a9e898af876a0ba32b916f6cbcbd3eb107f2cea61a0b14dc65f514a180e69fa","0xc4597100ca103148816d10abae96123cee1da5a4441c83a372be3ac29b9ea6cf","0x725b613a43bf536c3606772e7681b33133affe47ed8496a59b7891a1a5a3d074","0x70b8855ea5e935cbd711c0a9583efff6cbceef7e754fd2bd0e1c4d0c441b3403","0xa1dfb87b2e985781faef9eda689c6d7333bc828f82164591bd77d4d7e357d123","0x880e4ee6eb34f48a413095ccddbb9930a0d1fa9cd52d5d5e1097aaeb4a2accb3","0x7a51b337529732e5718a6f1bdf7bbc5c65f0c1a025c8a14925ce772ef42eb65c","0x731148c4ec5358b1f27dda1b6c30e3829f47568ccf87bebcd1f036db55db40c8","0xd8bca217dfb6a3e644993a750db36c3c48c288acbd56acd9103607ecbeacdfb0","0x2c652c5df3a74ad225ac7f604adff7af0f338f9f581cc4e7ebba9d1e55864cda","0x4236e682c35417d4cbae601eaae0fb11eadd3bd47e4109a7a552b1eafed1f602","0xd74b73cdf7bddcc054283315b9476b5047e3fa5f3c5b33968d83e66ca74a7636","0x6f7a97cfe4d4bd0d8a02713b37fc08687d79ae582256be30cffd8a3ed249ef00","0x8a937cda049b4abbb82bdbf1fb9064366dad095859e483d6dd0ad7acabf82324","0x1bb8c6e821f25ce13868d16549cf7275baf131193734ea64947d00d81c84d4d7","0xcea3201f457277a31a2628be93d16a56c65734467c17a91a30a9973fa10a9e3c","0xa3cc92527df20f955d9caf42055d9f579d13a52ede001e72360bec5431450dea","0x77481b1e01ce979a2cf0e8f1364e06c4ca90a43f4afdfd62275074c10c45a312","0x75c80d5252b74e9f774524599d85daedef1fbb857e8f2d49a0c0a64af07d57e3","0x738ec5a9d7e8041ab4fa4cd03932488aa211ee1a78ebc0ac96e683f92c2e944f","0xd362dc2f44d89319cc66cf41f66cbce6701a166186c479deabc7028585b6874c","0xed6c01ce27c2e1b7bf5edec72adf61ed6930ef8fb94a99e7a8907685d9ab69de","0xa9e9406c0bbe777e5f1a0c432e7074526413de23817a273b3130386613a09fe9","0x299fd4bb647947fb82d5251f4a7875ca93baed653000f6a0d1e663bb9a19e871","0x4cbdc8dde68f24715f0bb12434e2c7da9b563e87fa794b5b06ff604b9283b661","0x9fb7ad615eb3897c2a174f09357bb7d9e46da74c0327798a6ef22de3db26672c","0x4bc8c1fbf8c7e8eb4947f81c892fa4ab05b862c21452b3ce09343e223cce5600","0x0ca2e24dc3f633db5be2eb457c2d08e7046dfd26385569454a30bf9bb68086de","0x5d0f38927839055393bdc29db06f33977b7b9f2fce88011497e43f665ea7fb00","0x0a5a8d6248285a31d07f055a298ce1be10c811330687b3ea5361e7e9af9ca137","0xc890fd8cd64b4613ab5017bcfd2fcc315c7bee1f66b16008fa874b9a10a6ae49","0xdd00d71f230d027a971837be866635b21b2ae81a9d24a13bfb7c49eaef90ad4f","0xf0c346123a67a404e0a2ff4dcacce72f9158da320466b16dac3c500f1c14eb43","0xb73abafc5e44e9ecf4162e3f3a7fbe230eadc0ca77dca3c1e7cb15d31ef07dfd","0xf870a34c2a16a8da8b60029ad865c49aff76efcebff22551ca625235fda5533b","0x434102f5594ee556f97fd8d6d137af6855d29c19bbf6ef6f883ff227c2c2768c","0x43760dcf7f75527f47a8e6459c98189974a3afa0e88255b336e923f61df910b8","0xe330967fd19b86b296c3bafec36a9982a73a5da26179d3299861817ff1488741","0x72229da46cbf86a9b1073afd5fbcd1b5ddf212e48ae9dc36c19ffe86078f38ba","0x5a3484926f8ec055d2907cb7485770fdfc000fc1491d4d877220e56fe0f26e93","0x60ab7b3862691c435d687d93b308bf26e84814577ad81dfebbf991c74c9ec084","0xbb6f3dbbcd4e74be26bc8aedfc31e14ef7b16a5df00f7b295c70fee0ebbb64b4","0xc60b62cf3a24d63fa6623b83d6ce73fefe88db98a774bbd3e40e519c18f2826e","0x5870da28a9c62863f2c8c69f089e5cb42f014417ec049317c067b095cd14aa55","0xaac4d88ad74608dee51d99b24bdc20a8d972de46618f263fb468cd0b1fda2b98","0xc012bf7df189c6d6d400a54601bb194afae44d2ed240f6b1fb1238192de43c34","0xc4da572920d3556ad88fb7ec85e45f59d05105c8ff5cee4b5f558e8ae5e26ae7","0x39bddcc04b58eaeaf8129486176641761c3b4ec3798b170247becd0a5ed58740","0x66c86f9cccfdc3be0dc633992af708453d0696ae0c260cfdecb4fd8b87f4e732","0x76f4f0bcd5d8455a5d63bcce638d69c1e64a2060f6f16b557073bdbceb2261fd","0x0a9377625b91d7f99cde941183444f4610586f09c1ab42554fb5d8efcfd33717","0x47761379617939eaa5de427c28338f196c897d9f1755494fa9f3e9a6a4bb1df6","0x41b2f63da50e1673421895676f8e60647d72b55f34ee53661e82d985f72438a7","0x6899ec69bb18c340887f37ea2827acd359971a3b7a8eacb681c3dbb9a771316a","0xe7d59cc977a303af5cd7151a08a1ab55dfd7fa73644394b249a6924292e5ccd0","0x72a614ac7626e8ad1eadf561596a043029eaf95717fdf2d672f4900c8482622a","0x08dcb548d11d22841cf533f6b6634b52c84b317e5fe857eecc660000e7ff4f12","0x2c5b4f0f4773905bec6703d98076b3c32c2307ab2528d4d1e1805b13ca20de22","0xd1fe00337258d21c18cf3c09d5e3147753f379874b37f04f9b4502699959bcfa","0xf6535ded4720be9b193e3b5f578f027472460cc1a5ba608e6c5c6f384ca7fb8b","0xe2a4e373c3392f9d7cd3499869665a90ab672a59c9091bee2ce38544302748df"],"transactionsRoot":"0x60860a2c7338c6bf98a597ae6a5d887719f62e14192ea8d191d17dfd864783e1","uncles":[]}}' + headers: + content-encoding: + - gzip + content-type: + - application/json + date: + - Mon, 9 Mar 2026 20:13:30 GMT + server: + - istio-envoy + transfer-encoding: + - chunked + x-alchemy-trace-id: + - ef65ed03-ca15-440d-b1c5-bcc9c1950905 + x-envoy-upstream-service-time: + - "4" + status: + code: 200 + message: OK + - request: + body: '{"jsonrpc": "2.0", "method": "eth_maxPriorityFeePerGas", "params": [], + "id": 1}' + headers: + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - "79" + Content-Type: + - application/json + User-Agent: + - web3.py/7.14.1/web3.providers.rpc.rpc.HTTPProvider + method: POST + uri: http://example.org + response: + body: + string: '{"jsonrpc":"2.0","id":1,"result":"0x645849ef5"} + + ' + headers: + content-encoding: + - gzip + content-type: + - application/json + date: + - Mon, 9 Mar 2026 20:13:32 GMT + server: + - istio-envoy + transfer-encoding: + - chunked + x-alchemy-trace-id: + - 4cf67e9f-baf5-41b8-9353-a886051644dc + x-envoy-upstream-service-time: + - "12" + status: + code: 200 + message: OK +version: 1 diff --git a/tests/conftest.py b/tests/conftest.py index 87e6ebd..37e991b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,7 +46,7 @@ def pytest_recording_configure(config, vcr: VCR): @pytest.fixture(autouse=True) def vcr_config(): return { - "match_on": ["json_rpc"], + "match_on": ["method", "host", "json_rpc"], "allow_playback_repeats": True, "allowed_hosts": ["localhost", "127.0.0.1", "::1"], } diff --git a/tests/test_aa_bundler.py b/tests/test_aa_bundler.py index 0e10107..c296f56 100644 --- a/tests/test_aa_bundler.py +++ b/tests/test_aa_bundler.py @@ -296,6 +296,7 @@ def test_build_user_operation(w3): userop = aa_bundler.Bundler( w3, + bundler_url="https://bundler.example.com/rpc", nonce_mode=aa_bundler.NonceMode.FIXED_KEY_LOCAL_NONCE, fixed_nonce_key=0xAE85C374AE0606ED34D0EE009A9CA43A757A8A46A32451, executor_pk=TEST_PRIVATE_KEY, @@ -330,3 +331,59 @@ def test_build_user_operation(w3): ), "verificationGasLimit": "0xac76", } + + +@pytest.mark.vcr +def test_build_user_operation_execute_user_op(w3): + tx = aa_bundler.Tx( + value=0, + chain_id=137, + from_="0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9", + target="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + data=HexBytes( + # 0x095ea7b3 -> approve(address,uint256) + "0x095ea7b30000000000000000000000007ace242f32208d836a2245df957c08547059bf45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" # noqa + ), + ) + + userop = aa_bundler.Bundler( + w3, + bundler_url="https://bundler.example.com/rpc", + nonce_mode=aa_bundler.NonceMode.FIXED_KEY_LOCAL_NONCE, + fixed_nonce_key=0xAE85C374AE0606ED34D0EE009A9CA43A757A8A46A32452, + executor_pk=TEST_PRIVATE_KEY, + entrypoint=ENTRYPOINT, + bundler_type="generic", + use_execute_user_op=True, + ).build_user_operation(tx) + + assert userop.as_dict() == { + "callData": ( + "0x8dd7712f" # executeUserOp selector + # followed by payload (address dest, uint256 value, bytes calldata) + "00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8" # private key signer + "0000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174" # dest + "0000000000000000000000000000000000000000000000000000000000000000" # value + "0000000000000000000000000000000000000000000000000000000000000080" # calldata offset + "0000000000000000000000000000000000000000000000000000000000000044" # calldata length + "095ea7b3" # Inner call starts here: approve(address,uint256) + "0000000000000000000000007ace242f32208d836a2245df957c08547059bf45" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "00000000000000000000000000000000000000000000000000000000" # padding for 32 byte alignment + ), + "callGasLimit": "0x10282", + "maxFeePerGas": "0x1a3df05188", + "maxPriorityFeePerGas": "0x645849ef5", + "nonce": "0xae85c374ae0606ed34d0ee009a9ca43a757a8a46a324520000000000000000", + "paymaster": None, + "paymasterData": "0x", + "paymasterPostOpGasLimit": "0x0", + "paymasterVerificationGasLimit": "0x0", + "preVerificationGas": "0xc0fe", + "sender": "0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9", + "signature": ( + "0xfffffffffffffffffffffffffffffff00000000000000000000000000000000" + "07aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" + ), + "verificationGasLimit": "0x16471", + }