t402 1.7.1__py3-none-any.whl → 1.9.1__py3-none-any.whl
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.
- t402/__init__.py +2 -1
- t402/bridge/client.py +13 -5
- t402/bridge/constants.py +3 -1
- t402/bridge/router.py +1 -1
- t402/bridge/scan.py +3 -1
- t402/chains.py +268 -1
- t402/cli.py +31 -9
- t402/common.py +2 -0
- t402/cosmos_paywall_template.py +2 -0
- t402/encoding.py +9 -3
- t402/erc4337/accounts.py +56 -51
- t402/erc4337/bundlers.py +105 -99
- t402/erc4337/paymasters.py +100 -109
- t402/erc4337/types.py +39 -26
- t402/evm_paywall_template.py +1 -1
- t402/fastapi/middleware.py +1 -3
- t402/mcp/server.py +79 -46
- t402/near_paywall_template.py +2 -0
- t402/networks.py +34 -1
- t402/paywall.py +1 -3
- t402/schemes/__init__.py +164 -1
- t402/schemes/aptos/__init__.py +70 -0
- t402/schemes/aptos/constants.py +349 -0
- t402/schemes/aptos/exact_direct/__init__.py +44 -0
- t402/schemes/aptos/exact_direct/client.py +202 -0
- t402/schemes/aptos/exact_direct/facilitator.py +426 -0
- t402/schemes/aptos/exact_direct/server.py +272 -0
- t402/schemes/aptos/types.py +237 -0
- t402/schemes/evm/__init__.py +67 -1
- t402/schemes/evm/exact/__init__.py +11 -0
- t402/schemes/evm/exact/client.py +3 -1
- t402/schemes/evm/exact/facilitator.py +894 -0
- t402/schemes/evm/exact/server.py +1 -1
- t402/schemes/evm/exact_legacy/__init__.py +38 -0
- t402/schemes/evm/exact_legacy/client.py +291 -0
- t402/schemes/evm/exact_legacy/facilitator.py +777 -0
- t402/schemes/evm/exact_legacy/server.py +231 -0
- t402/schemes/evm/upto/__init__.py +70 -0
- t402/schemes/evm/upto/client.py +244 -0
- t402/schemes/evm/upto/facilitator.py +625 -0
- t402/schemes/evm/upto/server.py +243 -0
- t402/schemes/evm/upto/types.py +307 -0
- t402/schemes/interfaces.py +6 -2
- t402/schemes/near/__init__.py +112 -0
- t402/schemes/near/constants.py +189 -0
- t402/schemes/near/exact_direct/__init__.py +21 -0
- t402/schemes/near/exact_direct/client.py +204 -0
- t402/schemes/near/exact_direct/facilitator.py +455 -0
- t402/schemes/near/exact_direct/server.py +303 -0
- t402/schemes/near/types.py +419 -0
- t402/schemes/polkadot/__init__.py +72 -0
- t402/schemes/polkadot/constants.py +155 -0
- t402/schemes/polkadot/exact_direct/__init__.py +43 -0
- t402/schemes/polkadot/exact_direct/client.py +235 -0
- t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
- t402/schemes/polkadot/exact_direct/server.py +292 -0
- t402/schemes/polkadot/types.py +385 -0
- t402/schemes/registry.py +6 -2
- t402/schemes/stacks/__init__.py +68 -0
- t402/schemes/stacks/constants.py +122 -0
- t402/schemes/stacks/exact_direct/__init__.py +43 -0
- t402/schemes/stacks/exact_direct/client.py +222 -0
- t402/schemes/stacks/exact_direct/facilitator.py +424 -0
- t402/schemes/stacks/exact_direct/server.py +292 -0
- t402/schemes/stacks/types.py +380 -0
- t402/schemes/svm/__init__.py +29 -0
- t402/schemes/svm/exact/__init__.py +35 -0
- t402/schemes/svm/exact/client.py +23 -0
- t402/schemes/svm/exact/facilitator.py +24 -0
- t402/schemes/svm/exact/server.py +20 -0
- t402/schemes/tezos/__init__.py +84 -0
- t402/schemes/tezos/constants.py +372 -0
- t402/schemes/tezos/exact_direct/__init__.py +22 -0
- t402/schemes/tezos/exact_direct/client.py +226 -0
- t402/schemes/tezos/exact_direct/facilitator.py +491 -0
- t402/schemes/tezos/exact_direct/server.py +277 -0
- t402/schemes/tezos/types.py +220 -0
- t402/schemes/ton/__init__.py +9 -2
- t402/schemes/ton/exact/__init__.py +7 -0
- t402/schemes/ton/exact/facilitator.py +730 -0
- t402/schemes/ton/exact/server.py +1 -1
- t402/schemes/tron/__init__.py +11 -2
- t402/schemes/tron/exact/__init__.py +9 -0
- t402/schemes/tron/exact/facilitator.py +673 -0
- t402/schemes/tron/exact/server.py +1 -1
- t402/schemes/upto/__init__.py +80 -0
- t402/schemes/upto/types.py +376 -0
- t402/stacks_paywall_template.py +2 -0
- t402/svm.py +45 -11
- t402/svm_paywall_template.py +1 -1
- t402/ton.py +5 -1
- t402/ton_paywall_template.py +1 -192
- t402/tron.py +2 -0
- t402/tron_paywall_template.py +2 -0
- t402/types.py +4 -2
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
- t402-1.9.1.dist-info/RECORD +125 -0
- t402-1.7.1.dist-info/RECORD +0 -67
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
t402/erc4337/paymasters.py
CHANGED
|
@@ -34,20 +34,14 @@ class PaymasterClient(ABC):
|
|
|
34
34
|
|
|
35
35
|
@abstractmethod
|
|
36
36
|
def get_paymaster_data(
|
|
37
|
-
self,
|
|
38
|
-
user_op: UserOperation,
|
|
39
|
-
chain_id: int,
|
|
40
|
-
entry_point: str
|
|
37
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
41
38
|
) -> PaymasterData:
|
|
42
39
|
"""Get paymaster data for a UserOperation."""
|
|
43
40
|
pass
|
|
44
41
|
|
|
45
42
|
@abstractmethod
|
|
46
43
|
def will_sponsor(
|
|
47
|
-
self,
|
|
48
|
-
user_op: UserOperation,
|
|
49
|
-
chain_id: int,
|
|
50
|
-
entry_point: str
|
|
44
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
51
45
|
) -> bool:
|
|
52
46
|
"""Check if the paymaster will sponsor this operation."""
|
|
53
47
|
pass
|
|
@@ -60,13 +54,27 @@ def _pack_user_op_for_paymaster(user_op: UserOperation) -> Dict[str, Any]:
|
|
|
60
54
|
"nonce": hex(user_op.nonce),
|
|
61
55
|
"initCode": "0x" + user_op.init_code.hex() if user_op.init_code else "0x",
|
|
62
56
|
"callData": "0x" + user_op.call_data.hex() if user_op.call_data else "0x",
|
|
63
|
-
"verificationGasLimit": hex(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
57
|
+
"verificationGasLimit": hex(
|
|
58
|
+
user_op.verification_gas_limit or DEFAULT_GAS_LIMITS.verification_gas_limit
|
|
59
|
+
),
|
|
60
|
+
"callGasLimit": hex(
|
|
61
|
+
user_op.call_gas_limit or DEFAULT_GAS_LIMITS.call_gas_limit
|
|
62
|
+
),
|
|
63
|
+
"preVerificationGas": hex(
|
|
64
|
+
user_op.pre_verification_gas or DEFAULT_GAS_LIMITS.pre_verification_gas
|
|
65
|
+
),
|
|
66
|
+
"maxFeePerGas": hex(user_op.max_fee_per_gas)
|
|
67
|
+
if user_op.max_fee_per_gas
|
|
68
|
+
else "0x0",
|
|
69
|
+
"maxPriorityFeePerGas": hex(user_op.max_priority_fee_per_gas)
|
|
70
|
+
if user_op.max_priority_fee_per_gas
|
|
71
|
+
else "0x0",
|
|
72
|
+
"paymasterAndData": "0x" + user_op.paymaster_and_data.hex()
|
|
73
|
+
if user_op.paymaster_and_data
|
|
74
|
+
else "0x",
|
|
75
|
+
"signature": "0x" + user_op.signature.hex()
|
|
76
|
+
if user_op.signature
|
|
77
|
+
else "0x" + get_dummy_signature().hex(),
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
|
|
@@ -79,10 +87,12 @@ class PimlicoPaymaster(PaymasterClient):
|
|
|
79
87
|
chain_id: int,
|
|
80
88
|
paymaster_url: Optional[str] = None,
|
|
81
89
|
entry_point: str = ENTRYPOINT_V07_ADDRESS,
|
|
82
|
-
sponsorship_policy_id: Optional[str] = None
|
|
90
|
+
sponsorship_policy_id: Optional[str] = None,
|
|
83
91
|
):
|
|
84
92
|
network = PIMLICO_NETWORKS.get(chain_id, str(chain_id))
|
|
85
|
-
self.paymaster_url =
|
|
93
|
+
self.paymaster_url = (
|
|
94
|
+
paymaster_url or f"https://api.pimlico.io/v2/{network}/rpc?apikey={api_key}"
|
|
95
|
+
)
|
|
86
96
|
self.api_key = api_key
|
|
87
97
|
self.chain_id = chain_id
|
|
88
98
|
self.entry_point = entry_point
|
|
@@ -91,19 +101,13 @@ class PimlicoPaymaster(PaymasterClient):
|
|
|
91
101
|
self._client = httpx.Client(timeout=30.0)
|
|
92
102
|
|
|
93
103
|
def get_paymaster_data(
|
|
94
|
-
self,
|
|
95
|
-
user_op: UserOperation,
|
|
96
|
-
chain_id: int,
|
|
97
|
-
entry_point: str
|
|
104
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
98
105
|
) -> PaymasterData:
|
|
99
106
|
"""Get paymaster data for sponsorship."""
|
|
100
107
|
return self.sponsor_user_operation(user_op)
|
|
101
108
|
|
|
102
109
|
def will_sponsor(
|
|
103
|
-
self,
|
|
104
|
-
user_op: UserOperation,
|
|
105
|
-
chain_id: int,
|
|
106
|
-
entry_point: str
|
|
110
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
107
111
|
) -> bool:
|
|
108
112
|
"""Check if the paymaster will sponsor this operation."""
|
|
109
113
|
try:
|
|
@@ -125,27 +129,26 @@ class PimlicoPaymaster(PaymasterClient):
|
|
|
125
129
|
return self._parse_paymaster_response(result)
|
|
126
130
|
|
|
127
131
|
def get_token_quotes(
|
|
128
|
-
self,
|
|
129
|
-
user_op: UserOperation,
|
|
130
|
-
tokens: List[str]
|
|
132
|
+
self, user_op: UserOperation, tokens: List[str]
|
|
131
133
|
) -> List[TokenQuote]:
|
|
132
134
|
"""Get quotes for paying gas with tokens."""
|
|
133
135
|
packed = _pack_user_op_for_paymaster(user_op)
|
|
134
136
|
|
|
135
137
|
result = self._rpc_call(
|
|
136
|
-
"pimlico_getTokenQuotes",
|
|
137
|
-
[packed, self.entry_point, tokens]
|
|
138
|
+
"pimlico_getTokenQuotes", [packed, self.entry_point, tokens]
|
|
138
139
|
)
|
|
139
140
|
|
|
140
141
|
quotes = []
|
|
141
142
|
for r in result:
|
|
142
|
-
quotes.append(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
quotes.append(
|
|
144
|
+
TokenQuote(
|
|
145
|
+
token=r.get("token", ""),
|
|
146
|
+
symbol=r.get("symbol", ""),
|
|
147
|
+
decimals=r.get("decimals", 18),
|
|
148
|
+
fee=int(r.get("fee", "0x0"), 16),
|
|
149
|
+
exchange_rate=int(r.get("exchangeRate", "0x0"), 16),
|
|
150
|
+
)
|
|
151
|
+
)
|
|
149
152
|
|
|
150
153
|
return quotes
|
|
151
154
|
|
|
@@ -155,9 +158,15 @@ class PimlicoPaymaster(PaymasterClient):
|
|
|
155
158
|
if result.get("paymaster"):
|
|
156
159
|
return PaymasterData(
|
|
157
160
|
paymaster=result["paymaster"],
|
|
158
|
-
paymaster_verification_gas_limit=int(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
paymaster_verification_gas_limit=int(
|
|
162
|
+
result.get("paymasterVerificationGasLimit", "0x0"), 16
|
|
163
|
+
),
|
|
164
|
+
paymaster_post_op_gas_limit=int(
|
|
165
|
+
result.get("paymasterPostOpGasLimit", "0x0"), 16
|
|
166
|
+
),
|
|
167
|
+
paymaster_data=bytes.fromhex(result.get("paymasterData", "0x")[2:])
|
|
168
|
+
if result.get("paymasterData")
|
|
169
|
+
else b"",
|
|
161
170
|
)
|
|
162
171
|
|
|
163
172
|
# Fall back to v0.6 format (packed)
|
|
@@ -186,7 +195,7 @@ class PimlicoPaymaster(PaymasterClient):
|
|
|
186
195
|
response = self._client.post(
|
|
187
196
|
self.paymaster_url,
|
|
188
197
|
json=request,
|
|
189
|
-
headers={"Content-Type": "application/json"}
|
|
198
|
+
headers={"Content-Type": "application/json"},
|
|
190
199
|
)
|
|
191
200
|
|
|
192
201
|
if response.status_code != 200:
|
|
@@ -199,7 +208,7 @@ class PimlicoPaymaster(PaymasterClient):
|
|
|
199
208
|
raise PaymasterError(
|
|
200
209
|
error.get("message", "Unknown error"),
|
|
201
210
|
code=error.get("code"),
|
|
202
|
-
data=error.get("data")
|
|
211
|
+
data=error.get("data"),
|
|
203
212
|
)
|
|
204
213
|
|
|
205
214
|
return data.get("result")
|
|
@@ -213,7 +222,7 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
213
222
|
api_key: str,
|
|
214
223
|
chain_id: int,
|
|
215
224
|
paymaster_url: str,
|
|
216
|
-
mode: str = "sponsored" # "sponsored" or "erc20"
|
|
225
|
+
mode: str = "sponsored", # "sponsored" or "erc20"
|
|
217
226
|
):
|
|
218
227
|
self.api_key = api_key
|
|
219
228
|
self.chain_id = chain_id
|
|
@@ -223,10 +232,7 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
223
232
|
self._client = httpx.Client(timeout=30.0)
|
|
224
233
|
|
|
225
234
|
def get_paymaster_data(
|
|
226
|
-
self,
|
|
227
|
-
user_op: UserOperation,
|
|
228
|
-
chain_id: int,
|
|
229
|
-
entry_point: str
|
|
235
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
230
236
|
) -> PaymasterData:
|
|
231
237
|
"""Get paymaster data for sponsorship."""
|
|
232
238
|
packed = _pack_user_op_for_paymaster(user_op)
|
|
@@ -244,10 +250,7 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
244
250
|
return self._parse_paymaster_response(result)
|
|
245
251
|
|
|
246
252
|
def will_sponsor(
|
|
247
|
-
self,
|
|
248
|
-
user_op: UserOperation,
|
|
249
|
-
chain_id: int,
|
|
250
|
-
entry_point: str
|
|
253
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
251
254
|
) -> bool:
|
|
252
255
|
"""Check if the paymaster will sponsor this operation."""
|
|
253
256
|
try:
|
|
@@ -257,9 +260,7 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
257
260
|
return False
|
|
258
261
|
|
|
259
262
|
def get_fee_quotes(
|
|
260
|
-
self,
|
|
261
|
-
user_op: UserOperation,
|
|
262
|
-
tokens: List[str]
|
|
263
|
+
self, user_op: UserOperation, tokens: List[str]
|
|
263
264
|
) -> List[TokenQuote]:
|
|
264
265
|
"""Get fee quotes for ERC20 token payment."""
|
|
265
266
|
packed = _pack_user_op_for_paymaster(user_op)
|
|
@@ -268,21 +269,19 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
268
269
|
|
|
269
270
|
quotes = []
|
|
270
271
|
for r in result:
|
|
271
|
-
quotes.append(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
quotes.append(
|
|
273
|
+
TokenQuote(
|
|
274
|
+
token=r.get("token", ""),
|
|
275
|
+
symbol=r.get("symbol", ""),
|
|
276
|
+
decimals=r.get("decimals", 18),
|
|
277
|
+
fee=int(r.get("fee", "0x0"), 16),
|
|
278
|
+
exchange_rate=int(r.get("exchangeRate", "0x0"), 16),
|
|
279
|
+
)
|
|
280
|
+
)
|
|
278
281
|
|
|
279
282
|
return quotes
|
|
280
283
|
|
|
281
|
-
def check_sponsorship(
|
|
282
|
-
self,
|
|
283
|
-
user_op: UserOperation,
|
|
284
|
-
entry_point: str
|
|
285
|
-
) -> bool:
|
|
284
|
+
def check_sponsorship(self, user_op: UserOperation, entry_point: str) -> bool:
|
|
286
285
|
"""Check if the operation can be sponsored."""
|
|
287
286
|
packed = _pack_user_op_for_paymaster(user_op)
|
|
288
287
|
|
|
@@ -297,9 +296,15 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
297
296
|
if result.get("paymaster"):
|
|
298
297
|
return PaymasterData(
|
|
299
298
|
paymaster=result["paymaster"],
|
|
300
|
-
paymaster_verification_gas_limit=int(
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
paymaster_verification_gas_limit=int(
|
|
300
|
+
result.get("paymasterVerificationGasLimit", "0x0"), 16
|
|
301
|
+
),
|
|
302
|
+
paymaster_post_op_gas_limit=int(
|
|
303
|
+
result.get("paymasterPostOpGasLimit", "0x0"), 16
|
|
304
|
+
),
|
|
305
|
+
paymaster_data=bytes.fromhex(result.get("paymasterData", "0x")[2:])
|
|
306
|
+
if result.get("paymasterData")
|
|
307
|
+
else b"",
|
|
303
308
|
)
|
|
304
309
|
|
|
305
310
|
paymaster_and_data = result.get("paymasterAndData", "0x")
|
|
@@ -330,7 +335,7 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
330
335
|
headers={
|
|
331
336
|
"Content-Type": "application/json",
|
|
332
337
|
"x-api-key": self.api_key,
|
|
333
|
-
}
|
|
338
|
+
},
|
|
334
339
|
)
|
|
335
340
|
|
|
336
341
|
if response.status_code != 200:
|
|
@@ -343,7 +348,7 @@ class BiconomyPaymaster(PaymasterClient):
|
|
|
343
348
|
raise PaymasterError(
|
|
344
349
|
error.get("message", "Unknown error"),
|
|
345
350
|
code=error.get("code"),
|
|
346
|
-
data=error.get("data")
|
|
351
|
+
data=error.get("data"),
|
|
347
352
|
)
|
|
348
353
|
|
|
349
354
|
return data.get("result")
|
|
@@ -357,7 +362,7 @@ class StackupPaymaster(PaymasterClient):
|
|
|
357
362
|
api_key: str,
|
|
358
363
|
chain_id: int,
|
|
359
364
|
paymaster_url: str,
|
|
360
|
-
paymaster_type: Optional[str] = None
|
|
365
|
+
paymaster_type: Optional[str] = None,
|
|
361
366
|
):
|
|
362
367
|
self.api_key = api_key
|
|
363
368
|
self.chain_id = chain_id
|
|
@@ -367,10 +372,7 @@ class StackupPaymaster(PaymasterClient):
|
|
|
367
372
|
self._client = httpx.Client(timeout=30.0)
|
|
368
373
|
|
|
369
374
|
def get_paymaster_data(
|
|
370
|
-
self,
|
|
371
|
-
user_op: UserOperation,
|
|
372
|
-
chain_id: int,
|
|
373
|
-
entry_point: str
|
|
375
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
374
376
|
) -> PaymasterData:
|
|
375
377
|
"""Get paymaster data for sponsorship."""
|
|
376
378
|
packed = _pack_user_op_for_paymaster(user_op)
|
|
@@ -379,20 +381,14 @@ class StackupPaymaster(PaymasterClient):
|
|
|
379
381
|
if self.paymaster_type:
|
|
380
382
|
context["type"] = self.paymaster_type
|
|
381
383
|
|
|
382
|
-
result = self._rpc_call(
|
|
383
|
-
packed,
|
|
384
|
-
|
|
385
|
-
hex(chain_id),
|
|
386
|
-
context
|
|
387
|
-
])
|
|
384
|
+
result = self._rpc_call(
|
|
385
|
+
"pm_getPaymasterStubData", [packed, entry_point, hex(chain_id), context]
|
|
386
|
+
)
|
|
388
387
|
|
|
389
388
|
return self._parse_paymaster_response(result)
|
|
390
389
|
|
|
391
390
|
def will_sponsor(
|
|
392
|
-
self,
|
|
393
|
-
user_op: UserOperation,
|
|
394
|
-
chain_id: int,
|
|
395
|
-
entry_point: str
|
|
391
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
396
392
|
) -> bool:
|
|
397
393
|
"""Check if the paymaster will sponsor this operation."""
|
|
398
394
|
try:
|
|
@@ -406,9 +402,15 @@ class StackupPaymaster(PaymasterClient):
|
|
|
406
402
|
if result.get("paymaster"):
|
|
407
403
|
return PaymasterData(
|
|
408
404
|
paymaster=result["paymaster"],
|
|
409
|
-
paymaster_verification_gas_limit=int(
|
|
410
|
-
|
|
411
|
-
|
|
405
|
+
paymaster_verification_gas_limit=int(
|
|
406
|
+
result.get("paymasterVerificationGasLimit", "0x0"), 16
|
|
407
|
+
),
|
|
408
|
+
paymaster_post_op_gas_limit=int(
|
|
409
|
+
result.get("paymasterPostOpGasLimit", "0x0"), 16
|
|
410
|
+
),
|
|
411
|
+
paymaster_data=bytes.fromhex(result.get("paymasterData", "0x")[2:])
|
|
412
|
+
if result.get("paymasterData")
|
|
413
|
+
else b"",
|
|
412
414
|
)
|
|
413
415
|
|
|
414
416
|
paymaster_and_data = result.get("paymasterAndData", "0x")
|
|
@@ -439,7 +441,7 @@ class StackupPaymaster(PaymasterClient):
|
|
|
439
441
|
headers={
|
|
440
442
|
"Content-Type": "application/json",
|
|
441
443
|
"Authorization": f"Bearer {self.api_key}",
|
|
442
|
-
}
|
|
444
|
+
},
|
|
443
445
|
)
|
|
444
446
|
|
|
445
447
|
if response.status_code != 200:
|
|
@@ -452,17 +454,14 @@ class StackupPaymaster(PaymasterClient):
|
|
|
452
454
|
raise PaymasterError(
|
|
453
455
|
error.get("message", "Unknown error"),
|
|
454
456
|
code=error.get("code"),
|
|
455
|
-
data=error.get("data")
|
|
457
|
+
data=error.get("data"),
|
|
456
458
|
)
|
|
457
459
|
|
|
458
460
|
return data.get("result")
|
|
459
461
|
|
|
460
462
|
|
|
461
463
|
def create_paymaster(
|
|
462
|
-
provider: str,
|
|
463
|
-
api_key: str,
|
|
464
|
-
chain_id: int,
|
|
465
|
-
**kwargs
|
|
464
|
+
provider: str, api_key: str, chain_id: int, **kwargs
|
|
466
465
|
) -> PaymasterClient:
|
|
467
466
|
"""Factory function to create a paymaster client."""
|
|
468
467
|
if provider == "pimlico":
|
|
@@ -471,21 +470,21 @@ def create_paymaster(
|
|
|
471
470
|
chain_id=chain_id,
|
|
472
471
|
paymaster_url=kwargs.get("paymaster_url"),
|
|
473
472
|
entry_point=kwargs.get("entry_point", ENTRYPOINT_V07_ADDRESS),
|
|
474
|
-
sponsorship_policy_id=kwargs.get("sponsorship_policy_id")
|
|
473
|
+
sponsorship_policy_id=kwargs.get("sponsorship_policy_id"),
|
|
475
474
|
)
|
|
476
475
|
elif provider == "biconomy":
|
|
477
476
|
return BiconomyPaymaster(
|
|
478
477
|
api_key=api_key,
|
|
479
478
|
chain_id=chain_id,
|
|
480
479
|
paymaster_url=kwargs.get("paymaster_url", ""),
|
|
481
|
-
mode=kwargs.get("mode", "sponsored")
|
|
480
|
+
mode=kwargs.get("mode", "sponsored"),
|
|
482
481
|
)
|
|
483
482
|
elif provider == "stackup":
|
|
484
483
|
return StackupPaymaster(
|
|
485
484
|
api_key=api_key,
|
|
486
485
|
chain_id=chain_id,
|
|
487
486
|
paymaster_url=kwargs.get("paymaster_url", ""),
|
|
488
|
-
paymaster_type=kwargs.get("paymaster_type")
|
|
487
|
+
paymaster_type=kwargs.get("paymaster_type"),
|
|
489
488
|
)
|
|
490
489
|
else:
|
|
491
490
|
raise PaymasterError(f"Unknown paymaster provider: {provider}")
|
|
@@ -498,10 +497,7 @@ class UnifiedPaymaster:
|
|
|
498
497
|
self.paymasters = paymasters
|
|
499
498
|
|
|
500
499
|
def get_paymaster_data(
|
|
501
|
-
self,
|
|
502
|
-
user_op: UserOperation,
|
|
503
|
-
chain_id: int,
|
|
504
|
-
entry_point: str
|
|
500
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
505
501
|
) -> PaymasterData:
|
|
506
502
|
"""Try each paymaster until one succeeds."""
|
|
507
503
|
last_error: Optional[Exception] = None
|
|
@@ -513,15 +509,10 @@ class UnifiedPaymaster:
|
|
|
513
509
|
last_error = e
|
|
514
510
|
continue
|
|
515
511
|
|
|
516
|
-
raise PaymasterError(
|
|
517
|
-
f"All paymasters failed. Last error: {last_error}"
|
|
518
|
-
)
|
|
512
|
+
raise PaymasterError(f"All paymasters failed. Last error: {last_error}")
|
|
519
513
|
|
|
520
514
|
def will_sponsor(
|
|
521
|
-
self,
|
|
522
|
-
user_op: UserOperation,
|
|
523
|
-
chain_id: int,
|
|
524
|
-
entry_point: str
|
|
515
|
+
self, user_op: UserOperation, chain_id: int, entry_point: str
|
|
525
516
|
) -> bool:
|
|
526
517
|
"""Check if any paymaster will sponsor."""
|
|
527
518
|
for paymaster in self.paymasters:
|
t402/erc4337/types.py
CHANGED
|
@@ -26,6 +26,7 @@ SAFE_4337_ADDRESSES = {
|
|
|
26
26
|
|
|
27
27
|
class PaymasterType(str, Enum):
|
|
28
28
|
"""Type of paymaster for gas sponsorship."""
|
|
29
|
+
|
|
29
30
|
NONE = "none"
|
|
30
31
|
VERIFYING = "verifying"
|
|
31
32
|
TOKEN = "token"
|
|
@@ -39,6 +40,7 @@ class UserOperation:
|
|
|
39
40
|
|
|
40
41
|
This is the format used before packing for on-chain submission.
|
|
41
42
|
"""
|
|
43
|
+
|
|
42
44
|
sender: str
|
|
43
45
|
nonce: int = 0
|
|
44
46
|
init_code: bytes = field(default_factory=bytes)
|
|
@@ -63,20 +65,18 @@ class UserOperation:
|
|
|
63
65
|
"preVerificationGas": hex(self.pre_verification_gas),
|
|
64
66
|
"maxPriorityFeePerGas": hex(self.max_priority_fee_per_gas),
|
|
65
67
|
"maxFeePerGas": hex(self.max_fee_per_gas),
|
|
66
|
-
"paymasterAndData": "0x" + self.paymaster_and_data.hex()
|
|
68
|
+
"paymasterAndData": "0x" + self.paymaster_and_data.hex()
|
|
69
|
+
if self.paymaster_and_data
|
|
70
|
+
else "0x",
|
|
67
71
|
"signature": "0x" + self.signature.hex() if self.signature else "0x",
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
def to_packed_dict(self) -> dict:
|
|
71
75
|
"""Convert to packed format for v0.7 submission."""
|
|
72
76
|
account_gas_limits = pack_account_gas_limits(
|
|
73
|
-
self.verification_gas_limit,
|
|
74
|
-
self.call_gas_limit
|
|
75
|
-
)
|
|
76
|
-
gas_fees = pack_gas_fees(
|
|
77
|
-
self.max_priority_fee_per_gas,
|
|
78
|
-
self.max_fee_per_gas
|
|
77
|
+
self.verification_gas_limit, self.call_gas_limit
|
|
79
78
|
)
|
|
79
|
+
gas_fees = pack_gas_fees(self.max_priority_fee_per_gas, self.max_fee_per_gas)
|
|
80
80
|
|
|
81
81
|
return {
|
|
82
82
|
"sender": self.sender,
|
|
@@ -86,7 +86,9 @@ class UserOperation:
|
|
|
86
86
|
"accountGasLimits": "0x" + account_gas_limits.hex(),
|
|
87
87
|
"preVerificationGas": hex(self.pre_verification_gas),
|
|
88
88
|
"gasFees": "0x" + gas_fees.hex(),
|
|
89
|
-
"paymasterAndData": "0x" + self.paymaster_and_data.hex()
|
|
89
|
+
"paymasterAndData": "0x" + self.paymaster_and_data.hex()
|
|
90
|
+
if self.paymaster_and_data
|
|
91
|
+
else "0x",
|
|
90
92
|
"signature": "0x" + self.signature.hex() if self.signature else "0x",
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -98,6 +100,7 @@ class PackedUserOperation:
|
|
|
98
100
|
|
|
99
101
|
Gas fields are packed into bytes32 for efficiency.
|
|
100
102
|
"""
|
|
103
|
+
|
|
101
104
|
sender: str
|
|
102
105
|
nonce: int
|
|
103
106
|
init_code: bytes
|
|
@@ -112,6 +115,7 @@ class PackedUserOperation:
|
|
|
112
115
|
@dataclass
|
|
113
116
|
class PaymasterData:
|
|
114
117
|
"""Paymaster information for gas sponsorship."""
|
|
118
|
+
|
|
115
119
|
paymaster: str
|
|
116
120
|
paymaster_verification_gas_limit: int = 50000
|
|
117
121
|
paymaster_post_op_gas_limit: int = 50000
|
|
@@ -120,14 +124,17 @@ class PaymasterData:
|
|
|
120
124
|
def to_bytes(self) -> bytes:
|
|
121
125
|
"""Pack into paymasterAndData format."""
|
|
122
126
|
paymaster_bytes = bytes.fromhex(self.paymaster[2:])
|
|
123
|
-
verification_bytes = self.paymaster_verification_gas_limit.to_bytes(16,
|
|
124
|
-
post_op_bytes = self.paymaster_post_op_gas_limit.to_bytes(16,
|
|
125
|
-
return
|
|
127
|
+
verification_bytes = self.paymaster_verification_gas_limit.to_bytes(16, "big")
|
|
128
|
+
post_op_bytes = self.paymaster_post_op_gas_limit.to_bytes(16, "big")
|
|
129
|
+
return (
|
|
130
|
+
paymaster_bytes + verification_bytes + post_op_bytes + self.paymaster_data
|
|
131
|
+
)
|
|
126
132
|
|
|
127
133
|
|
|
128
134
|
@dataclass
|
|
129
135
|
class GasEstimate:
|
|
130
136
|
"""Gas estimation results from the bundler."""
|
|
137
|
+
|
|
131
138
|
verification_gas_limit: int
|
|
132
139
|
call_gas_limit: int
|
|
133
140
|
pre_verification_gas: int
|
|
@@ -138,6 +145,7 @@ class GasEstimate:
|
|
|
138
145
|
@dataclass
|
|
139
146
|
class UserOperationReceipt:
|
|
140
147
|
"""Receipt after UserOperation execution."""
|
|
148
|
+
|
|
141
149
|
user_op_hash: str
|
|
142
150
|
sender: str
|
|
143
151
|
nonce: int
|
|
@@ -154,6 +162,7 @@ class UserOperationReceipt:
|
|
|
154
162
|
@dataclass
|
|
155
163
|
class BundlerConfig:
|
|
156
164
|
"""Configuration for bundler client."""
|
|
165
|
+
|
|
157
166
|
bundler_url: str
|
|
158
167
|
chain_id: int
|
|
159
168
|
entry_point: str = ENTRYPOINT_V07_ADDRESS
|
|
@@ -162,6 +171,7 @@ class BundlerConfig:
|
|
|
162
171
|
@dataclass
|
|
163
172
|
class PaymasterConfig:
|
|
164
173
|
"""Configuration for paymaster integration."""
|
|
174
|
+
|
|
165
175
|
address: str
|
|
166
176
|
url: Optional[str] = None
|
|
167
177
|
paymaster_type: PaymasterType = PaymasterType.SPONSORING
|
|
@@ -170,6 +180,7 @@ class PaymasterConfig:
|
|
|
170
180
|
@dataclass
|
|
171
181
|
class TokenQuote:
|
|
172
182
|
"""Quote for token paymaster."""
|
|
183
|
+
|
|
173
184
|
token: str
|
|
174
185
|
symbol: str
|
|
175
186
|
decimals: int
|
|
@@ -180,6 +191,7 @@ class TokenQuote:
|
|
|
180
191
|
@dataclass
|
|
181
192
|
class AssetChange:
|
|
182
193
|
"""Asset change from simulation."""
|
|
194
|
+
|
|
183
195
|
asset_type: str # native, erc20, erc721, erc1155
|
|
184
196
|
change_type: str # transfer_in, transfer_out
|
|
185
197
|
from_address: str
|
|
@@ -195,6 +207,7 @@ class AssetChange:
|
|
|
195
207
|
@dataclass
|
|
196
208
|
class SimulationResult:
|
|
197
209
|
"""Simulation result for UserOperation."""
|
|
210
|
+
|
|
198
211
|
success: bool
|
|
199
212
|
error: Optional[str] = None
|
|
200
213
|
changes: List[AssetChange] = field(default_factory=list)
|
|
@@ -223,13 +236,13 @@ BUNDLER_METHODS = {
|
|
|
223
236
|
|
|
224
237
|
# Supported chains for ERC-4337
|
|
225
238
|
SUPPORTED_CHAINS = [
|
|
226
|
-
1,
|
|
227
|
-
11155111,
|
|
228
|
-
8453,
|
|
229
|
-
84532,
|
|
230
|
-
10,
|
|
231
|
-
42161,
|
|
232
|
-
137,
|
|
239
|
+
1, # Ethereum Mainnet
|
|
240
|
+
11155111, # Ethereum Sepolia
|
|
241
|
+
8453, # Base
|
|
242
|
+
84532, # Base Sepolia
|
|
243
|
+
10, # Optimism
|
|
244
|
+
42161, # Arbitrum One
|
|
245
|
+
137, # Polygon
|
|
233
246
|
]
|
|
234
247
|
|
|
235
248
|
|
|
@@ -265,29 +278,29 @@ PIMLICO_NETWORKS = {
|
|
|
265
278
|
|
|
266
279
|
def pack_account_gas_limits(verification_gas_limit: int, call_gas_limit: int) -> bytes:
|
|
267
280
|
"""Pack verification and call gas limits into bytes32."""
|
|
268
|
-
verification_bytes = verification_gas_limit.to_bytes(16,
|
|
269
|
-
call_bytes = call_gas_limit.to_bytes(16,
|
|
281
|
+
verification_bytes = verification_gas_limit.to_bytes(16, "big")
|
|
282
|
+
call_bytes = call_gas_limit.to_bytes(16, "big")
|
|
270
283
|
return verification_bytes + call_bytes
|
|
271
284
|
|
|
272
285
|
|
|
273
286
|
def unpack_account_gas_limits(packed: bytes) -> tuple[int, int]:
|
|
274
287
|
"""Unpack account gas limits from bytes32."""
|
|
275
|
-
verification_gas_limit = int.from_bytes(packed[:16],
|
|
276
|
-
call_gas_limit = int.from_bytes(packed[16:],
|
|
288
|
+
verification_gas_limit = int.from_bytes(packed[:16], "big")
|
|
289
|
+
call_gas_limit = int.from_bytes(packed[16:], "big")
|
|
277
290
|
return verification_gas_limit, call_gas_limit
|
|
278
291
|
|
|
279
292
|
|
|
280
293
|
def pack_gas_fees(max_priority_fee_per_gas: int, max_fee_per_gas: int) -> bytes:
|
|
281
294
|
"""Pack max priority fee and max fee per gas into bytes32."""
|
|
282
|
-
priority_bytes = max_priority_fee_per_gas.to_bytes(16,
|
|
283
|
-
max_bytes = max_fee_per_gas.to_bytes(16,
|
|
295
|
+
priority_bytes = max_priority_fee_per_gas.to_bytes(16, "big")
|
|
296
|
+
max_bytes = max_fee_per_gas.to_bytes(16, "big")
|
|
284
297
|
return priority_bytes + max_bytes
|
|
285
298
|
|
|
286
299
|
|
|
287
300
|
def unpack_gas_fees(packed: bytes) -> tuple[int, int]:
|
|
288
301
|
"""Unpack gas fees from bytes32."""
|
|
289
|
-
max_priority_fee_per_gas = int.from_bytes(packed[:16],
|
|
290
|
-
max_fee_per_gas = int.from_bytes(packed[16:],
|
|
302
|
+
max_priority_fee_per_gas = int.from_bytes(packed[:16], "big")
|
|
303
|
+
max_fee_per_gas = int.from_bytes(packed[16:], "big")
|
|
291
304
|
return max_priority_fee_per_gas, max_fee_per_gas
|
|
292
305
|
|
|
293
306
|
|