t402 1.5.3__tar.gz → 1.6.0__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 (72) hide show
  1. t402-1.5.3/README.md → t402-1.6.0/PKG-INFO +59 -0
  2. t402-1.5.3/PKG-INFO → t402-1.6.0/README.md +34 -19
  3. {t402-1.5.3 → t402-1.6.0}/pyproject.toml +13 -1
  4. {t402-1.5.3 → t402-1.6.0}/src/t402/__init__.py +37 -0
  5. t402-1.6.0/src/t402/mcp/__init__.py +109 -0
  6. t402-1.6.0/src/t402/mcp/constants.py +213 -0
  7. t402-1.6.0/src/t402/mcp/server.py +527 -0
  8. t402-1.6.0/src/t402/mcp/tools.py +169 -0
  9. t402-1.6.0/src/t402/mcp/types.py +241 -0
  10. {t402-1.5.3 → t402-1.6.0}/src/t402/networks.py +47 -3
  11. t402-1.6.0/src/t402/svm.py +566 -0
  12. t402-1.6.0/tests/test_bridge.py +706 -0
  13. t402-1.6.0/tests/test_mcp.py +505 -0
  14. t402-1.6.0/tests/test_svm.py +493 -0
  15. {t402-1.5.3 → t402-1.6.0}/.gitignore +0 -0
  16. {t402-1.5.3 → t402-1.6.0}/.python-version +0 -0
  17. {t402-1.5.3 → t402-1.6.0}/src/t402/bridge/__init__.py +0 -0
  18. {t402-1.5.3 → t402-1.6.0}/src/t402/bridge/client.py +0 -0
  19. {t402-1.5.3 → t402-1.6.0}/src/t402/bridge/constants.py +0 -0
  20. {t402-1.5.3 → t402-1.6.0}/src/t402/bridge/router.py +0 -0
  21. {t402-1.5.3 → t402-1.6.0}/src/t402/bridge/scan.py +0 -0
  22. {t402-1.5.3 → t402-1.6.0}/src/t402/bridge/types.py +0 -0
  23. {t402-1.5.3 → t402-1.6.0}/src/t402/chains.py +0 -0
  24. {t402-1.5.3 → t402-1.6.0}/src/t402/cli.py +0 -0
  25. {t402-1.5.3 → t402-1.6.0}/src/t402/clients/__init__.py +0 -0
  26. {t402-1.5.3 → t402-1.6.0}/src/t402/clients/base.py +0 -0
  27. {t402-1.5.3 → t402-1.6.0}/src/t402/clients/httpx.py +0 -0
  28. {t402-1.5.3 → t402-1.6.0}/src/t402/clients/requests.py +0 -0
  29. {t402-1.5.3 → t402-1.6.0}/src/t402/common.py +0 -0
  30. {t402-1.5.3 → t402-1.6.0}/src/t402/encoding.py +0 -0
  31. {t402-1.5.3 → t402-1.6.0}/src/t402/erc4337/__init__.py +0 -0
  32. {t402-1.5.3 → t402-1.6.0}/src/t402/erc4337/accounts.py +0 -0
  33. {t402-1.5.3 → t402-1.6.0}/src/t402/erc4337/bundlers.py +0 -0
  34. {t402-1.5.3 → t402-1.6.0}/src/t402/erc4337/paymasters.py +0 -0
  35. {t402-1.5.3 → t402-1.6.0}/src/t402/erc4337/types.py +0 -0
  36. {t402-1.5.3 → t402-1.6.0}/src/t402/evm_paywall_template.py +0 -0
  37. {t402-1.5.3 → t402-1.6.0}/src/t402/exact.py +0 -0
  38. {t402-1.5.3 → t402-1.6.0}/src/t402/facilitator.py +0 -0
  39. {t402-1.5.3 → t402-1.6.0}/src/t402/fastapi/__init__.py +0 -0
  40. {t402-1.5.3 → t402-1.6.0}/src/t402/fastapi/middleware.py +0 -0
  41. {t402-1.5.3 → t402-1.6.0}/src/t402/flask/__init__.py +0 -0
  42. {t402-1.5.3 → t402-1.6.0}/src/t402/flask/middleware.py +0 -0
  43. {t402-1.5.3 → t402-1.6.0}/src/t402/path.py +0 -0
  44. {t402-1.5.3 → t402-1.6.0}/src/t402/paywall.py +0 -0
  45. {t402-1.5.3 → t402-1.6.0}/src/t402/py.typed +0 -0
  46. {t402-1.5.3 → t402-1.6.0}/src/t402/svm_paywall_template.py +0 -0
  47. {t402-1.5.3 → t402-1.6.0}/src/t402/ton.py +0 -0
  48. {t402-1.5.3 → t402-1.6.0}/src/t402/ton_paywall_template.py +0 -0
  49. {t402-1.5.3 → t402-1.6.0}/src/t402/tron.py +0 -0
  50. {t402-1.5.3 → t402-1.6.0}/src/t402/types.py +0 -0
  51. {t402-1.5.3 → t402-1.6.0}/src/t402/wdk/__init__.py +0 -0
  52. {t402-1.5.3 → t402-1.6.0}/src/t402/wdk/chains.py +0 -0
  53. {t402-1.5.3 → t402-1.6.0}/src/t402/wdk/errors.py +0 -0
  54. {t402-1.5.3 → t402-1.6.0}/src/t402/wdk/signer.py +0 -0
  55. {t402-1.5.3 → t402-1.6.0}/src/t402/wdk/types.py +0 -0
  56. {t402-1.5.3 → t402-1.6.0}/tests/clients/__init__.py +0 -0
  57. {t402-1.5.3 → t402-1.6.0}/tests/clients/test_base.py +0 -0
  58. {t402-1.5.3 → t402-1.6.0}/tests/clients/test_httpx.py +0 -0
  59. {t402-1.5.3 → t402-1.6.0}/tests/clients/test_requests.py +0 -0
  60. {t402-1.5.3 → t402-1.6.0}/tests/fastapi_tests/__init__.py +0 -0
  61. {t402-1.5.3 → t402-1.6.0}/tests/fastapi_tests/test_middleware.py +0 -0
  62. {t402-1.5.3 → t402-1.6.0}/tests/flask_tests/__init__.py +0 -0
  63. {t402-1.5.3 → t402-1.6.0}/tests/flask_tests/test_middleware.py +0 -0
  64. {t402-1.5.3 → t402-1.6.0}/tests/test_common.py +0 -0
  65. {t402-1.5.3 → t402-1.6.0}/tests/test_encoding.py +0 -0
  66. {t402-1.5.3 → t402-1.6.0}/tests/test_exact.py +0 -0
  67. {t402-1.5.3 → t402-1.6.0}/tests/test_paywall.py +0 -0
  68. {t402-1.5.3 → t402-1.6.0}/tests/test_ton.py +0 -0
  69. {t402-1.5.3 → t402-1.6.0}/tests/test_tron.py +0 -0
  70. {t402-1.5.3 → t402-1.6.0}/tests/test_types.py +0 -0
  71. {t402-1.5.3 → t402-1.6.0}/tests/test_wdk.py +0 -0
  72. {t402-1.5.3 → t402-1.6.0}/uv.lock +0 -0
