eth-prototype 1.5.2__tar.gz → 1.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/PKG-INFO +1 -1
  2. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/eth_prototype.egg-info/PKG-INFO +1 -1
  3. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/eth_prototype.egg-info/SOURCES.txt +1 -0
  4. eth_prototype-1.5.3/src/ethproto/aa_bundler.py +432 -0
  5. eth_prototype-1.5.3/src/ethproto/aa_types.py +370 -0
  6. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/w3wrappers.py +3 -2
  7. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/cassettes/test_aa_bundler/test_build_user_operation_execute_user_op.yaml +3 -2
  8. eth_prototype-1.5.3/tests/test_aa_bundler.py +960 -0
  9. eth_prototype-1.5.2/src/ethproto/aa_bundler.py +0 -653
  10. eth_prototype-1.5.2/tests/test_aa_bundler.py +0 -389
  11. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/.coveragerc +0 -0
  12. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/.github/workflows/publish.yaml +0 -0
  13. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/.github/workflows/test.yaml +0 -0
  14. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/.gitignore +0 -0
  15. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/.isort.cfg +0 -0
  16. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/.pre-commit-config.yaml +0 -0
  17. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/.readthedocs.yml +0 -0
  18. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/AUTHORS.rst +0 -0
  19. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/CHANGELOG.rst +0 -0
  20. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/LICENSE.txt +0 -0
  21. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/README.md +0 -0
  22. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/Makefile +0 -0
  23. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/_static/.gitignore +0 -0
  24. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/authors.rst +0 -0
  25. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/changelog.rst +0 -0
  26. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/conf.py +0 -0
  27. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/index.rst +0 -0
  28. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/license.rst +0 -0
  29. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/readme.rst +0 -0
  30. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/docs/requirements.txt +0 -0
  31. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/pyproject.toml +0 -0
  32. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/setup.cfg +0 -0
  33. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/setup.py +0 -0
  34. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/eth_prototype.egg-info/dependency_links.txt +0 -0
  35. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/eth_prototype.egg-info/not-zip-safe +0 -0
  36. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/eth_prototype.egg-info/requires.txt +0 -0
  37. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/eth_prototype.egg-info/top_level.txt +0 -0
  38. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/__init__.py +0 -0
  39. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/build_artifacts.py +0 -0
  40. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/contracts.py +0 -0
  41. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/defender_relay.py +0 -0
  42. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/test_utils/__init__.py +0 -0
  43. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/test_utils/factories.py +0 -0
  44. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/test_utils/hardhat.py +0 -0
  45. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/test_utils/vcr_utils.py +0 -0
  46. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/wadray.py +0 -0
  47. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/src/ethproto/wrappers.py +0 -0
  48. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/__init__.py +0 -0
  49. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/cassettes/test_aa_bundler/test_build_user_operation.yaml +0 -0
  50. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/conftest.py +0 -0
  51. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/README.md +0 -0
  52. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/artifacts2/TestCurrency.sol/TestCurrency.json +0 -0
  53. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/Count.sol +0 -0
  54. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/Counter.sol +0 -0
  55. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/CounterUpgradeable.sol +0 -0
  56. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/CounterUpgradeableWithLibrary.sol +0 -0
  57. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/CounterWithLibrary.sol +0 -0
  58. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/Datatypes.sol +0 -0
  59. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/EventLauncher.sol +0 -0
  60. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/TestCurrency.sol +0 -0
  61. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/TestCurrencyUUPS.sol +0 -0
  62. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/contracts/TestNFT.sol +0 -0
  63. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/hardhat.config.js +0 -0
  64. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/package-lock.json +0 -0
  65. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/package.json +0 -0
  66. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/verifiable-binaries/@anotherOrg/aPkg/1.0.2/build/contracts/TestCurrency.sol/TestCurrency.json +0 -0
  67. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/verifiable-binaries/@org/pkg/0.2.1/build/contracts/TestCurrency.json +0 -0
  68. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/hardhat-project/verifiable-binaries/@org/pkg/0.3.0/build/contracts/TestCurrency.json +0 -0
  69. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/test_build_artifacts.py +0 -0
  70. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/test_contracts.py +0 -0
  71. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/test_defender.py +0 -0
  72. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/test_time_control.py +0 -0
  73. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/test_w3.py +0 -0
  74. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tests/test_wadray.py +0 -0
  75. {eth_prototype-1.5.2 → eth_prototype-1.5.3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eth-prototype
3
- Version: 1.5.2
3
+ Version: 1.5.3
4
4
  Summary: Prototype Ethereum Smart Contracts in Python
5
5
  Home-page: https://github.com/gnarvaja/eth-prototype
6
6
  Author: Guillermo M. Narvaja
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eth-prototype
3
- Version: 1.5.2
3
+ Version: 1.5.3
4
4
  Summary: Prototype Ethereum Smart Contracts in Python
5
5
  Home-page: https://github.com/gnarvaja/eth-prototype
6
6
  Author: Guillermo M. Narvaja
@@ -30,6 +30,7 @@ src/eth_prototype.egg-info/requires.txt
30
30
  src/eth_prototype.egg-info/top_level.txt
31
31
  src/ethproto/__init__.py
32
32
  src/ethproto/aa_bundler.py
33
+ src/ethproto/aa_types.py
33
34
  src/ethproto/build_artifacts.py
34
35
  src/ethproto/contracts.py
35
36
  src/ethproto/defender_relay.py
@@ -0,0 +1,432 @@
1
+ import random
2
+ from abc import ABC, abstractmethod
3
+ from collections import defaultdict
4
+ from dataclasses import replace
5
+ from enum import Enum
6
+ from threading import local
7
+ from typing import ClassVar, Optional
8
+
9
+ from environs import Env
10
+ from eth_account import Account
11
+ from eth_typing import ChecksumAddress, HexAddress
12
+ from hexbytes import HexBytes
13
+ from requests import HTTPError
14
+ from web3 import Web3
15
+ from web3.constants import ADDRESS_ZERO
16
+ from web3.types import StateOverride
17
+
18
+ from .aa_types import PackedUserOperation # noqa: F401
19
+ from .aa_types import pack_two # noqa: F401
20
+ from .aa_types import ( # Import everything for backwards compatibility; Exceptions; Constants; Dataclasses; Functions
21
+ DUMMY_SIGNATURE,
22
+ GET_NONCE_ABI,
23
+ AlchemyGasAndPaymasterAndData,
24
+ BundlerError,
25
+ BundlerRevertError,
26
+ GasPrice,
27
+ NonceError,
28
+ PaymasterAndData,
29
+ Tx,
30
+ UserOperation,
31
+ UserOpEstimation,
32
+ )
33
+
34
+ env = Env()
35
+
36
+ AA_BUNDLER_URL = env.str("AA_BUNDLER_URL", env.str("WEB3_PROVIDER_URI", None))
37
+ AA_BUNDLER_SENDER = env.str("AA_BUNDLER_SENDER", None)
38
+ AA_BUNDLER_ENTRYPOINT = env.str("AA_BUNDLER_ENTRYPOINT", "0x0000000071727De22E5E9d8BAf0edAc6f37da032")
39
+ AA_BUNDLER_EXECUTOR_PK = env.str("AA_BUNDLER_EXECUTOR_PK", None)
40
+ AA_BUNDLER_PROVIDER = env.str("AA_BUNDLER_PROVIDER", "generic")
41
+ AA_BUNDLER_GAS_LIMIT_FACTOR = env.float("AA_BUNDLER_GAS_LIMIT_FACTOR", 1)
42
+ AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR = env.float("AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR", 1)
43
+ AA_BUNDLER_BASE_GAS_PRICE_FACTOR = env.float("AA_BUNDLER_BASE_GAS_PRICE_FACTOR", 1)
44
+ AA_BUNDLER_VERIFICATION_GAS_FACTOR = env.float("AA_BUNDLER_VERIFICATION_GAS_FACTOR", 1)
45
+ AA_BUNDLER_MAX_FEE_PER_GAS = env.int("AA_BUNDLER_MAX_FEE_PER_GAS", 200000000000) # 200 gwei
46
+
47
+ AA_BUNDLER_STATE_OVERRIDES = env.json("AA_BUNDLER_STATE_OVERRIDES", default={})
48
+
49
+ NonceMode = Enum(
50
+ "NonceMode",
51
+ [
52
+ "RANDOM_KEY", # first time initializes a random key and increments nonce locally with calling the blockchain
53
+ "RANDOM_KEY_EVERYTIME", # initializes a random key every time and increments nonce locally
54
+ "FIXED_KEY_LOCAL_NONCE", # uses a fixed key, keeps nonce locally and fetches the nonce when receiving
55
+ # 'AA25 invalid account nonce'
56
+ "FIXED_KEY_FETCH_ALWAYS", # uses a fixed key, always fetches unless received as parameter
57
+ ],
58
+ )
59
+
60
+ AA_BUNDLER_NONCE_MODE = env.enum("AA_BUNDLER_NONCE_MODE", default="FIXED_KEY_LOCAL_NONCE", enum=NonceMode)
61
+ AA_BUNDLER_NONCE_KEY = env.int("AA_BUNDLER_NONCE_KEY", 0)
62
+ AA_BUNDLER_ALCHEMY_GAS_POLICY_ID = env.str("AA_BUNDLER_ALCHEMY_GAS_POLICY_ID", None)
63
+ AA_BUNDLER_USE_EXECUTE_USER_OP = env.bool("AA_BUNDLER_USE_EXECUTE_USER_OP", False)
64
+
65
+ NONCE_CACHE = defaultdict(lambda: 0)
66
+ RANDOM_NONCE_KEY = local()
67
+
68
+
69
+ def _to_uint(x):
70
+ if isinstance(x, str):
71
+ return int(x, 16)
72
+ elif isinstance(x, int):
73
+ return x
74
+ raise RuntimeError(f"Invalid int value {x}")
75
+
76
+
77
+ def make_nonce(nonce_key, nonce):
78
+ nonce_key = _to_uint(nonce_key)
79
+ nonce = _to_uint(nonce)
80
+ return (nonce_key << 64) | nonce
81
+
82
+
83
+ def fetch_nonce(w3, account, entrypoint, nonce_key):
84
+ ep = w3.eth.contract(abi=GET_NONCE_ABI, address=entrypoint)
85
+ return ep.functions.getNonce(account, nonce_key).call()
86
+
87
+
88
+ def get_random_nonce_key(force=False):
89
+ if force or getattr(RANDOM_NONCE_KEY, "key", None) is None:
90
+ RANDOM_NONCE_KEY.key = random.randint(1, 2**192 - 1)
91
+ return RANDOM_NONCE_KEY.key
92
+
93
+
94
+ def consume_nonce(nonce_key, nonce):
95
+ NONCE_CACHE[nonce_key] = max(NONCE_CACHE[nonce_key], nonce + 1)
96
+
97
+
98
+ def is_nonce_error(resp):
99
+ """Check if a bundler response contains an AA25 nonce error."""
100
+ return "error" in resp and "AA25" in resp["error"]["message"]
101
+
102
+
103
+ def get_sender(tx):
104
+ if tx.from_ == ADDRESS_ZERO:
105
+ if AA_BUNDLER_SENDER is None:
106
+ raise RuntimeError("Must define AA_BUNDLER_SENDER or send 'from' in the TX")
107
+ return AA_BUNDLER_SENDER
108
+ else:
109
+ return tx.from_
110
+
111
+
112
+ class Bundler:
113
+ def __init__(
114
+ self,
115
+ w3: Web3,
116
+ bundler_url: str = AA_BUNDLER_URL,
117
+ bundler_type: str = AA_BUNDLER_PROVIDER,
118
+ entrypoint: HexAddress = AA_BUNDLER_ENTRYPOINT,
119
+ nonce_mode: NonceMode = AA_BUNDLER_NONCE_MODE,
120
+ fixed_nonce_key: int = AA_BUNDLER_NONCE_KEY,
121
+ verification_gas_factor: float = AA_BUNDLER_VERIFICATION_GAS_FACTOR,
122
+ gas_limit_factor: float = AA_BUNDLER_GAS_LIMIT_FACTOR,
123
+ priority_gas_price_factor: float = AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR,
124
+ base_gas_price_factor: float = AA_BUNDLER_BASE_GAS_PRICE_FACTOR,
125
+ max_fee_per_gas: int = AA_BUNDLER_MAX_FEE_PER_GAS,
126
+ executor_pk: HexBytes = AA_BUNDLER_EXECUTOR_PK,
127
+ overrides: StateOverride = AA_BUNDLER_STATE_OVERRIDES,
128
+ use_execute_user_op: bool = AA_BUNDLER_USE_EXECUTE_USER_OP,
129
+ ):
130
+ self.w3 = w3
131
+ self.bundler_w3 = Web3(Web3.HTTPProvider(bundler_url), middleware=[]) if bundler_url else w3
132
+ self.bundler_type = bundler_type
133
+ self.entrypoint = entrypoint
134
+ self.nonce_mode = nonce_mode
135
+ self.fixed_nonce_key = fixed_nonce_key
136
+ self.verification_gas_factor = verification_gas_factor
137
+ self.gas_limit_factor = gas_limit_factor
138
+ self.priority_gas_price_factor = priority_gas_price_factor
139
+ self.base_gas_price_factor = base_gas_price_factor
140
+ self.account = Account.from_key(executor_pk) if executor_pk else None
141
+ self.max_fee_per_gas = max_fee_per_gas
142
+
143
+ # stateOverrideSet mapping to use when calling eth_estimateUserOperationGas
144
+ # https://docs.alchemy.com/reference/eth-estimateuseroperationgas
145
+ self.overrides = overrides
146
+ self.use_execute_user_op = use_execute_user_op
147
+
148
+ strategy_class = GasEstimationStrategy._strategies.get(bundler_type)
149
+ if strategy_class is None:
150
+ raise BundlerError(f"Unknown bundler_type: {bundler_type}")
151
+ self.gas_strategy = strategy_class(self)
152
+
153
+ def __str__(self):
154
+ return (
155
+ f"Bundler(type={self.bundler_type}, entrypoint={self.entrypoint}, nonce_mode={self.nonce_mode}, "
156
+ f"fixed_nonce_key={self.fixed_nonce_key}, verification_gas_factor={self.verification_gas_factor}, "
157
+ f"gas_limit_factor={self.gas_limit_factor}, priority_gas_price_factor={self.priority_gas_price_factor}, "
158
+ f"base_gas_price_factor={self.base_gas_price_factor}, max_fee_per_gas={self.max_fee_per_gas}), "
159
+ f"use_execute_user_op={self.use_execute_user_op}, signer={self.account.address if self.account else None}"
160
+ )
161
+
162
+ @property
163
+ def execute_user_op_context(self) -> ChecksumAddress:
164
+ if self.use_execute_user_op and self.account:
165
+ return self.account.address
166
+ return None
167
+
168
+ def get_nonce_and_key(self, tx: Tx, fetch=False):
169
+ nonce_key = tx.nonce_key
170
+ nonce = tx.nonce
171
+
172
+ if nonce_key is None:
173
+ if self.nonce_mode == NonceMode.RANDOM_KEY:
174
+ nonce_key = get_random_nonce_key()
175
+ elif self.nonce_mode == NonceMode.RANDOM_KEY_EVERYTIME:
176
+ nonce_key = get_random_nonce_key(force=True)
177
+ else:
178
+ nonce_key = self.fixed_nonce_key
179
+
180
+ if nonce is None:
181
+ if fetch or self.nonce_mode == NonceMode.FIXED_KEY_FETCH_ALWAYS:
182
+ nonce = fetch_nonce(self.w3, get_sender(tx), self.entrypoint, nonce_key)
183
+ else:
184
+ nonce = NONCE_CACHE[nonce_key]
185
+ return nonce_key, nonce
186
+
187
+ def build_user_operation(self, tx: Tx, enable_cap=True) -> UserOperation:
188
+ nonce_key, nonce = self.get_nonce_and_key(tx)
189
+ consume_nonce(nonce_key, nonce)
190
+
191
+ user_operation = UserOperation.from_tx(
192
+ tx, make_nonce(nonce_key, nonce), execute_user_op_context=self.execute_user_op_context
193
+ )
194
+
195
+ estimation = self.gas_strategy.estimate_gas_limits(user_operation)
196
+ user_operation = user_operation.add_estimation(estimation)
197
+
198
+ gas_price = self.gas_strategy.estimate_gas_price(user_operation)
199
+ if enable_cap:
200
+ gas_price = replace(
201
+ gas_price,
202
+ max_fee_per_gas=min(gas_price.max_fee_per_gas, self.max_fee_per_gas),
203
+ )
204
+ user_operation = user_operation.add_gas_price(gas_price)
205
+
206
+ paymaster_and_data = self.gas_strategy.estimate_paymaster(user_operation)
207
+ if paymaster_and_data is not None:
208
+ user_operation = user_operation.add_paymaster_and_data(paymaster_and_data)
209
+
210
+ return user_operation
211
+
212
+ def send_transaction(self, tx: Tx):
213
+ user_operation = self.build_user_operation(tx).sign(self.account.key, tx.chain_id, self.entrypoint)
214
+ return self.send_user_operation(user_operation)
215
+
216
+ def send_user_operation(self, user_operation: UserOperation):
217
+ resp = self.bundler_w3.provider.make_request(
218
+ "eth_sendUserOperation", [user_operation.as_dict(), self.entrypoint]
219
+ )
220
+ if "error" in resp:
221
+ if is_nonce_error(resp):
222
+ raise NonceError(resp["error"]["message"], userop=user_operation, response=resp)
223
+ raise BundlerRevertError(resp["error"]["message"], userop=user_operation, response=resp)
224
+ return {"userOpHash": resp["result"]}
225
+
226
+ def get_user_operation(self, user_op_hash):
227
+ resp = self.bundler_w3.provider.make_request("eth_getUserOperationByHash", [user_op_hash])
228
+ if "error" in resp:
229
+ raise BundlerRevertError(resp["error"]["message"], response=resp)
230
+ return resp["result"]
231
+
232
+
233
+ class GasEstimationStrategy(ABC):
234
+ _strategies: ClassVar[dict] = {}
235
+
236
+ @classmethod
237
+ def register(cls, name: str):
238
+ def decorator(strategy_cls):
239
+ cls._strategies[name] = strategy_cls
240
+ return strategy_cls
241
+
242
+ return decorator
243
+
244
+ def __init__(self, bundler: "Bundler", **kwargs):
245
+ self.bundler = bundler
246
+
247
+ def _estimate_user_operation_gas(self, user_operation: UserOperation) -> UserOpEstimation:
248
+ resp = self.bundler.bundler_w3.provider.make_request(
249
+ "eth_estimateUserOperationGas",
250
+ [user_operation.as_dict(), self.bundler.entrypoint, self.bundler.overrides],
251
+ )
252
+ if "error" in resp:
253
+ raise BundlerRevertError(resp["error"]["message"], user_operation, resp)
254
+
255
+ paymaster_verification_gas_limit = resp["result"].get("paymasterVerificationGasLimit", "0x00")
256
+ return UserOpEstimation(
257
+ pre_verification_gas=int(resp["result"].get("preVerificationGas", "0x00"), 16),
258
+ verification_gas_limit=int(
259
+ int(resp["result"].get("verificationGasLimit", "0x00"), 16)
260
+ * self.bundler.verification_gas_factor
261
+ ),
262
+ call_gas_limit=int(
263
+ int(resp["result"].get("callGasLimit", "0x00"), 16) * self.bundler.gas_limit_factor
264
+ ),
265
+ paymaster_verification_gas_limit=(
266
+ int(paymaster_verification_gas_limit, 16)
267
+ if paymaster_verification_gas_limit is not None
268
+ else 0
269
+ ),
270
+ )
271
+
272
+ def _get_base_fee(self) -> int:
273
+ blk = self.bundler.w3.eth.get_block("latest")
274
+ return int(_to_uint(blk["baseFeePerGas"]) * self.bundler.base_gas_price_factor)
275
+
276
+ @abstractmethod
277
+ def estimate_gas_limits(self, user_operation: UserOperation) -> UserOpEstimation:
278
+ ...
279
+
280
+ @abstractmethod
281
+ def estimate_gas_price(self, user_operation: UserOperation) -> GasPrice:
282
+ ...
283
+
284
+ def estimate_paymaster(self, user_operation: UserOperation) -> Optional[PaymasterAndData]:
285
+ return None
286
+
287
+
288
+ @GasEstimationStrategy.register("generic")
289
+ class GenericGasStrategy(GasEstimationStrategy):
290
+ def estimate_gas_limits(self, user_operation: UserOperation) -> UserOpEstimation:
291
+ return self._estimate_user_operation_gas(user_operation)
292
+
293
+ def estimate_gas_price(self, user_operation: UserOperation) -> GasPrice:
294
+ base_fee = self._get_base_fee()
295
+ priority_fee = self.bundler.w3.eth.max_priority_fee
296
+ max_priority_fee_per_gas = int(priority_fee * self.bundler.priority_gas_price_factor)
297
+ max_fee_per_gas = max_priority_fee_per_gas + base_fee
298
+ return GasPrice(max_priority_fee_per_gas=max_priority_fee_per_gas, max_fee_per_gas=max_fee_per_gas)
299
+
300
+
301
+ @GasEstimationStrategy.register("pimlico")
302
+ class PimlicoGasStrategy(GasEstimationStrategy):
303
+ def estimate_gas_limits(self, user_operation: UserOperation) -> UserOpEstimation:
304
+ return self._estimate_user_operation_gas(user_operation)
305
+
306
+ def estimate_gas_price(self, user_operation: UserOperation) -> GasPrice:
307
+ resp = self.bundler.bundler_w3.provider.make_request("pimlico_getUserOperationGasPrice", [])
308
+ if "error" in resp:
309
+ raise BundlerRevertError(resp["error"]["message"], response=resp)
310
+ # {
311
+ # "jsonrpc": "2.0",
312
+ # "id": 1,
313
+ # "result": {
314
+ # "slow": {
315
+ # "maxFeePerGas": "0x829b42b5",
316
+ # "maxPriorityFeePerGas": "0x829b42b5"
317
+ # },
318
+ # "standard": {
319
+ # "maxFeePerGas": "0x88d36a75",
320
+ # "maxPriorityFeePerGas": "0x88d36a75"
321
+ # },
322
+ # "fast": {
323
+ # "maxFeePerGas": "0x8f0b9234",
324
+ # "maxPriorityFeePerGas": "0x8f0b9234"
325
+ # }
326
+ # }
327
+ # }
328
+ priority_fee = int(resp["result"]["standard"]["maxPriorityFeePerGas"], 16)
329
+ total_fee = int(resp["result"]["standard"]["maxFeePerGas"], 16)
330
+ base_fee = total_fee - priority_fee
331
+ max_priority_fee_per_gas = int(priority_fee * self.bundler.priority_gas_price_factor)
332
+ max_fee_per_gas = max_priority_fee_per_gas + base_fee
333
+ return GasPrice(max_priority_fee_per_gas=max_priority_fee_per_gas, max_fee_per_gas=max_fee_per_gas)
334
+
335
+
336
+ @GasEstimationStrategy.register("alchemy")
337
+ class AlchemyGasStrategy(GasEstimationStrategy):
338
+ def __init__(self, bundler: "Bundler", **kwargs):
339
+ super().__init__(bundler)
340
+ gas_policy_id = kwargs.pop("gas_policy_id", AA_BUNDLER_ALCHEMY_GAS_POLICY_ID)
341
+ if gas_policy_id is None:
342
+ raise BundlerError("Must provide alchemy_gas_policy_id when using alchemy bundler_type")
343
+ self._gas_policy_id = gas_policy_id
344
+ self._cached_result: Optional[AlchemyGasAndPaymasterAndData] = None
345
+
346
+ def _get_estimation(self, user_operation: UserOperation) -> AlchemyGasAndPaymasterAndData:
347
+ if self._cached_result is None:
348
+ self._cached_result = self._alchemy_estimation(user_operation)
349
+ return self._cached_result
350
+
351
+ def _alchemy_estimation(self, user_operation: UserOperation) -> AlchemyGasAndPaymasterAndData:
352
+ try:
353
+ resp = self.bundler.bundler_w3.provider.make_request(
354
+ "alchemy_requestGasAndPaymasterAndData",
355
+ [
356
+ {
357
+ "policyId": self._gas_policy_id,
358
+ "entryPoint": self.bundler.entrypoint,
359
+ "dummySignature": DUMMY_SIGNATURE,
360
+ "userOperation": user_operation.as_reduced_dict(),
361
+ "overrides": {
362
+ "maxFeePerGas": {"multiplier": self.bundler.base_gas_price_factor},
363
+ "maxPriorityFeePerGas": {"multiplier": self.bundler.priority_gas_price_factor},
364
+ "callGasLimit": {"multiplier": self.bundler.gas_limit_factor},
365
+ "verificationGasLimit": {"multiplier": self.bundler.verification_gas_factor},
366
+ },
367
+ # Alchemy seems to be ignoring this, even though it's documented
368
+ "stateOverrideSet": self.bundler.overrides,
369
+ }
370
+ ],
371
+ )
372
+ except HTTPError as e:
373
+ raise BundlerRevertError(
374
+ f"HTTP error while requesting gas and paymaster data: {str(e)}",
375
+ userop=user_operation,
376
+ response=e.response.text,
377
+ ) from e
378
+
379
+ if "error" in resp:
380
+ raise BundlerRevertError(resp["error"]["message"], userop=user_operation, response=resp)
381
+
382
+ # {
383
+ # "callGasLimit": "0x3dab",
384
+ # "paymasterVerificationGasLimit": "0x9afa",
385
+ # "paymasterPostOpGasLimit": "0x0",
386
+ # "verificationGasLimit": "0xac33",
387
+ # "maxPriorityFeePerGas": "0x7aef40a00",
388
+ # "paymaster": "0x2cc0c7981D846b9F2a16276556f6e8cb52BfB633",
389
+ # "maxFeePerGas": "0xaf9fe62e48",
390
+ # "paymasterData": "0xabcd...",
391
+ # "preVerificationGas": "0xb8ec"
392
+ # }
393
+
394
+ estimation = UserOpEstimation(
395
+ pre_verification_gas=int(resp["result"]["preVerificationGas"], 16),
396
+ verification_gas_limit=int(resp["result"]["verificationGasLimit"], 16),
397
+ call_gas_limit=int(resp["result"]["callGasLimit"], 16),
398
+ paymaster_verification_gas_limit=int(resp["result"]["paymasterVerificationGasLimit"], 16),
399
+ )
400
+ gas_price = GasPrice(
401
+ max_priority_fee_per_gas=int(resp["result"]["maxPriorityFeePerGas"], 16),
402
+ max_fee_per_gas=int(resp["result"]["maxFeePerGas"], 16),
403
+ )
404
+ paymaster_and_data = PaymasterAndData(
405
+ paymaster=resp["result"]["paymaster"],
406
+ paymaster_data=HexBytes(resp["result"]["paymasterData"]),
407
+ paymaster_verification_gas_limit=int(resp["result"]["paymasterVerificationGasLimit"], 16),
408
+ paymaster_post_op_gas_limit=int(resp["result"]["paymasterPostOpGasLimit"], 16),
409
+ )
410
+ return AlchemyGasAndPaymasterAndData(
411
+ estimation=estimation,
412
+ gas_price=gas_price,
413
+ paymaster_and_data=paymaster_and_data,
414
+ )
415
+
416
+ def estimate_gas_limits(self, user_operation: UserOperation) -> UserOpEstimation:
417
+ return self._get_estimation(user_operation).estimation
418
+
419
+ def estimate_gas_price(self, user_operation: UserOperation) -> GasPrice:
420
+ return self._get_estimation(user_operation).gas_price
421
+
422
+ def estimate_paymaster(self, user_operation: UserOperation) -> PaymasterAndData:
423
+ return self._get_estimation(user_operation).paymaster_and_data
424
+
425
+
426
+ @GasEstimationStrategy.register("zeroprice")
427
+ class ZeroPriceGasStrategy(GasEstimationStrategy):
428
+ def estimate_gas_limits(self, user_operation: UserOperation) -> UserOpEstimation:
429
+ return self._estimate_user_operation_gas(user_operation)
430
+
431
+ def estimate_gas_price(self, user_operation: UserOperation) -> GasPrice:
432
+ return GasPrice(max_priority_fee_per_gas=0, max_fee_per_gas=0)