t402 1.7.1__tar.gz → 1.9.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 (98) hide show
  1. {t402-1.7.1 → t402-1.9.0}/.gitignore +1 -0
  2. {t402-1.7.1 → t402-1.9.0}/PKG-INFO +1 -1
  3. {t402-1.7.1 → t402-1.9.0}/pyproject.toml +1 -1
  4. {t402-1.7.1 → t402-1.9.0}/src/t402/__init__.py +1 -1
  5. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/__init__.py +40 -1
  6. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/evm/__init__.py +21 -0
  7. t402-1.9.0/src/t402/schemes/evm/upto/__init__.py +58 -0
  8. t402-1.9.0/src/t402/schemes/evm/upto/client.py +240 -0
  9. t402-1.9.0/src/t402/schemes/evm/upto/types.py +305 -0
  10. t402-1.9.0/src/t402/schemes/upto/__init__.py +80 -0
  11. t402-1.9.0/src/t402/schemes/upto/types.py +376 -0
  12. {t402-1.7.1 → t402-1.9.0}/src/t402/types.py +1 -1
  13. t402-1.9.0/tests/test_upto.py +325 -0
  14. t402-1.9.0/tests/test_upto_evm.py +297 -0
  15. {t402-1.7.1 → t402-1.9.0}/.python-version +0 -0
  16. {t402-1.7.1 → t402-1.9.0}/README.md +0 -0
  17. {t402-1.7.1 → t402-1.9.0}/src/t402/bridge/__init__.py +0 -0
  18. {t402-1.7.1 → t402-1.9.0}/src/t402/bridge/client.py +0 -0
  19. {t402-1.7.1 → t402-1.9.0}/src/t402/bridge/constants.py +0 -0
  20. {t402-1.7.1 → t402-1.9.0}/src/t402/bridge/router.py +0 -0
  21. {t402-1.7.1 → t402-1.9.0}/src/t402/bridge/scan.py +0 -0
  22. {t402-1.7.1 → t402-1.9.0}/src/t402/bridge/types.py +0 -0
  23. {t402-1.7.1 → t402-1.9.0}/src/t402/chains.py +0 -0
  24. {t402-1.7.1 → t402-1.9.0}/src/t402/cli.py +0 -0
  25. {t402-1.7.1 → t402-1.9.0}/src/t402/clients/__init__.py +0 -0
  26. {t402-1.7.1 → t402-1.9.0}/src/t402/clients/base.py +0 -0
  27. {t402-1.7.1 → t402-1.9.0}/src/t402/clients/httpx.py +0 -0
  28. {t402-1.7.1 → t402-1.9.0}/src/t402/clients/requests.py +0 -0
  29. {t402-1.7.1 → t402-1.9.0}/src/t402/common.py +0 -0
  30. {t402-1.7.1 → t402-1.9.0}/src/t402/encoding.py +0 -0
  31. {t402-1.7.1 → t402-1.9.0}/src/t402/erc4337/__init__.py +0 -0
  32. {t402-1.7.1 → t402-1.9.0}/src/t402/erc4337/accounts.py +0 -0
  33. {t402-1.7.1 → t402-1.9.0}/src/t402/erc4337/bundlers.py +0 -0
  34. {t402-1.7.1 → t402-1.9.0}/src/t402/erc4337/paymasters.py +0 -0
  35. {t402-1.7.1 → t402-1.9.0}/src/t402/erc4337/types.py +0 -0
  36. {t402-1.7.1 → t402-1.9.0}/src/t402/evm_paywall_template.py +0 -0
  37. {t402-1.7.1 → t402-1.9.0}/src/t402/exact.py +0 -0
  38. {t402-1.7.1 → t402-1.9.0}/src/t402/facilitator.py +0 -0
  39. {t402-1.7.1 → t402-1.9.0}/src/t402/fastapi/__init__.py +0 -0
  40. {t402-1.7.1 → t402-1.9.0}/src/t402/fastapi/dependencies.py +0 -0
  41. {t402-1.7.1 → t402-1.9.0}/src/t402/fastapi/middleware.py +0 -0
  42. {t402-1.7.1 → t402-1.9.0}/src/t402/flask/__init__.py +0 -0
  43. {t402-1.7.1 → t402-1.9.0}/src/t402/flask/middleware.py +0 -0
  44. {t402-1.7.1 → t402-1.9.0}/src/t402/mcp/__init__.py +0 -0
  45. {t402-1.7.1 → t402-1.9.0}/src/t402/mcp/constants.py +0 -0
  46. {t402-1.7.1 → t402-1.9.0}/src/t402/mcp/server.py +0 -0
  47. {t402-1.7.1 → t402-1.9.0}/src/t402/mcp/tools.py +0 -0
  48. {t402-1.7.1 → t402-1.9.0}/src/t402/mcp/types.py +0 -0
  49. {t402-1.7.1 → t402-1.9.0}/src/t402/networks.py +0 -0
  50. {t402-1.7.1 → t402-1.9.0}/src/t402/path.py +0 -0
  51. {t402-1.7.1 → t402-1.9.0}/src/t402/paywall.py +0 -0
  52. {t402-1.7.1 → t402-1.9.0}/src/t402/py.typed +0 -0
  53. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/evm/exact/__init__.py +0 -0
  54. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/evm/exact/client.py +0 -0
  55. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/evm/exact/server.py +0 -0
  56. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/interfaces.py +0 -0
  57. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/registry.py +0 -0
  58. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/ton/__init__.py +0 -0
  59. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/ton/exact/__init__.py +0 -0
  60. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/ton/exact/client.py +0 -0
  61. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/ton/exact/server.py +0 -0
  62. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/tron/__init__.py +0 -0
  63. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/tron/exact/__init__.py +0 -0
  64. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/tron/exact/client.py +0 -0
  65. {t402-1.7.1 → t402-1.9.0}/src/t402/schemes/tron/exact/server.py +0 -0
  66. {t402-1.7.1 → t402-1.9.0}/src/t402/svm.py +0 -0
  67. {t402-1.7.1 → t402-1.9.0}/src/t402/svm_paywall_template.py +0 -0
  68. {t402-1.7.1 → t402-1.9.0}/src/t402/ton.py +0 -0
  69. {t402-1.7.1 → t402-1.9.0}/src/t402/ton_paywall_template.py +0 -0
  70. {t402-1.7.1 → t402-1.9.0}/src/t402/tron.py +0 -0
  71. {t402-1.7.1 → t402-1.9.0}/src/t402/wdk/__init__.py +0 -0
  72. {t402-1.7.1 → t402-1.9.0}/src/t402/wdk/chains.py +0 -0
  73. {t402-1.7.1 → t402-1.9.0}/src/t402/wdk/errors.py +0 -0
  74. {t402-1.7.1 → t402-1.9.0}/src/t402/wdk/signer.py +0 -0
  75. {t402-1.7.1 → t402-1.9.0}/src/t402/wdk/types.py +0 -0
  76. {t402-1.7.1 → t402-1.9.0}/tests/clients/__init__.py +0 -0
  77. {t402-1.7.1 → t402-1.9.0}/tests/clients/test_base.py +0 -0
  78. {t402-1.7.1 → t402-1.9.0}/tests/clients/test_httpx.py +0 -0
  79. {t402-1.7.1 → t402-1.9.0}/tests/clients/test_requests.py +0 -0
  80. {t402-1.7.1 → t402-1.9.0}/tests/fastapi_tests/__init__.py +0 -0
  81. {t402-1.7.1 → t402-1.9.0}/tests/fastapi_tests/test_middleware.py +0 -0
  82. {t402-1.7.1 → t402-1.9.0}/tests/flask_tests/__init__.py +0 -0
  83. {t402-1.7.1 → t402-1.9.0}/tests/flask_tests/test_middleware.py +0 -0
  84. {t402-1.7.1 → t402-1.9.0}/tests/test_bridge.py +0 -0
  85. {t402-1.7.1 → t402-1.9.0}/tests/test_common.py +0 -0
  86. {t402-1.7.1 → t402-1.9.0}/tests/test_encoding.py +0 -0
  87. {t402-1.7.1 → t402-1.9.0}/tests/test_exact.py +0 -0
  88. {t402-1.7.1 → t402-1.9.0}/tests/test_fastapi.py +0 -0
  89. {t402-1.7.1 → t402-1.9.0}/tests/test_mcp.py +0 -0
  90. {t402-1.7.1 → t402-1.9.0}/tests/test_paywall.py +0 -0
  91. {t402-1.7.1 → t402-1.9.0}/tests/test_schemes.py +0 -0
  92. {t402-1.7.1 → t402-1.9.0}/tests/test_svm.py +0 -0
  93. {t402-1.7.1 → t402-1.9.0}/tests/test_ton.py +0 -0
  94. {t402-1.7.1 → t402-1.9.0}/tests/test_tron.py +0 -0
  95. {t402-1.7.1 → t402-1.9.0}/tests/test_types.py +0 -0
  96. {t402-1.7.1 → t402-1.9.0}/tests/test_v2_types.py +0 -0
  97. {t402-1.7.1 → t402-1.9.0}/tests/test_wdk.py +0 -0
  98. {t402-1.7.1 → t402-1.9.0}/uv.lock +0 -0
