t402 1.4.0__py3-none-any.whl → 1.5.3__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,
@@ -117,12 +120,43 @@ from t402.bridge import (
117
120
  CrossChainPaymentParams,
118
121
  CrossChainPaymentResult,
119
122
  )
123
+ from t402.wdk import (
124
+ # Signer
125
+ WDKSigner,
126
+ generate_seed_phrase,
127
+ validate_seed_phrase,
128
+ # Types
129
+ WDKConfig,
130
+ ChainConfig as WDKChainConfig,
131
+ NetworkType,
132
+ TokenInfo as WDKTokenInfo,
133
+ TokenBalance,
134
+ ChainBalance,
135
+ AggregatedBalance,
136
+ PaymentParams,
137
+ PaymentResult,
138
+ SignedTypedData,
139
+ # Chain utilities
140
+ DEFAULT_CHAINS as WDK_DEFAULT_CHAINS,
141
+ USDT0_ADDRESSES as WDK_USDT0_ADDRESSES,
142
+ get_chain_config as get_wdk_chain_config,
143
+ get_usdt0_chains as get_wdk_usdt0_chains,
144
+ # Errors
145
+ WDKError,
146
+ WDKInitializationError,
147
+ SignerError,
148
+ SigningError,
149
+ BalanceError as WDKBalanceError,
150
+ WDKErrorCode,
151
+ )
120
152
 
121
153
  def hello() -> str:
122
154
  return "Hello from t402!"
123
155
 
124
156
 
125
157
  __all__ = [
158
+ # Version
159
+ "__version__",
126
160
  # Core
127
161
  "hello",
128
162
  "t402_VERSION",
@@ -226,4 +260,31 @@ __all__ = [
226
260
  "LayerZeroMessageStatus",
227
261
  "CrossChainPaymentParams",
228
262
  "CrossChainPaymentResult",
263
+ # WDK - Signer
264
+ "WDKSigner",
265
+ "generate_seed_phrase",
266
+ "validate_seed_phrase",
267
+ # WDK - Types
268
+ "WDKConfig",
269
+ "WDKChainConfig",
270
+ "NetworkType",
271
+ "WDKTokenInfo",
272
+ "TokenBalance",
273
+ "ChainBalance",
274
+ "AggregatedBalance",
275
+ "PaymentParams",
276
+ "PaymentResult",
277
+ "SignedTypedData",
278
+ # WDK - Chain utilities
279
+ "WDK_DEFAULT_CHAINS",
280
+ "WDK_USDT0_ADDRESSES",
281
+ "get_wdk_chain_config",
282
+ "get_wdk_usdt0_chains",
283
+ # WDK - Errors
284
+ "WDKError",
285
+ "WDKInitializationError",
286
+ "SignerError",
287
+ "SigningError",
288
+ "WDKBalanceError",
289
+ "WDKErrorCode",
229
290
  ]
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/wdk/__init__.py ADDED
@@ -0,0 +1,140 @@
1
+ """
2
+ T402 WDK Python Adapter
3
+
4
+ Provides wallet functionality for T402 payments using Tether WDK-compatible
5
+ seed phrase derivation.
6
+
7
+ Example:
8
+ ```python
9
+ from t402.wdk import WDKSigner, generate_seed_phrase
10
+
11
+ # Generate a new seed phrase
12
+ seed = generate_seed_phrase()
13
+
14
+ # Create signer
15
+ signer = WDKSigner(
16
+ seed_phrase=seed,
17
+ chains={"arbitrum": "https://arb1.arbitrum.io/rpc"}
18
+ )
19
+ await signer.initialize()
20
+
21
+ # Get address
22
+ address = signer.get_address("evm")
23
+
24
+ # Get balances
25
+ balances = await signer.get_all_balances()
26
+ ```
27
+ """
28
+
29
+ from .signer import (
30
+ WDKSigner,
31
+ generate_seed_phrase,
32
+ validate_seed_phrase,
33
+ format_token_amount,
34
+ )
35
+ from .types import (
36
+ # Core types
37
+ WDKConfig,
38
+ ChainConfig,
39
+ NetworkType,
40
+ # Token types
41
+ TokenInfo,
42
+ TokenBalance,
43
+ ChainBalance,
44
+ AggregatedBalance,
45
+ # Payment types
46
+ PaymentParams,
47
+ PaymentResult,
48
+ SignedTypedData,
49
+ TypedDataDomain,
50
+ # Bridge types
51
+ BridgeParams,
52
+ BridgeResult,
53
+ )
54
+ from .chains import (
55
+ # Chain configuration
56
+ DEFAULT_CHAINS,
57
+ CHAIN_TOKENS,
58
+ # Token addresses
59
+ USDT0_ADDRESSES,
60
+ USDC_ADDRESSES,
61
+ USDT_LEGACY_ADDRESSES,
62
+ # Utility functions
63
+ get_chain_config,
64
+ get_chain_id,
65
+ get_network_from_chain,
66
+ get_chain_from_network,
67
+ get_usdt0_chains,
68
+ get_chain_tokens,
69
+ get_preferred_token,
70
+ get_token_address,
71
+ is_testnet,
72
+ )
73
+ from .errors import (
74
+ # Error classes
75
+ WDKError,
76
+ WDKInitializationError,
77
+ SignerError,
78
+ SigningError,
79
+ ChainError,
80
+ BalanceError,
81
+ TransactionError,
82
+ BridgeError,
83
+ # Error codes
84
+ WDKErrorCode,
85
+ # Utilities
86
+ is_wdk_error,
87
+ )
88
+
89
+
90
+ __all__ = [
91
+ # Signer
92
+ "WDKSigner",
93
+ "generate_seed_phrase",
94
+ "validate_seed_phrase",
95
+ "format_token_amount",
96
+ # Core types
97
+ "WDKConfig",
98
+ "ChainConfig",
99
+ "NetworkType",
100
+ # Token types
101
+ "TokenInfo",
102
+ "TokenBalance",
103
+ "ChainBalance",
104
+ "AggregatedBalance",
105
+ # Payment types
106
+ "PaymentParams",
107
+ "PaymentResult",
108
+ "SignedTypedData",
109
+ "TypedDataDomain",
110
+ # Bridge types
111
+ "BridgeParams",
112
+ "BridgeResult",
113
+ # Chain configuration
114
+ "DEFAULT_CHAINS",
115
+ "CHAIN_TOKENS",
116
+ "USDT0_ADDRESSES",
117
+ "USDC_ADDRESSES",
118
+ "USDT_LEGACY_ADDRESSES",
119
+ # Chain utilities
120
+ "get_chain_config",
121
+ "get_chain_id",
122
+ "get_network_from_chain",
123
+ "get_chain_from_network",
124
+ "get_usdt0_chains",
125
+ "get_chain_tokens",
126
+ "get_preferred_token",
127
+ "get_token_address",
128
+ "is_testnet",
129
+ # Error classes
130
+ "WDKError",
131
+ "WDKInitializationError",
132
+ "SignerError",
133
+ "SigningError",
134
+ "ChainError",
135
+ "BalanceError",
136
+ "TransactionError",
137
+ "BridgeError",
138
+ "WDKErrorCode",
139
+ "is_wdk_error",
140
+ ]