t402 1.4.0__py3-none-any.whl → 1.6.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.
t402/__init__.py CHANGED
@@ -1,3 +1,6 @@
1
+ # Package version
2
+ __version__ = "1.5.3"
3
+
1
4
  # Re-export commonly used items for convenience
2
5
  from t402.common import (
3
6
  parse_money,
@@ -9,6 +12,7 @@ from t402.networks import (
9
12
  is_ton_network,
10
13
  is_tron_network,
11
14
  is_evm_network,
15
+ is_svm_network,
12
16
  get_network_type,
13
17
  )
14
18
  from t402.types import (
@@ -59,6 +63,24 @@ from t402.tron import (
59
63
  format_amount as format_tron_amount,
60
64
  is_testnet as is_tron_testnet,
61
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
+ )
62
84
  from t402.paywall import (
63
85
  get_paywall_html,
64
86
  get_paywall_template,
@@ -117,12 +139,43 @@ from t402.bridge import (
117
139
  CrossChainPaymentParams,
118
140
  CrossChainPaymentResult,
119
141
  )
142
+ from t402.wdk import (
143
+ # Signer
144
+ WDKSigner,
145
+ generate_seed_phrase,
146
+ validate_seed_phrase,
147
+ # Types
148
+ WDKConfig,
149
+ ChainConfig as WDKChainConfig,
150
+ NetworkType,
151
+ TokenInfo as WDKTokenInfo,
152
+ TokenBalance,
153
+ ChainBalance,
154
+ AggregatedBalance,
155
+ PaymentParams,
156
+ PaymentResult,
157
+ SignedTypedData,
158
+ # Chain utilities
159
+ DEFAULT_CHAINS as WDK_DEFAULT_CHAINS,
160
+ USDT0_ADDRESSES as WDK_USDT0_ADDRESSES,
161
+ get_chain_config as get_wdk_chain_config,
162
+ get_usdt0_chains as get_wdk_usdt0_chains,
163
+ # Errors
164
+ WDKError,
165
+ WDKInitializationError,
166
+ SignerError,
167
+ SigningError,
168
+ BalanceError as WDKBalanceError,
169
+ WDKErrorCode,
170
+ )
120
171
 
121
172
  def hello() -> str:
122
173
  return "Hello from t402!"
123
174
 
124
175
 
125
176
  __all__ = [
177
+ # Version
178
+ "__version__",
126
179
  # Core
127
180
  "hello",
128
181
  "t402_VERSION",
@@ -134,6 +187,7 @@ __all__ = [
134
187
  "is_ton_network",
135
188
  "is_tron_network",
136
189
  "is_evm_network",
190
+ "is_svm_network",
137
191
  "get_network_type",
138
192
  # Types
139
193
  "PaymentRequirements",
@@ -181,6 +235,23 @@ __all__ = [
181
235
  "parse_tron_amount",
182
236
  "format_tron_amount",
183
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",
184
255
  # Paywall
185
256
  "get_paywall_html",
186
257
  "get_paywall_template",
@@ -226,4 +297,31 @@ __all__ = [
226
297
  "LayerZeroMessageStatus",
227
298
  "CrossChainPaymentParams",
228
299
  "CrossChainPaymentResult",
300
+ # WDK - Signer
301
+ "WDKSigner",
302
+ "generate_seed_phrase",
303
+ "validate_seed_phrase",
304
+ # WDK - Types
305
+ "WDKConfig",
306
+ "WDKChainConfig",
307
+ "NetworkType",
308
+ "WDKTokenInfo",
309
+ "TokenBalance",
310
+ "ChainBalance",
311
+ "AggregatedBalance",
312
+ "PaymentParams",
313
+ "PaymentResult",
314
+ "SignedTypedData",
315
+ # WDK - Chain utilities
316
+ "WDK_DEFAULT_CHAINS",
317
+ "WDK_USDT0_ADDRESSES",
318
+ "get_wdk_chain_config",
319
+ "get_wdk_usdt0_chains",
320
+ # WDK - Errors
321
+ "WDKError",
322
+ "WDKInitializationError",
323
+ "SignerError",
324
+ "SigningError",
325
+ "WDKBalanceError",
326
+ "WDKErrorCode",
229
327
  ]
t402/bridge/client.py CHANGED
@@ -1,6 +1,5 @@
1
1
  """USDT0 Bridge Client for LayerZero OFT transfers."""
2
2
 
3
- from typing import Optional
4
3
 
5
4
  from .constants import (
6
5
  DEFAULT_EXTRA_OPTIONS,
t402/bridge/types.py CHANGED
@@ -3,7 +3,6 @@
3
3
  from dataclasses import dataclass, field
4
4
  from enum import Enum
5
5
  from typing import Callable, Optional, Protocol
6
- from decimal import Decimal
7
6
 
8
7
 
9
8
  class BridgeStatus(str, Enum):
t402/cli.py ADDED
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ T402 CLI - Command-line interface for the T402 payment protocol.
4
+
5
+ Usage:
6
+ t402 verify <payment-payload> [--facilitator URL]
7
+ t402 settle <payment-payload> [--facilitator URL]
8
+ t402 supported [--facilitator URL]
9
+ t402 encode <json-file>
10
+ t402 decode <base64-string>
11
+ t402 version
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import asyncio
17
+ import json
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from . import __version__
23
+ from .encoding import decode_payment, encode_payment
24
+ from .facilitator import FacilitatorClient, FacilitatorConfig
25
+ from .types import PaymentPayload
26
+
27
+
28
+ def create_parser() -> argparse.ArgumentParser:
29
+ """Create the argument parser for the CLI."""
30
+ parser = argparse.ArgumentParser(
31
+ prog="t402",
32
+ description="T402 Payment Protocol CLI",
33
+ formatter_class=argparse.RawDescriptionHelpFormatter,
34
+ epilog="""
35
+ Examples:
36
+ # Verify a payment
37
+ t402 verify <base64-encoded-payload>
38
+
39
+ # Settle a payment
40
+ t402 settle <base64-encoded-payload>
41
+
42
+ # List supported networks and schemes
43
+ t402 supported
44
+
45
+ # Encode a payment payload from JSON
46
+ t402 encode payment.json
47
+
48
+ # Decode a base64 payment payload
49
+ t402 decode <base64-string>
50
+ """,
51
+ )
52
+ parser.add_argument(
53
+ "-v", "--version", action="version", version=f"t402 {__version__}"
54
+ )
55
+ parser.add_argument(
56
+ "-f",
57
+ "--facilitator",
58
+ default="https://facilitator.t402.io",
59
+ help="Facilitator URL (default: https://facilitator.t402.io)",
60
+ )
61
+ parser.add_argument(
62
+ "-o",
63
+ "--output",
64
+ choices=["json", "text"],
65
+ default="text",
66
+ help="Output format (default: text)",
67
+ )
68
+
69
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
70
+
71
+ # verify command
72
+ verify_parser = subparsers.add_parser("verify", help="Verify a payment payload")
73
+ verify_parser.add_argument("payload", help="Base64-encoded payment payload")
74
+
75
+ # settle command
76
+ settle_parser = subparsers.add_parser("settle", help="Settle a payment")
77
+ settle_parser.add_argument("payload", help="Base64-encoded payment payload")
78
+
79
+ # supported command
80
+ subparsers.add_parser("supported", help="List supported networks and schemes")
81
+
82
+ # encode command
83
+ encode_parser = subparsers.add_parser(
84
+ "encode", help="Encode a payment payload from JSON"
85
+ )
86
+ encode_parser.add_argument(
87
+ "file", type=Path, help="JSON file containing payment payload"
88
+ )
89
+
90
+ # decode command
91
+ decode_parser = subparsers.add_parser(
92
+ "decode", help="Decode a base64-encoded payment payload"
93
+ )
94
+ decode_parser.add_argument("payload", help="Base64-encoded payment payload")
95
+
96
+ # info command
97
+ info_parser = subparsers.add_parser("info", help="Show information about a network")
98
+ info_parser.add_argument("network", help="Network identifier (e.g., eip155:1)")
99
+
100
+ return parser
101
+
102
+
103
+ def output_result(result: Any, output_format: str) -> None:
104
+ """Output result in the specified format."""
105
+ if output_format == "json":
106
+ if hasattr(result, "model_dump"):
107
+ print(json.dumps(result.model_dump(), indent=2))
108
+ elif isinstance(result, dict):
109
+ print(json.dumps(result, indent=2))
110
+ else:
111
+ print(json.dumps({"result": str(result)}, indent=2))
112
+ else:
113
+ if isinstance(result, dict):
114
+ for key, value in result.items():
115
+ print(f"{key}: {value}")
116
+ else:
117
+ print(result)
118
+
119
+
120
+ async def cmd_verify(args: argparse.Namespace) -> int:
121
+ """Verify a payment payload."""
122
+ try:
123
+ config = FacilitatorConfig(base_url=args.facilitator)
124
+ client = FacilitatorClient(config)
125
+
126
+ # Decode the payload first
127
+ payload_dict = decode_payment(args.payload)
128
+ payload = PaymentPayload.model_validate(payload_dict)
129
+
130
+ result = await client.verify(payload)
131
+
132
+ if args.output == "json":
133
+ print(json.dumps({"valid": result.valid, "error": result.error}, indent=2))
134
+ else:
135
+ if result.valid:
136
+ print("Payment is VALID")
137
+ else:
138
+ print(f"Payment is INVALID: {result.error}")
139
+
140
+ return 0 if result.valid else 1
141
+ except Exception as e:
142
+ print(f"Error: {e}", file=sys.stderr)
143
+ return 1
144
+
145
+
146
+ async def cmd_settle(args: argparse.Namespace) -> int:
147
+ """Settle a payment."""
148
+ try:
149
+ config = FacilitatorConfig(base_url=args.facilitator)
150
+ client = FacilitatorClient(config)
151
+
152
+ # Decode the payload first
153
+ payload_dict = decode_payment(args.payload)
154
+ payload = PaymentPayload.model_validate(payload_dict)
155
+
156
+ result = await client.settle(payload)
157
+
158
+ if args.output == "json":
159
+ print(
160
+ json.dumps(
161
+ {
162
+ "success": result.success,
163
+ "transaction_hash": result.transaction_hash,
164
+ "error": result.error,
165
+ },
166
+ indent=2,
167
+ )
168
+ )
169
+ else:
170
+ if result.success:
171
+ print("Payment settled successfully!")
172
+ print(f"Transaction hash: {result.transaction_hash}")
173
+ else:
174
+ print(f"Settlement failed: {result.error}")
175
+
176
+ return 0 if result.success else 1
177
+ except Exception as e:
178
+ print(f"Error: {e}", file=sys.stderr)
179
+ return 1
180
+
181
+
182
+ async def cmd_supported(args: argparse.Namespace) -> int:
183
+ """List supported networks and schemes."""
184
+ try:
185
+ config = FacilitatorConfig(base_url=args.facilitator)
186
+ client = FacilitatorClient(config)
187
+
188
+ result = await client.list_supported()
189
+
190
+ if args.output == "json":
191
+ print(
192
+ json.dumps(
193
+ {
194
+ "kinds": [k.model_dump() for k in result.kinds],
195
+ "signers": result.signers,
196
+ "extensions": result.extensions,
197
+ },
198
+ indent=2,
199
+ )
200
+ )
201
+ else:
202
+ print("Supported Payment Kinds:")
203
+ print("-" * 50)
204
+ for kind in result.kinds:
205
+ print(f" Scheme: {kind.scheme}")
206
+ print(f" Network: {kind.network}")
207
+ if hasattr(kind, "token") and kind.token:
208
+ print(f" Token: {kind.token}")
209
+ print()
210
+
211
+ if result.signers:
212
+ print("Supported Signers:")
213
+ for signer in result.signers:
214
+ print(f" - {signer}")
215
+ print()
216
+
217
+ if result.extensions:
218
+ print("Supported Extensions:")
219
+ for ext in result.extensions:
220
+ print(f" - {ext}")
221
+
222
+ return 0
223
+ except Exception as e:
224
+ print(f"Error: {e}", file=sys.stderr)
225
+ return 1
226
+
227
+
228
+ def cmd_encode(args: argparse.Namespace) -> int:
229
+ """Encode a payment payload from JSON."""
230
+ try:
231
+ with open(args.file) as f:
232
+ payload_dict = json.load(f)
233
+
234
+ encoded = encode_payment(payload_dict)
235
+ print(encoded)
236
+ return 0
237
+ except FileNotFoundError:
238
+ print(f"Error: File not found: {args.file}", file=sys.stderr)
239
+ return 1
240
+ except json.JSONDecodeError as e:
241
+ print(f"Error: Invalid JSON: {e}", file=sys.stderr)
242
+ return 1
243
+ except Exception as e:
244
+ print(f"Error: {e}", file=sys.stderr)
245
+ return 1
246
+
247
+
248
+ def cmd_decode(args: argparse.Namespace) -> int:
249
+ """Decode a base64-encoded payment payload."""
250
+ try:
251
+ decoded = decode_payment(args.payload)
252
+
253
+ if args.output == "json":
254
+ print(json.dumps(decoded, indent=2))
255
+ else:
256
+ print(json.dumps(decoded, indent=2))
257
+
258
+ return 0
259
+ except Exception as e:
260
+ print(f"Error: Failed to decode payload: {e}", file=sys.stderr)
261
+ return 1
262
+
263
+
264
+ def cmd_info(args: argparse.Namespace) -> int:
265
+ """Show information about a network."""
266
+ from .networks import is_evm_network, is_ton_network, is_tron_network
267
+
268
+ network = args.network
269
+
270
+ info = {
271
+ "network": network,
272
+ "is_evm": is_evm_network(network),
273
+ "is_ton": is_ton_network(network),
274
+ "is_tron": is_tron_network(network),
275
+ }
276
+
277
+ # Add chain-specific info
278
+ if is_evm_network(network):
279
+ from .chains import EVM_CHAINS
280
+
281
+ chain_id = network.split(":")[1] if ":" in network else network
282
+ if chain_id in EVM_CHAINS:
283
+ chain = EVM_CHAINS[chain_id]
284
+ info["chain_name"] = chain.get("name", "Unknown")
285
+ info["currency"] = chain.get("currency", "Unknown")
286
+
287
+ if args.output == "json":
288
+ print(json.dumps(info, indent=2))
289
+ else:
290
+ for key, value in info.items():
291
+ print(f"{key}: {value}")
292
+
293
+ return 0
294
+
295
+
296
+ def main() -> int:
297
+ """Main entry point for the CLI."""
298
+ parser = create_parser()
299
+ args = parser.parse_args()
300
+
301
+ if not args.command:
302
+ parser.print_help()
303
+ return 0
304
+
305
+ # Route to the appropriate command handler
306
+ if args.command == "verify":
307
+ return asyncio.run(cmd_verify(args))
308
+ elif args.command == "settle":
309
+ return asyncio.run(cmd_settle(args))
310
+ elif args.command == "supported":
311
+ return asyncio.run(cmd_supported(args))
312
+ elif args.command == "encode":
313
+ return cmd_encode(args)
314
+ elif args.command == "decode":
315
+ return cmd_decode(args)
316
+ elif args.command == "info":
317
+ return cmd_info(args)
318
+ else:
319
+ parser.print_help()
320
+ return 0
321
+
322
+
323
+ if __name__ == "__main__":
324
+ sys.exit(main())
t402/erc4337/accounts.py CHANGED
@@ -6,7 +6,7 @@ including Safe smart account with 4337 module support.
6
6
  """
7
7
 
8
8
  from dataclasses import dataclass
9
- from typing import Optional, List, Tuple
9
+ from typing import Optional, List
10
10
  from abc import ABC, abstractmethod
11
11
  from eth_account import Account
12
12
  from eth_account.messages import encode_defunct
@@ -95,8 +95,6 @@ class SafeSmartAccount(SmartAccountSigner):
95
95
  return self._cached_address
96
96
 
97
97
  # Calculate counterfactual address via CREATE2
98
- init_code = self.get_init_code()
99
-
100
98
  factory_address = bytes.fromhex(SAFE_4337_ADDRESSES["proxy_factory"][2:])
101
99
  salt_hash = self._calculate_salt()
102
100
  proxy_init_code = self._get_proxy_creation_code()
@@ -6,15 +6,13 @@ gas sponsorship, including Pimlico, Biconomy, and Stackup clients.
6
6
  """
7
7
 
8
8
  import httpx
9
- from dataclasses import dataclass
10
- from typing import Optional, List, Dict, Any, Union
9
+ from typing import Optional, List, Dict, Any
11
10
  from abc import ABC, abstractmethod
12
11
 
13
12
  from .types import (
14
13
  UserOperation,
15
14
  PaymasterData,
16
15
  TokenQuote,
17
- GasEstimate,
18
16
  ENTRYPOINT_V07_ADDRESS,
19
17
  PIMLICO_NETWORKS,
20
18
  DEFAULT_GAS_LIMITS,
t402/erc4337/types.py CHANGED
@@ -7,8 +7,7 @@ bundler interactions, and paymaster integration.
7
7
 
8
8
  from dataclasses import dataclass, field
9
9
  from enum import Enum
10
- from typing import Optional, List, Union
11
- from eth_typing import HexStr, Address
10
+ from typing import Optional, List
12
11
 
13
12
  # EntryPoint addresses (canonical deployments)
14
13
  ENTRYPOINT_V07_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
@@ -108,7 +108,7 @@ def require_payment(
108
108
  mime_type=mime_type,
109
109
  pay_to=pay_to_address,
110
110
  max_timeout_seconds=max_deadline_seconds,
111
- # TODO: Rename output_schema to request_structure
111
+ # Contains both input and output schema (field name kept for backwards compatibility)
112
112
  output_schema={
113
113
  "input": {
114
114
  "type": "http",
t402/flask/middleware.py CHANGED
@@ -192,7 +192,7 @@ class PaymentMiddleware:
192
192
  mime_type=config["mime_type"],
193
193
  pay_to=config["pay_to_address"],
194
194
  max_timeout_seconds=config["max_deadline_seconds"],
195
- # TODO: Rename output_schema to request_structure
195
+ # Contains both input and output schema (field name kept for backwards compatibility)
196
196
  output_schema={
197
197
  "input": {
198
198
  "type": "http",
t402/mcp/__init__.py ADDED
@@ -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
+ ]