@@ -1,3 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: t402
3
+ Version: 1.6.0
4
+ Summary: t402: An internet native payments protocol
5
+ Author-email: T402 Team <dev@t402.io>
6
+ License: Apache-2.0
7
+ Keywords: crypto,payments,sdk,t402,tether,usdt,web3
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: eth-account>=0.13.7
10
+ Requires-Dist: eth-typing>=4.0.0
11
+ Requires-Dist: eth-utils>=3.0.0
12
+ Requires-Dist: fastapi[standard]>=0.115.12
13
+ Requires-Dist: flask>=3.0.0
14
+ Requires-Dist: pydantic-settings>=2.2.1
15
+ Requires-Dist: pydantic>=2.10.3
16
+ Requires-Dist: python-dotenv>=1.0.1
17
+ Requires-Dist: web3>=6.0.0
18
+ Provides-Extra: all
19
+ Requires-Dist: solana>=0.35.0; extra == 'all'
20
+ Requires-Dist: solders>=0.21.0; extra == 'all'
21
+ Provides-Extra: svm
22
+ Requires-Dist: solana>=0.35.0; extra == 'svm'
23
+ Requires-Dist: solders>=0.21.0; extra == 'svm'
24
+ Description-Content-Type: text/markdown
25
+
1
26
  # t402 Python
