t402 1.9.1__py3-none-any.whl → 1.10.0__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.
Files changed (51) hide show
  1. t402/__init__.py +1 -1
  2. t402/a2a/__init__.py +73 -0
  3. t402/a2a/helpers.py +158 -0
  4. t402/a2a/types.py +145 -0
  5. t402/bridge/constants.py +1 -1
  6. t402/django/__init__.py +42 -0
  7. t402/django/middleware.py +596 -0
  8. t402/errors.py +213 -0
  9. t402/facilitator.py +125 -0
  10. t402/mcp/constants.py +3 -6
  11. t402/mcp/server.py +428 -44
  12. t402/mcp/web3_utils.py +493 -0
  13. t402/multisig/__init__.py +120 -0
  14. t402/multisig/constants.py +54 -0
  15. t402/multisig/safe.py +441 -0
  16. t402/multisig/signature.py +228 -0
  17. t402/multisig/transaction.py +238 -0
  18. t402/multisig/types.py +108 -0
  19. t402/multisig/utils.py +77 -0
  20. t402/schemes/__init__.py +19 -0
  21. t402/schemes/cosmos/__init__.py +114 -0
  22. t402/schemes/cosmos/constants.py +211 -0
  23. t402/schemes/cosmos/exact_direct/__init__.py +21 -0
  24. t402/schemes/cosmos/exact_direct/client.py +198 -0
  25. t402/schemes/cosmos/exact_direct/facilitator.py +493 -0
  26. t402/schemes/cosmos/exact_direct/server.py +315 -0
  27. t402/schemes/cosmos/types.py +501 -0
  28. t402/schemes/evm/__init__.py +1 -1
  29. t402/schemes/evm/exact_legacy/server.py +1 -1
  30. t402/schemes/near/__init__.py +25 -0
  31. t402/schemes/near/upto/__init__.py +54 -0
  32. t402/schemes/near/upto/types.py +272 -0
  33. t402/schemes/svm/__init__.py +15 -0
  34. t402/schemes/svm/upto/__init__.py +23 -0
  35. t402/schemes/svm/upto/types.py +193 -0
  36. t402/schemes/ton/__init__.py +15 -0
  37. t402/schemes/ton/upto/__init__.py +31 -0
  38. t402/schemes/ton/upto/types.py +215 -0
  39. t402/schemes/tron/__init__.py +21 -4
  40. t402/schemes/tron/upto/__init__.py +30 -0
  41. t402/schemes/tron/upto/types.py +213 -0
  42. t402/starlette/__init__.py +38 -0
  43. t402/starlette/middleware.py +522 -0
  44. t402/ton.py +1 -1
  45. t402/ton_paywall_template.py +1 -1
  46. t402/types.py +100 -2
  47. t402/wdk/chains.py +1 -1
  48. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/METADATA +3 -3
  49. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/RECORD +51 -20
  50. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/WHEEL +0 -0
  51. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/entry_points.txt +0 -0