@@ -12,3 +12,4 @@ e2e/facilitators/external-proxies/*
12
12
  !e2e/facilitators/external-proxies/README.md
13
13
  .vercel
14
14
  .env*.local
15
+ api-docs/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: t402
3
- Version: 1.7.1
3
+ Version: 1.9.0
4
4
  Summary: t402: An internet native payments protocol
5
5
  Author-email: T402 Team <dev@t402.io>
6
6
  License: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "t402"
3
- version = "1.7.1"
3
+ version = "1.9.0"
4
4
  description = "t402: An internet native payments protocol"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -1,5 +1,5 @@
1
1
  # Package version
2
- __version__ = "1.6.2"
2
+ __version__ = "1.7.1"
3
3
 
4
4
  # Re-export commonly used items for convenience
5
5
  from t402.common import (
@@ -71,6 +71,27 @@ from t402.schemes.evm import (
71
71
  ExactEvmClientScheme,
72
72
  ExactEvmServerScheme,
73
73
  EvmSigner,
74
+ # Upto EVM
75
+ UptoEvmClientScheme,
76
+ PermitSignature,
77
+ PermitAuthorization,
78
+ UptoEIP2612Payload,
79
+ UptoEvmExtra,
80
+ )
81
+
82
+ # Upto Core Types
83
+ from t402.schemes.upto import (
84
+ SCHEME_UPTO,
85
+ UptoPaymentRequirements,
86
+ UptoExtra,
87
+ UptoSettlement,
88
+ UptoUsageDetails,
89
+ UptoSettlementResponse,
90
+ UptoValidationResult,
91
+ is_upto_payment_requirements,
92
+ is_valid_unit,
93
+ create_payment_requirements as create_upto_requirements,
94
+ create_settlement as create_upto_settlement,
74
95
  )
75
96
 
76
97
  # TON Schemes
@@ -110,10 +131,28 @@ __all__ = [
110
131
  "get_server_registry",
111
132
  "get_facilitator_registry",
112
133
  "reset_global_registries",
113
- # EVM Schemes
134
+ # EVM Exact Schemes
114
135
  "ExactEvmClientScheme",
115
136
  "ExactEvmServerScheme",
116
137
  "EvmSigner",
138
+ # EVM Upto Schemes
139
+ "UptoEvmClientScheme",
140
+ "PermitSignature",
141
+ "PermitAuthorization",
142
+ "UptoEIP2612Payload",
143
+ "UptoEvmExtra",
144
+ # Upto Core Types
145
+ "SCHEME_UPTO",
146
+ "UptoPaymentRequirements",
147
+ "UptoExtra",
148
+ "UptoSettlement",
149
+ "UptoUsageDetails",
150
+ "UptoSettlementResponse",
151
+ "UptoValidationResult",
152
+ "is_upto_payment_requirements",
153
+ "is_valid_unit",
154
+ "create_upto_requirements",
155
+ "create_upto_settlement",
117
156
  # TON Schemes
118
157
  "ExactTonClientScheme",
119
158
  "ExactTonServerScheme",
@@ -5,6 +5,7 @@ blockchains (Ethereum, Base, Avalanche, etc.).
5
5
 
6
6
  Supported schemes:
7
7
  - exact: EIP-3009 TransferWithAuthorization
8
+ - upto: EIP-2612 Permit (usage-based billing)
8
9
  """
9
10
 
10
11
  from t402.schemes.evm.exact import (
@@ -15,6 +16,17 @@ from t402.schemes.evm.exact import (
15
16
  SCHEME_EXACT,
16
17
  )
17
18
 
19
+ from t402.schemes.evm.upto import (
20
+ UptoEvmClientScheme,
21
+ create_payment_nonce,
22
+ SCHEME_UPTO,
23
+ PermitSignature,
24
+ PermitAuthorization,
25
+ UptoEIP2612Payload,
26
+ UptoEvmExtra,
27
+ is_eip2612_payload,
28
+ )
29
+
18
30
  __all__ = [
19
31
  # Exact scheme
20
32
  "ExactEvmClientScheme",
@@ -22,4 +34,13 @@ __all__ = [
22
34
  "EvmSigner",
23
35
  "create_nonce",
24
36
  "SCHEME_EXACT",
37
+ # Upto scheme
38
+ "UptoEvmClientScheme",
39
+ "create_payment_nonce",
40
+ "SCHEME_UPTO",
41
+ "PermitSignature",
42
+ "PermitAuthorization",
43
+ "UptoEIP2612Payload",
44
+ "UptoEvmExtra",
45
+ "is_eip2612_payload",
25
46
  ]
@@ -0,0 +1,58 @@
1
+ """EVM Up-To Payment Scheme.
2
+
3
+ This package provides the upto payment scheme implementation for EVM networks
4
+ using EIP-2612 Permit for gasless token approvals.
5
+
6
+ The upto scheme allows clients to authorize a maximum amount that can be
7
+ settled later based on actual usage.
8
+ """
9
+
10
+ from t402.schemes.evm.upto.types import (
11
+ # Type definitions
12
+ PERMIT_TYPES,
13
+ PERMIT_DOMAIN_TYPES,
14
+ # Models
15
+ PermitSignature,
16
+ PermitAuthorization,
17
+ UptoEIP2612Payload,
18
+ UptoCompactPayload,
19
+ UptoEvmExtra,
20
+ UptoEvmSettlement,
21
+ UptoEvmUsageDetails,
22
+ # Type guards
23
+ is_eip2612_payload,
24
+ # Helper functions
25
+ create_permit_domain,
26
+ create_permit_message,
27
+ payload_from_dict,
28
+ )
29
+
30
+ from t402.schemes.evm.upto.client import (
31
+ UptoEvmClientScheme,
32
+ create_payment_nonce,
33
+ SCHEME_UPTO,
34
+ )
35
+
36
+ __all__ = [
37
+ # Constants
38
+ "SCHEME_UPTO",
39
+ "PERMIT_TYPES",
40
+ "PERMIT_DOMAIN_TYPES",
41
+ # Client
42
+ "UptoEvmClientScheme",
43
+ "create_payment_nonce",
44
+ # Types
45
+ "PermitSignature",
46
+ "PermitAuthorization",
47
+ "UptoEIP2612Payload",
48
+ "UptoCompactPayload",
49
+ "UptoEvmExtra",
50
+ "UptoEvmSettlement",
51
+ "UptoEvmUsageDetails",
52
+ # Type guards
53
+ "is_eip2612_payload",
54
+ # Helper functions
55
+ "create_permit_domain",
56
+ "create_permit_message",
57
+ "payload_from_dict",
58
+ ]
@@ -0,0 +1,240 @@
1
+ """EVM Up-To Scheme - Client Implementation.
2
+
3
+ This module provides the client-side implementation of the upto payment scheme
4
+ for EVM networks using EIP-2612 Permit.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import secrets
10
+ import time
11
+ from typing import Any, Dict, Optional, Union
12
+
13
+ from t402.types import PaymentRequirementsV2
14
+ from t402.chains import get_chain_id
15
+ from t402.schemes.evm.exact.client import EvmSigner
16
+ from t402.schemes.upto.types import UptoPaymentRequirements
17
+
18
+
19
+ # Constants
20
+ SCHEME_UPTO = "upto"
21
+
22
+
23
+ def create_payment_nonce() -> bytes:
24
+ """Create a random 32-byte payment nonce."""
25
+ return secrets.token_bytes(32)
26
+
27
+
28
+ class UptoEvmClientScheme:
29
+ """Client scheme for EVM upto payments using EIP-2612.
30
+
31
+ This scheme creates payment payloads using EIP-2612 Permit,
32
+ which allows gasless token approvals for up-to payments.
33
+
34
+ Example:
35
+ ```python
36
+ from eth_account import Account
37
+
38
+ # Create signer from private key
39
+ account = Account.from_key("0x...")
40
+
41
+ # Create scheme
42
+ scheme = UptoEvmClientScheme(account)
43
+
44
+ # Create payment payload
45
+ payload = await scheme.create_payment_payload(
46
+ t402_version=2,
47
+ requirements=requirements,
48
+ )
49
+ ```
50
+ """
51
+
52
+ scheme = SCHEME_UPTO
53
+ caip_family = "eip155:*"
54
+
55
+ def __init__(
56
+ self,
57
+ signer: EvmSigner,
58
+ router_address: Optional[str] = None,
59
+ ):
60
+ """Initialize with an EVM signer.
61
+
62
+ Args:
63
+ signer: Any object implementing the EvmSigner protocol
64
+ router_address: Optional default router contract address
65
+ """
66
+ self._signer = signer
67
+ self._router_address = router_address
68
+
69
+ @property
70
+ def address(self) -> str:
71
+ """Get the signer's address."""
72
+ return self._signer.address
73
+
74
+ async def create_payment_payload(
75
+ self,
76
+ t402_version: int,
77
+ requirements: Union[UptoPaymentRequirements, PaymentRequirementsV2, Dict[str, Any]],
78
+ ) -> Dict[str, Any]:
79
+ """Create a payment payload for EVM upto scheme.
80
+
81
+ Creates an EIP-2612 Permit authorization and signs it.
82
+
83
+ Args:
84
+ t402_version: Protocol version (1 or 2)
85
+ requirements: Payment requirements with maxAmount, asset, payTo, etc.
86
+
87
+ Returns:
88
+ Dict with t402Version and payload containing permit signature
89
+ and authorization data.
90
+ """
91
+ # Extract requirements (handle both model and dict)
92
+ if hasattr(requirements, "model_dump"):
93
+ req = requirements.model_dump(by_alias=True)
94
+ else:
95
+ req = dict(requirements)
96
+
97
+ # Get network and chain ID
98
+ network = req.get("network", "")
99
+ chain_id = self._get_chain_id(network)
100
+
101
+ # Get maxAmount for upto scheme
102
+ max_amount = req.get("maxAmount") or req.get("max_amount", "0")
103
+
104
+ # Get router/spender address
105
+ extra = req.get("extra", {})
106
+ router_address = (
107
+ extra.get("routerAddress")
108
+ or extra.get("router_address")
109
+ or self._router_address
110
+ or req.get("payTo") # Fallback to payTo
111
+ )
112
+
113
+ # Get asset address
114
+ asset = req.get("asset", "")
115
+
116
+ # Get timeout
117
+ max_timeout = req.get("maxTimeoutSeconds") or req.get("max_timeout_seconds", 300)
118
+
119
+ # Get EIP-712 domain info
120
+ token_name = extra.get("name", "USD Coin")
121
+ token_version = extra.get("version", "2")
122
+
123
+ # Create payment nonce
124
+ payment_nonce = create_payment_nonce()
125
+
126
+ # Calculate deadline
127
+ deadline = int(time.time()) + max_timeout
128
+
129
+ # Get permit nonce from token contract (would need RPC call in production)
130
+ # For now, use 0 as placeholder - real implementation needs contract call
131
+ permit_nonce = 0
132
+
133
+ # Create authorization
134
+ authorization = {
135
+ "owner": self._signer.address,
136
+ "spender": router_address,
137
+ "value": str(max_amount),
138
+ "deadline": str(deadline),
139
+ "nonce": permit_nonce,
140
+ }
141
+
142
+ # Sign the permit
143
+ signature = self._sign_permit(
144
+ authorization=authorization,
145
+ chain_id=chain_id,
146
+ asset_address=asset,
147
+ token_name=token_name,
148
+ token_version=token_version,
149
+ )
150
+
151
+ # Build payload
152
+ payload = {
153
+ "signature": signature,
154
+ "authorization": authorization,
155
+ "paymentNonce": f"0x{payment_nonce.hex()}",
156
+ }
157
+
158
+ return {
159
+ "t402Version": t402_version,
160
+ "payload": payload,
161
+ }
162
+
163
+ def _get_chain_id(self, network: str) -> int:
164
+ """Get chain ID from network identifier."""
165
+ if network.startswith("eip155:"):
166
+ return int(network.split(":")[1])
167
+
168
+ try:
169
+ return get_chain_id(network)
170
+ except (KeyError, ValueError):
171
+ raise ValueError(f"Unknown network: {network}")
172
+
173
+ def _sign_permit(
174
+ self,
175
+ authorization: Dict[str, Any],
176
+ chain_id: int,
177
+ asset_address: str,
178
+ token_name: str,
179
+ token_version: str,
180
+ ) -> Dict[str, Any]:
181
+ """Sign an EIP-2612 Permit.
182
+
183
+ Args:
184
+ authorization: Permit authorization data
185
+ chain_id: EVM chain ID
186
+ asset_address: Token contract address
187
+ token_name: Token name for EIP-712 domain
188
+ token_version: Token version for EIP-712 domain
189
+
190
+ Returns:
191
+ Signature as dict with v, r, s components
192
+ """
193
+ # Build EIP-712 typed data
194
+ domain = {
195
+ "name": token_name,
196
+ "version": token_version,
197
+ "chainId": chain_id,
198
+ "verifyingContract": asset_address,
199
+ }
200
+
201
+ types = {
202
+ "Permit": [
203
+ {"name": "owner", "type": "address"},
204
+ {"name": "spender", "type": "address"},
205
+ {"name": "value", "type": "uint256"},
206
+ {"name": "nonce", "type": "uint256"},
207
+ {"name": "deadline", "type": "uint256"},
208
+ ]
209
+ }
210
+
211
+ message = {
212
+ "owner": authorization["owner"],
213
+ "spender": authorization["spender"],
214
+ "value": int(authorization["value"]),
215
+ "nonce": authorization["nonce"],
216
+ "deadline": int(authorization["deadline"]),
217
+ }
218
+
219
+ # Sign
220
+ signed = self._signer.sign_typed_data(
221
+ domain_data=domain,
222
+ message_types=types,
223
+ message_data=message,
224
+ )
225
+
226
+ # Extract signature components
227
+ sig_hex = signed.signature.hex()
228
+ if sig_hex.startswith("0x"):
229
+ sig_hex = sig_hex[2:]
230
+
231
+ # Split into v, r, s
232
+ r = f"0x{sig_hex[:64]}"
233
+ s = f"0x{sig_hex[64:128]}"
234
+ v = int(sig_hex[128:], 16) if len(sig_hex) > 128 else 27
235
+
236
+ return {
237
+ "v": v,
238
+ "r": r,
239
+ "s": s,
240
+ }