t402 1.9.0__py3-none-any.whl → 1.9.1__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 (100) hide show
  1. t402/__init__.py +2 -1
  2. t402/bridge/client.py +13 -5
  3. t402/bridge/constants.py +3 -1
  4. t402/bridge/router.py +1 -1
  5. t402/bridge/scan.py +3 -1
  6. t402/chains.py +268 -1
  7. t402/cli.py +31 -9
  8. t402/common.py +2 -0
  9. t402/cosmos_paywall_template.py +2 -0
  10. t402/encoding.py +9 -3
  11. t402/erc4337/accounts.py +56 -51
  12. t402/erc4337/bundlers.py +105 -99
  13. t402/erc4337/paymasters.py +100 -109
  14. t402/erc4337/types.py +39 -26
  15. t402/evm_paywall_template.py +1 -1
  16. t402/fastapi/middleware.py +1 -3
  17. t402/mcp/server.py +79 -46
  18. t402/near_paywall_template.py +2 -0
  19. t402/networks.py +34 -1
  20. t402/paywall.py +1 -3
  21. t402/schemes/__init__.py +124 -0
  22. t402/schemes/aptos/__init__.py +70 -0
  23. t402/schemes/aptos/constants.py +349 -0
  24. t402/schemes/aptos/exact_direct/__init__.py +44 -0
  25. t402/schemes/aptos/exact_direct/client.py +202 -0
  26. t402/schemes/aptos/exact_direct/facilitator.py +426 -0
  27. t402/schemes/aptos/exact_direct/server.py +272 -0
  28. t402/schemes/aptos/types.py +237 -0
  29. t402/schemes/evm/__init__.py +46 -1
  30. t402/schemes/evm/exact/__init__.py +11 -0
  31. t402/schemes/evm/exact/client.py +3 -1
  32. t402/schemes/evm/exact/facilitator.py +894 -0
  33. t402/schemes/evm/exact/server.py +1 -1
  34. t402/schemes/evm/exact_legacy/__init__.py +38 -0
  35. t402/schemes/evm/exact_legacy/client.py +291 -0
  36. t402/schemes/evm/exact_legacy/facilitator.py +777 -0
  37. t402/schemes/evm/exact_legacy/server.py +231 -0
  38. t402/schemes/evm/upto/__init__.py +12 -0
  39. t402/schemes/evm/upto/client.py +6 -2
  40. t402/schemes/evm/upto/facilitator.py +625 -0
  41. t402/schemes/evm/upto/server.py +243 -0
  42. t402/schemes/evm/upto/types.py +3 -1
  43. t402/schemes/interfaces.py +6 -2
  44. t402/schemes/near/__init__.py +112 -0
  45. t402/schemes/near/constants.py +189 -0
  46. t402/schemes/near/exact_direct/__init__.py +21 -0
  47. t402/schemes/near/exact_direct/client.py +204 -0
  48. t402/schemes/near/exact_direct/facilitator.py +455 -0
  49. t402/schemes/near/exact_direct/server.py +303 -0
  50. t402/schemes/near/types.py +419 -0
  51. t402/schemes/polkadot/__init__.py +72 -0
  52. t402/schemes/polkadot/constants.py +155 -0
  53. t402/schemes/polkadot/exact_direct/__init__.py +43 -0
  54. t402/schemes/polkadot/exact_direct/client.py +235 -0
  55. t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
  56. t402/schemes/polkadot/exact_direct/server.py +292 -0
  57. t402/schemes/polkadot/types.py +385 -0
  58. t402/schemes/registry.py +6 -2
  59. t402/schemes/stacks/__init__.py +68 -0
  60. t402/schemes/stacks/constants.py +122 -0
  61. t402/schemes/stacks/exact_direct/__init__.py +43 -0
  62. t402/schemes/stacks/exact_direct/client.py +222 -0
  63. t402/schemes/stacks/exact_direct/facilitator.py +424 -0
  64. t402/schemes/stacks/exact_direct/server.py +292 -0
  65. t402/schemes/stacks/types.py +380 -0
  66. t402/schemes/svm/__init__.py +29 -0
  67. t402/schemes/svm/exact/__init__.py +35 -0
  68. t402/schemes/svm/exact/client.py +23 -0
  69. t402/schemes/svm/exact/facilitator.py +24 -0
  70. t402/schemes/svm/exact/server.py +20 -0
  71. t402/schemes/tezos/__init__.py +84 -0
  72. t402/schemes/tezos/constants.py +372 -0
  73. t402/schemes/tezos/exact_direct/__init__.py +22 -0
  74. t402/schemes/tezos/exact_direct/client.py +226 -0
  75. t402/schemes/tezos/exact_direct/facilitator.py +491 -0
  76. t402/schemes/tezos/exact_direct/server.py +277 -0
  77. t402/schemes/tezos/types.py +220 -0
  78. t402/schemes/ton/__init__.py +9 -2
  79. t402/schemes/ton/exact/__init__.py +7 -0
  80. t402/schemes/ton/exact/facilitator.py +730 -0
  81. t402/schemes/ton/exact/server.py +1 -1
  82. t402/schemes/tron/__init__.py +11 -2
  83. t402/schemes/tron/exact/__init__.py +9 -0
  84. t402/schemes/tron/exact/facilitator.py +673 -0
  85. t402/schemes/tron/exact/server.py +1 -1
  86. t402/stacks_paywall_template.py +2 -0
  87. t402/svm.py +45 -11
  88. t402/svm_paywall_template.py +1 -1
  89. t402/ton.py +5 -1
  90. t402/ton_paywall_template.py +1 -192
  91. t402/tron.py +2 -0
  92. t402/tron_paywall_template.py +2 -0
  93. t402/types.py +3 -1
  94. t402/wdk/errors.py +15 -5
  95. t402/wdk/signer.py +11 -2
  96. {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
  97. t402-1.9.1.dist-info/RECORD +125 -0
  98. t402-1.9.0.dist-info/RECORD +0 -72
  99. {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
  100. {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
@@ -97,7 +97,7 @@ class ExactEvmServerScheme:
97
97
  amount_decimal = Decimal(str(price))
98
98
 
99
99
  # Convert to atomic units
100
- atomic_amount = int(amount_decimal * Decimal(10 ** decimals))
100
+ atomic_amount = int(amount_decimal * Decimal(10**decimals))
101
101
 
102
102
  # Get EIP-712 domain info
103
103
  extra = {
@@ -0,0 +1,38 @@
1
+ """EVM Exact-Legacy Payment Scheme.
2
+
3
+ This package provides the exact-legacy payment scheme implementation for EVM networks
4
+ using the approve + transferFrom pattern for legacy tokens without EIP-3009 support.
5
+
6
+ The exact-legacy scheme requires users to approve a facilitator address to spend
7
+ their tokens, then sign an authorization that includes timing constraints.
8
+ """
9
+
10
+ from t402.schemes.evm.exact_legacy.client import (
11
+ ExactLegacyEvmClientScheme,
12
+ create_nonce,
13
+ SCHEME_EXACT_LEGACY,
14
+ )
15
+ from t402.schemes.evm.exact_legacy.server import (
16
+ ExactLegacyEvmServerScheme,
17
+ )
18
+ from t402.schemes.evm.exact_legacy.facilitator import (
19
+ ExactLegacyEvmFacilitatorScheme,
20
+ FacilitatorLegacyEvmSigner,
21
+ LegacyVerifyResult,
22
+ LegacyTransactionConfirmation,
23
+ )
24
+
25
+ __all__ = [
26
+ # Client
27
+ "ExactLegacyEvmClientScheme",
28
+ "create_nonce",
29
+ # Server
30
+ "ExactLegacyEvmServerScheme",
31
+ # Facilitator
32
+ "ExactLegacyEvmFacilitatorScheme",
33
+ "FacilitatorLegacyEvmSigner",
34
+ "LegacyVerifyResult",
35
+ "LegacyTransactionConfirmation",
36
+ # Constants
37
+ "SCHEME_EXACT_LEGACY",
38
+ ]
@@ -0,0 +1,291 @@
1
+ """EVM Exact-Legacy Scheme - Client Implementation.
2
+
3
+ This module provides the client-side implementation of the exact-legacy payment scheme
4
+ for EVM networks using the approve + transferFrom pattern.
5
+
6
+ This scheme is used for legacy USDT and other tokens without EIP-3009 support.
7
+
8
+ .. deprecated:: 2.3.0
9
+ The exact-legacy scheme is deprecated in favor of using USDT0 with the "exact" scheme.
10
+ USDT0 supports EIP-3009 for gasless transfers on 19+ chains via LayerZero.
11
+
12
+ **Migration:**
13
+ Replace `ExactLegacyEvmClientScheme` with `ExactEvmClientScheme` and use USDT0 tokens.
14
+
15
+ See server.py docstring for full deprecation details and migration guide.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import secrets
21
+ import time
22
+ from typing import Any, Dict, Protocol, Union, runtime_checkable
23
+
24
+ from t402.types import (
25
+ PaymentRequirementsV2,
26
+ T402_VERSION_V1,
27
+ )
28
+ from t402.chains import get_chain_id
29
+
30
+
31
+ # Constants
32
+ SCHEME_EXACT_LEGACY = "exact-legacy"
33
+
34
+
35
+ @runtime_checkable
36
+ class EvmSigner(Protocol):
37
+ """Protocol for EVM signing operations."""
38
+
39
+ @property
40
+ def address(self) -> str:
41
+ """Get the signer's address."""
42
+ ...
43
+
44
+ def sign_typed_data(
45
+ self,
46
+ domain_data: Dict[str, Any],
47
+ message_types: Dict[str, Any],
48
+ message_data: Dict[str, Any],
49
+ ) -> Any:
50
+ """Sign EIP-712 typed data."""
51
+ ...
52
+
53
+
54
+ def create_nonce() -> bytes:
55
+ """Create a random 32-byte nonce for authorization signatures."""
56
+ return secrets.token_bytes(32)
57
+
58
+
59
+ class ExactLegacyEvmClientScheme:
60
+ """Client scheme for EVM exact-legacy payments.
61
+
62
+ This scheme creates payment payloads for legacy tokens that don't support
63
+ EIP-3009. It requires the user to:
64
+ 1. Approve the facilitator to spend their tokens
65
+ 2. Sign an authorization for the payment
66
+
67
+ Example:
68
+ ```python
69
+ from eth_account import Account
70
+
71
+ # Create signer from private key
72
+ account = Account.from_key("0x...")
73
+
74
+ # Create scheme
75
+ scheme = ExactLegacyEvmClientScheme(account)
76
+
77
+ # Create payment payload
78
+ payload = await scheme.create_payment_payload(
79
+ t402_version=2,
80
+ requirements=requirements,
81
+ )
82
+ ```
83
+ """
84
+
85
+ scheme = SCHEME_EXACT_LEGACY
86
+ caip_family = "eip155:*"
87
+
88
+ def __init__(self, signer: EvmSigner):
89
+ """Initialize with an EVM signer.
90
+
91
+ Args:
92
+ signer: Any object implementing the EvmSigner protocol
93
+ (e.g., eth_account.Account)
94
+ """
95
+ self._signer = signer
96
+
97
+ @property
98
+ def address(self) -> str:
99
+ """Get the signer's address."""
100
+ return self._signer.address
101
+
102
+ async def create_payment_payload(
103
+ self,
104
+ t402_version: int,
105
+ requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
106
+ ) -> Dict[str, Any]:
107
+ """Create a payment payload for EVM exact-legacy scheme.
108
+
109
+ Creates a LegacyTransferAuthorization and signs it.
110
+
111
+ Args:
112
+ t402_version: Protocol version (1 or 2)
113
+ requirements: Payment requirements with amount, asset, payTo, etc.
114
+ Must include 'extra.spender' with the facilitator address.
115
+
116
+ Returns:
117
+ Dict with t402Version, scheme, network, and payload containing
118
+ signature and authorization data.
119
+ """
120
+ # Extract requirements (handle both model and dict)
121
+ if hasattr(requirements, "model_dump"):
122
+ req = requirements.model_dump(by_alias=True)
123
+ else:
124
+ req = dict(requirements)
125
+
126
+ # Get network and chain ID
127
+ network = req.get("network", "")
128
+ chain_id = self._get_chain_id(network)
129
+
130
+ # Get amount - V2 uses 'amount', V1 uses 'maxAmountRequired'
131
+ amount = req.get("amount") or req.get("maxAmountRequired", "0")
132
+
133
+ # Get pay_to address
134
+ pay_to = req.get("payTo") or req.get("pay_to", "")
135
+
136
+ # Get asset address
137
+ asset = req.get("asset", "")
138
+
139
+ # Get timeout
140
+ max_timeout = req.get("maxTimeoutSeconds") or req.get(
141
+ "max_timeout_seconds", 300
142
+ )
143
+
144
+ # Get extra data
145
+ extra = req.get("extra", {})
146
+
147
+ # Get spender (facilitator address) - required for exact-legacy
148
+ spender = extra.get("spender", "")
149
+ if not spender:
150
+ raise ValueError("exact-legacy scheme requires 'extra.spender' to be set")
151
+
152
+ # Get EIP-712 domain info from extra
153
+ token_name = extra.get("name", "T402LegacyTransfer")
154
+ token_version = extra.get("version", "1")
155
+
156
+ # Create authorization
157
+ nonce = create_nonce()
158
+ now = int(time.time())
159
+ valid_after = str(now - 60) # 60 seconds buffer
160
+ valid_before = str(now + max_timeout)
161
+
162
+ authorization = {
163
+ "from": self._signer.address,
164
+ "to": pay_to,
165
+ "value": str(amount),
166
+ "validAfter": valid_after,
167
+ "validBefore": valid_before,
168
+ "nonce": nonce,
169
+ "spender": spender,
170
+ }
171
+
172
+ # Sign the authorization
173
+ signature = self._sign_authorization(
174
+ authorization=authorization,
175
+ chain_id=chain_id,
176
+ asset_address=asset,
177
+ token_name=token_name,
178
+ token_version=token_version,
179
+ )
180
+
181
+ # Convert nonce to hex string
182
+ authorization["nonce"] = f"0x{nonce.hex()}"
183
+
184
+ # Build payload based on version
185
+ if t402_version == T402_VERSION_V1:
186
+ return {
187
+ "t402Version": t402_version,
188
+ "scheme": self.scheme,
189
+ "network": network,
190
+ "payload": {
191
+ "signature": signature,
192
+ "authorization": authorization,
193
+ },
194
+ }
195
+ else:
196
+ # V2 - return just the partial payload
197
+ return {
198
+ "t402Version": t402_version,
199
+ "payload": {
200
+ "signature": signature,
201
+ "authorization": authorization,
202
+ },
203
+ }
204
+
205
+ def _get_chain_id(self, network: str) -> int:
206
+ """Get chain ID from network identifier.
207
+
208
+ Args:
209
+ network: Network identifier (CAIP-2 or legacy format)
210
+
211
+ Returns:
212
+ Chain ID as integer
213
+ """
214
+ # Handle CAIP-2 format (eip155:8453)
215
+ if network.startswith("eip155:"):
216
+ return int(network.split(":")[1])
217
+
218
+ # Handle legacy format
219
+ try:
220
+ return int(get_chain_id(network))
221
+ except (KeyError, ValueError):
222
+ raise ValueError(f"Unknown network: {network}")
223
+
224
+ def _sign_authorization(
225
+ self,
226
+ authorization: Dict[str, Any],
227
+ chain_id: int,
228
+ asset_address: str,
229
+ token_name: str,
230
+ token_version: str,
231
+ ) -> str:
232
+ """Sign a LegacyTransferAuthorization.
233
+
234
+ Args:
235
+ authorization: Authorization data including spender
236
+ chain_id: EVM chain ID
237
+ asset_address: Token contract address
238
+ token_name: Token name for EIP-712 domain
239
+ token_version: Token version for EIP-712 domain
240
+
241
+ Returns:
242
+ Signature as hex string
243
+ """
244
+ # Get nonce as bytes
245
+ nonce = authorization["nonce"]
246
+ if isinstance(nonce, str):
247
+ nonce = bytes.fromhex(nonce.replace("0x", ""))
248
+
249
+ # Build EIP-712 typed data
250
+ domain = {
251
+ "name": token_name,
252
+ "version": token_version,
253
+ "chainId": chain_id,
254
+ "verifyingContract": asset_address,
255
+ }
256
+
257
+ types = {
258
+ "LegacyTransferAuthorization": [
259
+ {"name": "from", "type": "address"},
260
+ {"name": "to", "type": "address"},
261
+ {"name": "value", "type": "uint256"},
262
+ {"name": "validAfter", "type": "uint256"},
263
+ {"name": "validBefore", "type": "uint256"},
264
+ {"name": "nonce", "type": "bytes32"},
265
+ {"name": "spender", "type": "address"},
266
+ ]
267
+ }
268
+
269
+ message = {
270
+ "from": authorization["from"],
271
+ "to": authorization["to"],
272
+ "value": int(authorization["value"]),
273
+ "validAfter": int(authorization["validAfter"]),
274
+ "validBefore": int(authorization["validBefore"]),
275
+ "nonce": nonce,
276
+ "spender": authorization["spender"],
277
+ }
278
+
279
+ # Sign
280
+ signed = self._signer.sign_typed_data(
281
+ domain_data=domain,
282
+ message_types=types,
283
+ message_data=message,
284
+ )
285
+
286
+ # Extract signature
287
+ signature = signed.signature.hex()
288
+ if not signature.startswith("0x"):
289
+ signature = f"0x{signature}"
290
+
291
+ return signature