t402/mcp/web3_utils.py ADDED
@@ -0,0 +1,493 @@
1
+ """Web3 utilities for T402 MCP Server blockchain interactions."""
2
+
3
+ import asyncio
4
+ from typing import Any, Optional
5
+
6
+ from web3 import Web3
7
+ from web3.contract import Contract
8
+ from web3.types import TxReceipt
9
+
10
+ # Minimal ERC-20 ABI for balanceOf, decimals, symbol, transfer, approve, allowance
11
+ ERC20_ABI = [
12
+ {
13
+ "inputs": [{"name": "account", "type": "address"}],
14
+ "name": "balanceOf",
15
+ "outputs": [{"name": "", "type": "uint256"}],
16
+ "stateMutability": "view",
17
+ "type": "function",
18
+ },
19
+ {
20
+ "inputs": [],
21
+ "name": "decimals",
22
+ "outputs": [{"name": "", "type": "uint8"}],
23
+ "stateMutability": "view",
24
+ "type": "function",
25
+ },
26
+ {
27
+ "inputs": [],
28
+ "name": "symbol",
29
+ "outputs": [{"name": "", "type": "string"}],
30
+ "stateMutability": "view",
31
+ "type": "function",
32
+ },
33
+ {
34
+ "inputs": [
35
+ {"name": "to", "type": "address"},
36
+ {"name": "amount", "type": "uint256"},
37
+ ],
38
+ "name": "transfer",
39
+ "outputs": [{"name": "", "type": "bool"}],
40
+ "stateMutability": "nonpayable",
41
+ "type": "function",
42
+ },
43
+ {
44
+ "inputs": [
45
+ {"name": "spender", "type": "address"},
46
+ {"name": "amount", "type": "uint256"},
47
+ ],
48
+ "name": "approve",
49
+ "outputs": [{"name": "", "type": "bool"}],
50
+ "stateMutability": "nonpayable",
51
+ "type": "function",
52
+ },
53
+ {
54
+ "inputs": [
55
+ {"name": "owner", "type": "address"},
56
+ {"name": "spender", "type": "address"},
57
+ ],
58
+ "name": "allowance",
59
+ "outputs": [{"name": "", "type": "uint256"}],
60
+ "stateMutability": "view",
61
+ "type": "function",
62
+ },
63
+ ]
64
+
65
+ # OFT ABI for LayerZero quoteSend and send
66
+ OFT_ABI = [
67
+ {
68
+ "inputs": [
69
+ {
70
+ "components": [
71
+ {"name": "dstEid", "type": "uint32"},
72
+ {"name": "to", "type": "bytes32"},
73
+ {"name": "amountLD", "type": "uint256"},
74
+ {"name": "minAmountLD", "type": "uint256"},
75
+ {"name": "extraOptions", "type": "bytes"},
76
+ {"name": "composeMsg", "type": "bytes"},
77
+ {"name": "oftCmd", "type": "bytes"},
78
+ ],
79
+ "name": "_sendParam",
80
+ "type": "tuple",
81
+ },
82
+ {"name": "_payInLzToken", "type": "bool"},
83
+ ],
84
+ "name": "quoteSend",
85
+ "outputs": [
86
+ {
87
+ "components": [
88
+ {"name": "nativeFee", "type": "uint256"},
89
+ {"name": "lzTokenFee", "type": "uint256"},
90
+ ],
91
+ "name": "msgFee",
92
+ "type": "tuple",
93
+ }
94
+ ],
95
+ "stateMutability": "view",
96
+ "type": "function",
97
+ },
98
+ {
99
+ "inputs": [
100
+ {
101
+ "components": [
102
+ {"name": "dstEid", "type": "uint32"},
103
+ {"name": "to", "type": "bytes32"},
104
+ {"name": "amountLD", "type": "uint256"},
105
+ {"name": "minAmountLD", "type": "uint256"},
106
+ {"name": "extraOptions", "type": "bytes"},
107
+ {"name": "composeMsg", "type": "bytes"},
108
+ {"name": "oftCmd", "type": "bytes"},
109
+ ],
110
+ "name": "_sendParam",
111
+ "type": "tuple",
112
+ },
113
+ {
114
+ "components": [
115
+ {"name": "nativeFee", "type": "uint256"},
116
+ {"name": "lzTokenFee", "type": "uint256"},
117
+ ],
118
+ "name": "_fee",
119
+ "type": "tuple",
120
+ },
121
+ {"name": "_refundAddress", "type": "address"},
122
+ ],
123
+ "name": "send",
124
+ "outputs": [
125
+ {
126
+ "components": [
127
+ {"name": "guid", "type": "bytes32"},
128
+ {"name": "nonce", "type": "uint64"},
129
+ {
130
+ "components": [
131
+ {"name": "nativeFee", "type": "uint256"},
132
+ {"name": "lzTokenFee", "type": "uint256"},
133
+ ],
134
+ "name": "fee",
135
+ "type": "tuple",
136
+ },
137
+ ],
138
+ "name": "msgReceipt",
139
+ "type": "tuple",
140
+ },
141
+ {
142
+ "components": [
143
+ {"name": "amountSentLD", "type": "uint256"},
144
+ {"name": "amountReceivedLD", "type": "uint256"},
145
+ ],
146
+ "name": "oftReceipt",
147
+ "type": "tuple",
148
+ },
149
+ ],
150
+ "stateMutability": "payable",
151
+ "type": "function",
152
+ },
153
+ ]
154
+
155
+ # OFTSent event signature: OFTSent(bytes32,uint32,address,uint256,uint256)
156
+ OFT_SENT_EVENT_SIGNATURE = "OFTSent(bytes32,uint32,address,uint256,uint256)"
157
+
158
+
159
+ def get_web3_provider(rpc_url: str) -> Web3:
160
+ """Create a Web3 instance connected to the given RPC URL.
161
+
162
+ Args:
163
+ rpc_url: The RPC endpoint URL
164
+
165
+ Returns:
166
+ Connected Web3 instance
167
+ """
168
+ return Web3(Web3.HTTPProvider(rpc_url))
169
+
170
+
171
+ def get_erc20_contract(w3: Web3, token_address: str) -> Contract:
172
+ """Get an ERC-20 contract instance.
173
+
174
+ Args:
175
+ w3: Web3 instance
176
+ token_address: Token contract address
177
+
178
+ Returns:
179
+ Contract instance
180
+ """
181
+ return w3.eth.contract(
182
+ address=Web3.to_checksum_address(token_address), abi=ERC20_ABI
183
+ )
184
+
185
+
186
+ def get_oft_contract(w3: Web3, token_address: str) -> Contract:
187
+ """Get an OFT contract instance for LayerZero operations.
188
+
189
+ Args:
190
+ w3: Web3 instance
191
+ token_address: OFT contract address
192
+
193
+ Returns:
194
+ Contract instance
195
+ """
196
+ return w3.eth.contract(
197
+ address=Web3.to_checksum_address(token_address), abi=OFT_ABI
198
+ )
199
+
200
+
201
+ def get_erc20_balance(w3: Web3, token_address: str, owner_address: str) -> int:
202
+ """Query ERC-20 token balance for an address.
203
+
204
+ Args:
205
+ w3: Web3 instance
206
+ token_address: Token contract address
207
+ owner_address: Wallet address to check
208
+
209
+ Returns:
210
+ Raw token balance (integer, not formatted)
211
+ """
212
+ contract = get_erc20_contract(w3, token_address)
213
+ return contract.functions.balanceOf(
214
+ Web3.to_checksum_address(owner_address)
215
+ ).call()
216
+
217
+
218
+ def get_erc20_decimals(w3: Web3, token_address: str) -> int:
219
+ """Query ERC-20 token decimals.
220
+
221
+ Args:
222
+ w3: Web3 instance
223
+ token_address: Token contract address
224
+
225
+ Returns:
226
+ Token decimals
227
+ """
228
+ contract = get_erc20_contract(w3, token_address)
229
+ return contract.functions.decimals().call()
230
+
231
+
232
+ def get_erc20_symbol(w3: Web3, token_address: str) -> str:
233
+ """Query ERC-20 token symbol.
234
+
235
+ Args:
236
+ w3: Web3 instance
237
+ token_address: Token contract address
238
+
239
+ Returns:
240
+ Token symbol
241
+ """
242
+ contract = get_erc20_contract(w3, token_address)
243
+ return contract.functions.symbol().call()
244
+
245
+
246
+ def get_native_balance(w3: Web3, address: str) -> int:
247
+ """Get native token balance for an address.
248
+
249
+ Args:
250
+ w3: Web3 instance
251
+ address: Wallet address
252
+
253
+ Returns:
254
+ Native balance in wei
255
+ """
256
+ return w3.eth.get_balance(Web3.to_checksum_address(address))
257
+
258
+
259
+ def transfer_erc20(
260
+ w3: Web3,
261
+ private_key: str,
262
+ token_address: str,
263
+ to: str,
264
+ amount: int,
265
+ ) -> TxReceipt:
266
+ """Build, sign, send, and wait for an ERC-20 transfer transaction.
267
+
268
+ Args:
269
+ w3: Web3 instance
270
+ private_key: Sender's private key (hex with 0x prefix)
271
+ token_address: Token contract address
272
+ to: Recipient address
273
+ amount: Raw token amount to transfer
274
+
275
+ Returns:
276
+ Transaction receipt
277
+
278
+ Raises:
279
+ ValueError: If balance is insufficient or transaction fails
280
+ """
281
+ contract = get_erc20_contract(w3, token_address)
282
+ account = w3.eth.account.from_key(private_key)
283
+ from_address = account.address
284
+
285
+ # Check balance
286
+ balance = contract.functions.balanceOf(from_address).call()
287
+ if balance < amount:
288
+ raise ValueError(
289
+ f"Insufficient token balance: have {balance}, need {amount}"
290
+ )
291
+
292
+ # Build transaction
293
+ tx = contract.functions.transfer(
294
+ Web3.to_checksum_address(to), amount
295
+ ).build_transaction(
296
+ {
297
+ "from": from_address,
298
+ "nonce": w3.eth.get_transaction_count(from_address),
299
+ "gas": 0, # Will be estimated
300
+ "gasPrice": w3.eth.gas_price,
301
+ }
302
+ )
303
+
304
+ # Estimate gas with 20% buffer
305
+ gas_estimate = w3.eth.estimate_gas(tx)
306
+ tx["gas"] = int(gas_estimate * 1.2)
307
+
308
+ # Sign and send
309
+ signed_tx = w3.eth.account.sign_transaction(tx, private_key)
310
+ tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
311
+
312
+ # Wait for receipt
313
+ receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
314
+
315
+ if receipt["status"] != 1:
316
+ raise ValueError(f"Transaction failed: {tx_hash.hex()}")
317
+
318
+ return receipt
319
+
320
+
321
+ def format_wei_to_ether(wei_amount: int) -> str:
322
+ """Format a wei amount to ether string.
323
+
324
+ Args:
325
+ wei_amount: Amount in wei
326
+
327
+ Returns:
328
+ Formatted ether string
329
+ """
330
+ return str(Web3.from_wei(wei_amount, "ether"))
331
+
332
+
333
+ def address_to_bytes32(address: str) -> bytes:
334
+ """Convert an Ethereum address to bytes32 (left-padded).
335
+
336
+ Args:
337
+ address: Ethereum address with 0x prefix
338
+
339
+ Returns:
340
+ 32-byte array with address right-aligned
341
+ """
342
+ addr = address.lower().removeprefix("0x")
343
+ addr_bytes = bytes.fromhex(addr)
344
+ return b"\x00" * 12 + addr_bytes
345
+
346
+
347
+ def quote_bridge_fee(
348
+ w3: Web3,
349
+ oft_address: str,
350
+ dst_eid: int,
351
+ recipient: str,
352
+ amount: int,
353
+ min_amount: int,
354
+ ) -> tuple[int, int]:
355
+ """Query LayerZero OFT quoteSend for bridge fee.
356
+
357
+ Args:
358
+ w3: Web3 instance
359
+ oft_address: USDT0 OFT contract address
360
+ dst_eid: Destination LayerZero endpoint ID
361
+ recipient: Recipient address on destination chain
362
+ amount: Amount to send (raw, 6 decimals)
363
+ min_amount: Minimum amount to receive
364
+
365
+ Returns:
366
+ Tuple of (native_fee, lz_token_fee) in wei
367
+ """
368
+ contract = get_oft_contract(w3, oft_address)
369
+
370
+ send_param = (
371
+ dst_eid,
372
+ address_to_bytes32(recipient),
373
+ amount,
374
+ min_amount,
375
+ b"", # extraOptions
376
+ b"", # composeMsg
377
+ b"", # oftCmd
378
+ )
379
+
380
+ result = contract.functions.quoteSend(send_param, False).call()
381
+
382
+ # Result is a tuple (nativeFee, lzTokenFee)
383
+ if isinstance(result, (list, tuple)):
384
+ return int(result[0]), int(result[1])
385
+ return int(result["nativeFee"]), int(result["lzTokenFee"])
386
+
387
+
388
+ def execute_bridge_send(
389
+ w3: Web3,
390
+ private_key: str,
391
+ oft_address: str,
392
+ dst_eid: int,
393
+ recipient: str,
394
+ amount: int,
395
+ min_amount: int,
396
+ native_fee: int,
397
+ ) -> TxReceipt:
398
+ """Execute a LayerZero OFT bridge send transaction.
399
+
400
+ Args:
401
+ w3: Web3 instance
402
+ private_key: Sender's private key (hex with 0x prefix)
403
+ oft_address: USDT0 OFT contract address
404
+ dst_eid: Destination LayerZero endpoint ID
405
+ recipient: Recipient address on destination chain
406
+ amount: Amount to send (raw, 6 decimals)
407
+ min_amount: Minimum amount to receive
408
+ native_fee: Native fee from quoteSend (with buffer)
409
+
410
+ Returns:
411
+ Transaction receipt
412
+
413
+ Raises:
414
+ ValueError: If transaction fails
415
+ """
416
+ contract = get_oft_contract(w3, oft_address)
417
+ account = w3.eth.account.from_key(private_key)
418
+ from_address = account.address
419
+
420
+ send_param = (
421
+ dst_eid,
422
+ address_to_bytes32(recipient),
423
+ amount,
424
+ min_amount,
425
+ b"", # extraOptions
426
+ b"", # composeMsg
427
+ b"", # oftCmd
428
+ )
429
+
430
+ fee_param = (native_fee, 0) # (nativeFee, lzTokenFee)
431
+
432
+ tx = contract.functions.send(
433
+ send_param, fee_param, from_address
434
+ ).build_transaction(
435
+ {
436
+ "from": from_address,
437
+ "value": native_fee,
438
+ "nonce": w3.eth.get_transaction_count(from_address),
439
+ "gas": 0,
440
+ "gasPrice": w3.eth.gas_price,
441
+ }
442
+ )
443
+
444
+ gas_estimate = w3.eth.estimate_gas(tx)
445
+ tx["gas"] = int(gas_estimate * 1.2)
446
+
447
+ signed_tx = w3.eth.account.sign_transaction(tx, private_key)
448
+ tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
449
+
450
+ receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
451
+
452
+ if receipt["status"] != 1:
453
+ raise ValueError(f"Bridge transaction failed: {tx_hash.hex()}")
454
+
455
+ return receipt
456
+
457
+
458
+ def extract_message_guid_from_receipt(receipt: TxReceipt) -> Optional[str]:
459
+ """Extract LayerZero message GUID from OFTSent event in receipt logs.
460
+
461
+ Args:
462
+ receipt: Transaction receipt
463
+
464
+ Returns:
465
+ Message GUID hex string, or None if not found
466
+ """
467
+ oft_sent_topic = Web3.keccak(text=OFT_SENT_EVENT_SIGNATURE).hex()
468
+
469
+ for log in receipt.get("logs", []):
470
+ topics = log.get("topics", [])
471
+ if len(topics) >= 2:
472
+ topic0 = topics[0].hex() if isinstance(topics[0], bytes) else topics[0]
473
+ if topic0.lower() == oft_sent_topic.lower():
474
+ guid = topics[1].hex() if isinstance(topics[1], bytes) else topics[1]
475
+ if not guid.startswith("0x"):
476
+ guid = "0x" + guid
477
+ return guid
478
+
479
+ return None
480
+
481
+
482
+ async def run_sync_in_executor(func: Any, *args: Any) -> Any:
483
+ """Run a synchronous function in an executor for async compatibility.
484
+
485
+ Args:
486
+ func: Synchronous function to call
487
+ *args: Arguments to pass to the function
488
+
489
+ Returns:
490
+ Result of the function
491
+ """
492
+ loop = asyncio.get_event_loop()
493
+ return await loop.run_in_executor(None, func, *args)
@@ -0,0 +1,120 @@
1
+ """
2
+ T402 Multi-sig (Safe) Support
3
+
4
+ Provides Safe multi-sig smart account support for T402 payments.
5
+
6
+ Example:
7
+ ```python
8
+ from t402.multisig import SafeClient, SafeConfig, TransactionBuilder
9
+
10
+ # Create client
11
+ config = SafeConfig(
12
+ address="0x...",
13
+ rpc_url="https://arb1.arbitrum.io/rpc",
14
+ )
15
+ client = SafeClient(config)
16
+
17
+ # Get Safe info
18
+ info = await client.get_info()
19
+ print(f"Owners: {info.owners}, Threshold: {info.threshold}")
20
+
21
+ # Create and propose a transaction
22
+ tx = TransactionBuilder().to("0x...").value(1000000).build()
23
+ request = await client.propose_transaction(tx)
24
+
25
+ # Sign with owner keys
26
+ sig1 = await client.sign_transaction_async(request.transaction, "0x...")
27
+ client.add_signature(request, sig1)
28
+
29
+ # Execute when threshold met
30
+ if request.is_ready():
31
+ result = await client.execute_transaction(request, executor_key)
32
+ ```
33
+ """
34
+
35
+ from .types import (
36
+ SignatureType,
37
+ OperationType,
38
+ SafeConfig,
39
+ SafeOwner,
40
+ SafeTransaction,
41
+ SafeSignature,
42
+ TransactionRequest,
43
+ SafeInfo,
44
+ ExecutionResult,
45
+ )
46
+ from .constants import (
47
+ SAFE_4337_MODULE,
48
+ SAFE_MODULE_SETUP,
49
+ SAFE_SINGLETON,
50
+ SAFE_PROXY_FACTORY,
51
+ SAFE_FALLBACK_HANDLER,
52
+ SAFE_ADD_MODULES_LIB,
53
+ SAFE_MULTISEND,
54
+ ENTRYPOINT_V07,
55
+ DEFAULT_REQUEST_EXPIRATION_SECONDS,
56
+ MIN_THRESHOLD,
57
+ MAX_OWNERS,
58
+ )
59
+ from .safe import SafeClient
60
+ from .signature import SignatureCollector
61
+ from .transaction import (
62
+ TransactionBuilder,
63
+ BatchTransactionBuilder,
64
+ erc20_transfer,
65
+ eth_transfer,
66
+ contract_call,
67
+ )
68
+ from .utils import (
69
+ generate_request_id,
70
+ current_timestamp,
71
+ sort_addresses,
72
+ is_valid_threshold,
73
+ are_addresses_unique,
74
+ get_owner_index,
75
+ combine_signatures,
76
+ )
77
+
78
+
79
+ __all__ = [
80
+ # Types
81
+ "SignatureType",
82
+ "OperationType",
83
+ "SafeConfig",
84
+ "SafeOwner",
85
+ "SafeTransaction",
86
+ "SafeSignature",
87
+ "TransactionRequest",
88
+ "SafeInfo",
89
+ "ExecutionResult",
90
+ # Constants
91
+ "SAFE_4337_MODULE",
92
+ "SAFE_MODULE_SETUP",
93
+ "SAFE_SINGLETON",
94
+ "SAFE_PROXY_FACTORY",
95
+ "SAFE_FALLBACK_HANDLER",
96
+ "SAFE_ADD_MODULES_LIB",
97
+ "SAFE_MULTISEND",
98
+ "ENTRYPOINT_V07",
99
+ "DEFAULT_REQUEST_EXPIRATION_SECONDS",
100
+ "MIN_THRESHOLD",
101
+ "MAX_OWNERS",
102
+ # Client
103
+ "SafeClient",
104
+ # Signature collector
105
+ "SignatureCollector",
106
+ # Transaction builders
107
+ "TransactionBuilder",
108
+ "BatchTransactionBuilder",
109
+ "erc20_transfer",
110
+ "eth_transfer",
111
+ "contract_call",
112
+ # Utilities
113
+ "generate_request_id",
114
+ "current_timestamp",
115
+ "sort_addresses",
116
+ "is_valid_threshold",
117
+ "are_addresses_unique",
118
+ "get_owner_index",
119
+ "combine_signatures",
120
+ ]
@@ -0,0 +1,54 @@
1
+ """
2
+ Constants for T402 Multi-sig (Safe) support.
3
+
4
+ Safe 4337 module addresses (v0.3.0) deployed on all major EVM chains.
5
+ """
6
+
7
+ # Safe 4337 Module address
8
+ SAFE_4337_MODULE = "0xa581c4A4DB7175302464fF3C06380BC3270b4037"
9
+
10
+ # Safe Module Setup address
11
+ SAFE_MODULE_SETUP = "0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47"
12
+
13
+ # Safe Singleton address
14
+ SAFE_SINGLETON = "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762"
15
+
16
+ # Safe Proxy Factory address
17
+ SAFE_PROXY_FACTORY = "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67"
18
+
19
+ # Safe Fallback Handler address
20
+ SAFE_FALLBACK_HANDLER = "0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99"
21
+
22
+ # Add Modules Lib address
23
+ SAFE_ADD_MODULES_LIB = "0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb"
24
+
25
+ # MultiSend library address
26
+ SAFE_MULTISEND = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526"
27
+
28
+ # ERC-4337 EntryPoint v0.7 address
29
+ ENTRYPOINT_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
30
+
31
+ # Default configuration values
32
+ DEFAULT_REQUEST_EXPIRATION_SECONDS = 3600 # 1 hour
33
+ DEFAULT_SALT_NONCE = 0
34
+ MAX_OWNERS = 10
35
+ MIN_THRESHOLD = 1
36
+
37
+ # Safe ABI method selectors (function selectors)
38
+ GET_OWNERS_SELECTOR = bytes.fromhex("a0e67e2b")
39
+ GET_THRESHOLD_SELECTOR = bytes.fromhex("e75b2357")
40
+ NONCE_SELECTOR = bytes.fromhex("affed0e0")
41
+ EXEC_TRANSACTION_SELECTOR = bytes.fromhex("6a761202")
42
+ GET_TRANSACTION_HASH_SELECTOR = bytes.fromhex("d8d11f78")
43
+
44
+ # EIP-712 domain type hash for Safe
45
+ SAFE_DOMAIN_SEPARATOR_TYPEHASH = "EIP712Domain(uint256 chainId,address verifyingContract)"
46
+
47
+ # Safe transaction type hash
48
+ SAFE_TX_TYPEHASH = "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"
49
+
50
+ # ERC20 transfer selector
51
+ ERC20_TRANSFER_SELECTOR = bytes.fromhex("a9059cbb")
52
+
53
+ # MultiSend selector
54
+ MULTISEND_SELECTOR = bytes.fromhex("8d80ff0a")