2
27
 
3
28
  Python SDK for the T402 HTTP-native stablecoin payments protocol.
@@ -260,6 +285,39 @@ is_valid = validate_tron_address("T...")
260
285
  config = get_tron_network_config(TRON_MAINNET)
261
286
  ```
262
287
 
288
+ ### Solana (SVM) Network
289
+
290
+ ```python
291
+ from t402 import (
292
+ SOLANA_MAINNET,
293
+ SOLANA_DEVNET,
294
+ SOLANA_TESTNET,
295
+ validate_svm_address,
296
+ prepare_svm_payment_header,
297
+ get_svm_network_config,
298
+ get_svm_usdc_address,
299
+ is_svm_network,
300
+ )
301
+
302
+ # Validate address
303
+ is_valid = validate_svm_address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
304
+
305
+ # Get network config
306
+ config = get_svm_network_config(SOLANA_MAINNET)
307
+
308
+ # Get USDC mint address
309
+ usdc_mint = get_svm_usdc_address(SOLANA_MAINNET)
310
+
311
+ # Check if network is Solana
312
+ is_solana = is_svm_network("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
313
+ ```
314
+
315
+ Install with optional Solana dependencies:
316
+
317
+ ```bash
318
+ pip install t402[svm]
319
+ ```
320
+
263
321
  ## ERC-4337 Account Abstraction
264
322
 
265
323
  Gasless payments using smart accounts and paymasters:
@@ -383,6 +441,7 @@ signature = await signer.sign_payment(
383
441
  | `is_evm_network(network)` | Check if EVM network |
384
442
  | `is_ton_network(network)` | Check if TON network |
385
443
  | `is_tron_network(network)` | Check if TRON network |
444
+ | `is_svm_network(network)` | Check if Solana SVM network |
386
445
  | `get_network_type(network)` | Get network type string |
387
446
 
388
447
  ### Facilitator Client
@@ -1,22 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: t402
3
- Version: 1.5.3
4
- Summary: t402: An internet native payments protocol
5
- Author-email: T402 Team <dev@t402.io>
6
- License: Apache-2.0
7
- Keywords: crypto,payments,sdk,t402,tether,usdt,web3
8
- Requires-Python: >=3.10
9
- Requires-Dist: eth-account>=0.13.7
10
- Requires-Dist: eth-typing>=4.0.0
11
- Requires-Dist: eth-utils>=3.0.0
12
- Requires-Dist: fastapi[standard]>=0.115.12
13
- Requires-Dist: flask>=3.0.0
14
- Requires-Dist: pydantic-settings>=2.2.1
15
- Requires-Dist: pydantic>=2.10.3
16
- Requires-Dist: python-dotenv>=1.0.1
17
- Requires-Dist: web3>=6.0.0
18
- Description-Content-Type: text/markdown
19
-
20
1
  # t402 Python
21
2
 
22
3
  Python SDK for the T402 HTTP-native stablecoin payments protocol.
@@ -279,6 +260,39 @@ is_valid = validate_tron_address("T...")
279
260
  config = get_tron_network_config(TRON_MAINNET)
280
261
  ```
281
262
 
263
+ ### Solana (SVM) Network
264
+
265
+ ```python
266
+ from t402 import (
267
+ SOLANA_MAINNET,
268
+ SOLANA_DEVNET,
269
+ SOLANA_TESTNET,
270
+ validate_svm_address,
271
+ prepare_svm_payment_header,
272
+ get_svm_network_config,
273
+ get_svm_usdc_address,
274
+ is_svm_network,
275
+ )
276
+
277
+ # Validate address
278
+ is_valid = validate_svm_address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
279
+
280
+ # Get network config
281
+ config = get_svm_network_config(SOLANA_MAINNET)
282
+
283
+ # Get USDC mint address
284
+ usdc_mint = get_svm_usdc_address(SOLANA_MAINNET)
285
+
286
+ # Check if network is Solana
287
+ is_solana = is_svm_network("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
288
+ ```
289
+
290
+ Install with optional Solana dependencies:
291
+
292
+ ```bash
293
+ pip install t402[svm]
294
+ ```
295
+
282
296
  ## ERC-4337 Account Abstraction
283
297
 
284
298
  Gasless payments using smart accounts and paymasters:
@@ -402,6 +416,7 @@ signature = await signer.sign_payment(
402
416
  | `is_evm_network(network)` | Check if EVM network |
403
417
  | `is_ton_network(network)` | Check if TON network |
404
418
  | `is_tron_network(network)` | Check if TRON network |
419
+ | `is_svm_network(network)` | Check if Solana SVM network |
405
420
  | `get_network_type(network)` | Get network type string |
406
421
 
407
422
  ### Facilitator Client
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "t402"
3
- version = "1.5.3"
3
+ version = "1.6.0"
4
4
  description = "t402: An internet native payments protocol"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -24,6 +24,16 @@ dependencies = [
24
24
  [project.scripts]
25
25
  t402 = "t402.cli:main"
26
26
 
27
+ [project.optional-dependencies]
28
+ svm = [
29
+ "solana>=0.35.0",
30
+ "solders>=0.21.0",
31
+ ]
32
+ all = [
33
+ "solana>=0.35.0",
34
+ "solders>=0.21.0",
35
+ ]
36
+
27
37
  [build-system]
28
38
  requires = ["hatchling"]
29
39
  build-backend = "hatchling.build"
@@ -33,6 +43,8 @@ dev = [
33
43
  "pytest>=8.3.5",
34
44
  "pytest-asyncio>=1.0.0",
35
45
  "ruff>=0.11.9",
46
+ "solana>=0.35.0",
47
+ "solders>=0.21.0",
36
48
  ]
37
49
 
38
50
  [tool.pytest.ini_options]
@@ -12,6 +12,7 @@ from t402.networks import (
12
12
  is_ton_network,
13
13
  is_tron_network,
14
14
  is_evm_network,
15
+ is_svm_network,
15
16
  get_network_type,
16
17
  )
17
18
  from t402.types import (
@@ -62,6 +63,24 @@ from t402.tron import (
62
63
  format_amount as format_tron_amount,
63
64
  is_testnet as is_tron_testnet,
64
65
  )
66
+ from t402.svm import (
67
+ SOLANA_MAINNET,
68
+ SOLANA_DEVNET,
69
+ SOLANA_TESTNET,
70
+ USDC_MAINNET_ADDRESS as SVM_USDC_MAINNET_ADDRESS,
71
+ USDC_DEVNET_ADDRESS as SVM_USDC_DEVNET_ADDRESS,
72
+ validate_svm_address,
73
+ get_usdc_address as get_svm_usdc_address,
74
+ get_network_config as get_svm_network_config,
75
+ get_default_asset as get_svm_default_asset,
76
+ prepare_svm_payment_header,
77
+ parse_amount as parse_svm_amount,
78
+ format_amount as format_svm_amount,
79
+ is_testnet as is_svm_testnet,
80
+ validate_transaction as validate_svm_transaction,
81
+ normalize_network as normalize_svm_network,
82
+ get_rpc_url as get_svm_rpc_url,
83
+ )
65
84
  from t402.paywall import (
66
85
  get_paywall_html,
67
86
  get_paywall_template,
@@ -168,6 +187,7 @@ __all__ = [
168
187
  "is_ton_network",
169
188
  "is_tron_network",
170
189
  "is_evm_network",
190
+ "is_svm_network",
171
191
  "get_network_type",
172
192
  # Types
173
193
  "PaymentRequirements",
@@ -215,6 +235,23 @@ __all__ = [
215
235
  "parse_tron_amount",
216
236
  "format_tron_amount",
217
237
  "is_tron_testnet",
238
+ # SVM (Solana) utilities
239
+ "SOLANA_MAINNET",
240
+ "SOLANA_DEVNET",
241
+ "SOLANA_TESTNET",
242
+ "SVM_USDC_MAINNET_ADDRESS",
243
+ "SVM_USDC_DEVNET_ADDRESS",
244
+ "validate_svm_address",
245
+ "get_svm_usdc_address",
246
+ "get_svm_network_config",
247
+ "get_svm_default_asset",
248
+ "prepare_svm_payment_header",
249
+ "parse_svm_amount",
250
+ "format_svm_amount",
251
+ "is_svm_testnet",
252
+ "validate_svm_transaction",
253
+ "normalize_svm_network",
254
+ "get_svm_rpc_url",
218
255
  # Paywall
219
256
  "get_paywall_html",
220
257
  "get_paywall_template",
@@ -0,0 +1,109 @@
1
+ """T402 MCP Server - Model Context Protocol server for AI agent integration.
2
+
3
+ This module provides an MCP server that enables AI agents to interact with
4
+ blockchain payments using the T402 protocol.
5
+
6
+ Example:
7
+ ```python
8
+ from t402.mcp import T402McpServer, ServerConfig
9
+
10
+ config = ServerConfig(demo_mode=True)
11
+ server = T402McpServer(config)
12
+ await server.run()
13
+ ```
14
+
15
+ Available Tools:
16
+ - t402/getBalance: Get token balances for a wallet on specific network
17
+ - t402/getAllBalances: Get balances across all supported networks
18
+ - t402/pay: Execute stablecoin payments (USDC, USDT, USDT0)
19
+ - t402/payGasless: ERC-4337 gasless payments
20
+ - t402/getBridgeFee: Get LayerZero bridge fee quotes
21
+ - t402/bridge: Bridge USDT0 between chains via LayerZero
22
+ """
23
+
24
+ from .server import T402McpServer, run_server
25
+ from .types import (
26
+ ServerConfig,
27
+ SupportedNetwork,
28
+ SupportedToken,
29
+ Tool,
30
+ ToolResult,
31
+ GetBalanceInput,
32
+ GetAllBalancesInput,
33
+ PayInput,
34
+ PayGaslessInput,
35
+ GetBridgeFeeInput,
36
+ BridgeInput,
37
+ BalanceInfo,
38
+ NetworkBalance,
39
+ PaymentResult,
40
+ BridgeFeeResult,
41
+ BridgeResultData,
42
+ )
43
+ from .constants import (
44
+ CHAIN_IDS,
45
+ NATIVE_SYMBOLS,
46
+ EXPLORER_URLS,
47
+ DEFAULT_RPC_URLS,
48
+ USDC_ADDRESSES,
49
+ USDT_ADDRESSES,
50
+ USDT0_ADDRESSES,
51
+ BRIDGEABLE_CHAINS,
52
+ GASLESS_NETWORKS,
53
+ ALL_NETWORKS,
54
+ is_valid_network,
55
+ is_bridgeable_chain,
56
+ is_gasless_network,
57
+ get_token_address,
58
+ get_explorer_tx_url,
59
+ get_rpc_url,
60
+ format_token_amount,
61
+ parse_token_amount,
62
+ )
63
+ from .tools import get_tool_definitions
64
+
65
+ __all__ = [
66
+ # Server
67
+ "T402McpServer",
68
+ "run_server",
69
+ # Config and types
70
+ "ServerConfig",
71
+ "SupportedNetwork",
72
+ "SupportedToken",
73
+ "Tool",
74
+ "ToolResult",
75
+ # Input types
76
+ "GetBalanceInput",
77
+ "GetAllBalancesInput",
78
+ "PayInput",
79
+ "PayGaslessInput",
80
+ "GetBridgeFeeInput",
81
+ "BridgeInput",
82
+ # Result types
83
+ "BalanceInfo",
84
+ "NetworkBalance",
85
+ "PaymentResult",
86
+ "BridgeFeeResult",
87
+ "BridgeResultData",
88
+ # Constants
89
+ "CHAIN_IDS",
90
+ "NATIVE_SYMBOLS",
91
+ "EXPLORER_URLS",
92
+ "DEFAULT_RPC_URLS",
93
+ "USDC_ADDRESSES",
94
+ "USDT_ADDRESSES",
95
+ "USDT0_ADDRESSES",
96
+ "BRIDGEABLE_CHAINS",
97
+ "GASLESS_NETWORKS",
98
+ "ALL_NETWORKS",
99
+ # Functions
100
+ "is_valid_network",
101
+ "is_bridgeable_chain",
102
+ "is_gasless_network",
103
+ "get_token_address",
104
+ "get_explorer_tx_url",
105
+ "get_rpc_url",
106
+ "format_token_amount",
107
+ "parse_token_amount",
108
+ "get_tool_definitions",
109
+ ]
@@ -0,0 +1,213 @@
1
+ """Constants for T402 MCP Server."""
2
+
3
+ from typing import Optional
4
+
5
+ from .types import ServerConfig, SupportedNetwork, SupportedToken
6
+
7
+ # Chain IDs for supported networks
8
+ CHAIN_IDS: dict[SupportedNetwork, int] = {
9
+ "ethereum": 1,
10
+ "base": 8453,
11
+ "arbitrum": 42161,
12
+ "optimism": 10,
13
+ "polygon": 137,
14
+ "avalanche": 43114,
15
+ "ink": 57073,
16
+ "berachain": 80094,
17
+ "unichain": 130,
18
+ }
19
+
20
+ # Native token symbols for each network
21
+ NATIVE_SYMBOLS: dict[SupportedNetwork, str] = {
22
+ "ethereum": "ETH",
23
+ "base": "ETH",
24
+ "arbitrum": "ETH",
25
+ "optimism": "ETH",
26
+ "polygon": "MATIC",
27
+ "avalanche": "AVAX",
28
+ "ink": "ETH",
29
+ "berachain": "BERA",
30
+ "unichain": "ETH",
31
+ }
32
+
33
+ # Block explorer URLs for each network
34
+ EXPLORER_URLS: dict[SupportedNetwork, str] = {
35
+ "ethereum": "https://etherscan.io",
36
+ "base": "https://basescan.org",
37
+ "arbitrum": "https://arbiscan.io",
38
+ "optimism": "https://optimistic.etherscan.io",
39
+ "polygon": "https://polygonscan.com",
40
+ "avalanche": "https://snowtrace.io",
41
+ "ink": "https://explorer.ink.xyz",
42
+ "berachain": "https://berascan.com",
43
+ "unichain": "https://uniscan.xyz",
44
+ }
45
+
46
+ # Default RPC URLs for each network
47
+ DEFAULT_RPC_URLS: dict[SupportedNetwork, str] = {
48
+ "ethereum": "https://eth.llamarpc.com",
49
+ "base": "https://mainnet.base.org",
50
+ "arbitrum": "https://arb1.arbitrum.io/rpc",
51
+ "optimism": "https://mainnet.optimism.io",
52
+ "polygon": "https://polygon-rpc.com",
53
+ "avalanche": "https://api.avax.network/ext/bc/C/rpc",
54
+ "ink": "https://rpc-qnd.ink.xyz",
55
+ "berachain": "https://artio.rpc.berachain.com",
56
+ "unichain": "https://mainnet.unichain.org",
57
+ }
58
+
59
+ # USDC contract addresses by network
60
+ USDC_ADDRESSES: dict[SupportedNetwork, str] = {
61
+ "ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
62
+ "base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
63
+ "arbitrum": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
64
+ "optimism": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
65
+ "polygon": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
66
+ "avalanche": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
67
+ "ink": "0x0200C29006150606B650577BBE7B6248F58470c1",
68
+ "berachain": "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
69
+ "unichain": "0x588ce4F028D8e7B53B687865d6A67b3A54C75518",
70
+ }
71
+
72
+ # USDT contract addresses by network
73
+ USDT_ADDRESSES: dict[SupportedNetwork, str] = {
74
+ "ethereum": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
75
+ "arbitrum": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
76
+ "optimism": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
77
+ "polygon": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
78
+ "avalanche": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
79
+ }
80
+
81
+ # USDT0 OFT contract addresses (LayerZero bridgeable)
82
+ USDT0_ADDRESSES: dict[SupportedNetwork, str] = {
83
+ "ethereum": "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
84
+ "arbitrum": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
85
+ "ink": "0x0200C29006150606B650577BBE7B6248F58470c1",
86
+ "berachain": "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
87
+ "unichain": "0x588ce4F028D8e7B53B687865d6A67b3A54C75518",
88
+ }
89
+
90
+ # Networks that support USDT0 bridging via LayerZero
91
+ BRIDGEABLE_CHAINS: list[SupportedNetwork] = [
92
+ "ethereum",
93
+ "arbitrum",
94
+ "ink",
95
+ "berachain",
96
+ "unichain",
97
+ ]
98
+
99
+ # Networks that support ERC-4337 gasless payments
100
+ GASLESS_NETWORKS: list[SupportedNetwork] = [
101
+ "ethereum",
102
+ "base",
103
+ "arbitrum",
104
+ "optimism",
105
+ "polygon",
106
+ "avalanche",
107
+ ]
108
+
109
+ # LayerZero endpoint IDs for bridging
110
+ LAYERZERO_ENDPOINT_IDS: dict[SupportedNetwork, int] = {
111
+ "ethereum": 30101,
112
+ "arbitrum": 30110,
113
+ "ink": 30291,
114
+ "berachain": 30362,
115
+ "unichain": 30320,
116
+ }
117
+
118
+ # LayerZero Scan URL for tracking bridge messages
119
+ LAYERZERO_SCAN_URL = "https://layerzeroscan.com/tx/"
120
+
121
+ # All supported networks
122
+ ALL_NETWORKS: list[SupportedNetwork] = [
123
+ "ethereum",
124
+ "base",
125
+ "arbitrum",
126
+ "optimism",
127
+ "polygon",
128
+ "avalanche",
129
+ "ink",
130
+ "berachain",
131
+ "unichain",
132
+ ]
133
+
134
+ # Token decimals
135
+ TOKEN_DECIMALS = 6
136
+ NATIVE_DECIMALS = 18
137
+
138
+
139
+ def is_valid_network(network: str) -> bool:
140
+ """Check if a network string is valid."""
141
+ return network in ALL_NETWORKS
142
+
143
+
144
+ def is_bridgeable_chain(network: str) -> bool:
145
+ """Check if a network supports USDT0 bridging."""
146
+ return network in BRIDGEABLE_CHAINS
147
+
148
+
149
+ def is_gasless_network(network: str) -> bool:
150
+ """Check if a network supports ERC-4337 gasless payments."""
151
+ return network in GASLESS_NETWORKS
152
+
153
+
154
+ def get_token_address(
155
+ network: SupportedNetwork, token: SupportedToken
156
+ ) -> Optional[str]:
157
+ """Get the token contract address for a network."""
158
+ if token == "USDC":
159
+ return USDC_ADDRESSES.get(network)
160
+ elif token == "USDT":
161
+ return USDT_ADDRESSES.get(network)
162
+ elif token == "USDT0":
163
+ return USDT0_ADDRESSES.get(network)
164
+ return None
165
+
166
+
167
+ def get_explorer_tx_url(network: SupportedNetwork, tx_hash: str) -> str:
168
+ """Get the explorer URL for a transaction."""
169
+ base_url = EXPLORER_URLS.get(network, "")
170
+ if not base_url:
171
+ return ""
172
+ return f"{base_url}/tx/{tx_hash}"
173
+
174
+
175
+ def get_rpc_url(config: Optional[ServerConfig], network: SupportedNetwork) -> str:
176
+ """Get the RPC URL for a network, using config override if available."""
177
+ if config and config.rpc_urls and network in config.rpc_urls:
178
+ return config.rpc_urls[network]
179
+ return DEFAULT_RPC_URLS.get(network, "")
180
+
181
+
182
+ def format_token_amount(amount: int, decimals: int) -> str:
183
+ """Format a raw token amount with decimals to human-readable string."""
184
+ if amount == 0:
185
+ return "0"
186
+
187
+ divisor = 10**decimals
188
+ whole = amount // divisor
189
+ fraction = amount % divisor
190
+
191
+ if fraction == 0:
192
+ return str(whole)
193
+
194
+ # Format fraction and trim trailing zeros
195
+ fraction_str = str(fraction).zfill(decimals).rstrip("0")
196
+ return f"{whole}.{fraction_str}"
197
+
198
+
199
+ def parse_token_amount(amount: str, decimals: int) -> int:
200
+ """Parse a human-readable amount string to raw token units."""
201
+ parts = amount.split(".")
202
+
203
+ whole = int(parts[0])
204
+ result = whole * (10**decimals)
205
+
206
+ if len(parts) == 2:
207
+ frac = parts[1]
208
+ if len(frac) > decimals:
209
+ frac = frac[:decimals]
210
+ frac = frac.ljust(decimals, "0")
211
+ result += int(frac)
212
+
213
+ return result