t402 1.7.1__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 (102) 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 +164 -1
  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 +67 -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 +70 -0
  39. t402/schemes/evm/upto/client.py +244 -0
  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 +307 -0
  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/schemes/upto/__init__.py +80 -0
  87. t402/schemes/upto/types.py +376 -0
  88. t402/stacks_paywall_template.py +2 -0
  89. t402/svm.py +45 -11
  90. t402/svm_paywall_template.py +1 -1
  91. t402/ton.py +5 -1
  92. t402/ton_paywall_template.py +1 -192
  93. t402/tron.py +2 -0
  94. t402/tron_paywall_template.py +2 -0
  95. t402/types.py +4 -2
  96. t402/wdk/errors.py +15 -5
  97. t402/wdk/signer.py +11 -2
  98. {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
  99. t402-1.9.1.dist-info/RECORD +125 -0
  100. t402-1.7.1.dist-info/RECORD +0 -67
  101. {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
  102. {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,303 @@
1
+ """NEAR Exact-Direct Scheme - Server Implementation.
2
+
3
+ This module provides the server-side implementation of the exact-direct payment
4
+ scheme for NEAR networks.
5
+
6
+ The server:
7
+ 1. Parses user-friendly prices into atomic token amounts (6 decimals for USDT).
8
+ 2. Enhances payment requirements with the token contract address and metadata.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from decimal import Decimal, ROUND_DOWN
15
+ from typing import Any, Dict, List, Optional, Union
16
+
17
+ from t402.types import PaymentRequirementsV2, Network
18
+ from t402.schemes.interfaces import AssetAmount, SupportedKindDict
19
+ from t402.schemes.near.constants import (
20
+ SCHEME_EXACT_DIRECT,
21
+ CAIP_FAMILY,
22
+ TokenInfo,
23
+ get_network_config,
24
+ get_token_by_contract,
25
+ get_token_info,
26
+ is_valid_network,
27
+ )
28
+
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class ExactDirectNearServerConfig:
34
+ """Configuration for the ExactDirectNearServerScheme.
35
+
36
+ Attributes:
37
+ preferred_token: Preferred token symbol (e.g., "USDT").
38
+ Defaults to the network's default token if not set.
39
+ """
40
+
41
+ def __init__(self, preferred_token: Optional[str] = None) -> None:
42
+ self.preferred_token = preferred_token
43
+
44
+
45
+ class ExactDirectNearServerScheme:
46
+ """Server scheme for NEAR exact-direct payments.
47
+
48
+ Handles parsing user-friendly prices to atomic token amounts and enhancing
49
+ payment requirements with NEAR-specific metadata.
50
+
51
+ Example:
52
+ ```python
53
+ scheme = ExactDirectNearServerScheme()
54
+
55
+ # Parse a USD price to USDT atomic units
56
+ asset_amount = await scheme.parse_price("$1.50", "near:mainnet")
57
+ # Returns: {"amount": "1500000", "asset": "usdt.tether-token.near", "extra": {...}}
58
+
59
+ # Enhance requirements with token metadata
60
+ enhanced = await scheme.enhance_requirements(
61
+ requirements, supported_kind, extensions
62
+ )
63
+ ```
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ config: Optional[ExactDirectNearServerConfig] = None,
69
+ ) -> None:
70
+ """Initialize the server scheme.
71
+
72
+ Args:
73
+ config: Optional configuration. If not provided, defaults are used.
74
+ """
75
+ self._config = config or ExactDirectNearServerConfig()
76
+
77
+ @property
78
+ def scheme(self) -> str:
79
+ """The scheme identifier."""
80
+ return SCHEME_EXACT_DIRECT
81
+
82
+ @property
83
+ def caip_family(self) -> str:
84
+ """The CAIP-2 family pattern for NEAR networks."""
85
+ return CAIP_FAMILY
86
+
87
+ async def parse_price(
88
+ self,
89
+ price: Union[str, int, float, Dict[str, Any]],
90
+ network: Network,
91
+ ) -> AssetAmount:
92
+ """Parse a user-friendly price to atomic amount and asset.
93
+
94
+ Supports:
95
+ - String with $ prefix: "$1.50" -> 1500000 (6 decimals)
96
+ - String without prefix: "1.50" -> 1500000
97
+ - Integer/float: 1.50 -> 1500000
98
+ - Dict (already parsed): {"amount": "1500000", "asset": "usdt.tether-token.near"}
99
+
100
+ Args:
101
+ price: User-friendly price.
102
+ network: Network identifier (CAIP-2 format, e.g., "near:mainnet").
103
+
104
+ Returns:
105
+ AssetAmount dict with amount (atomic units string), asset (contract ID),
106
+ and extra metadata (symbol, decimals).
107
+
108
+ Raises:
109
+ ValueError: If price format is invalid or network is unsupported.
110
+ """
111
+ if not is_valid_network(network):
112
+ raise ValueError(f"Unsupported network: {network}")
113
+
114
+ # Handle dict (already in AssetAmount format)
115
+ if isinstance(price, dict):
116
+ if "amount" in price:
117
+ token = self._get_default_token(network)
118
+ asset = price.get("asset", token.contract_id)
119
+ extra = price.get("extra", {})
120
+ return {
121
+ "amount": str(price["amount"]),
122
+ "asset": asset,
123
+ "extra": extra,
124
+ }
125
+
126
+ # Parse money to decimal
127
+ decimal_amount = self._parse_money_to_decimal(price)
128
+
129
+ # Convert to atomic units using the default token
130
+ return self._default_money_conversion(decimal_amount, network)
131
+
132
+ async def enhance_requirements(
133
+ self,
134
+ requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
135
+ supported_kind: SupportedKindDict,
136
+ facilitator_extensions: List[str],
137
+ ) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
138
+ """Enhance payment requirements with NEAR-specific metadata.
139
+
140
+ Adds token contract as asset and includes symbol/decimals metadata
141
+ from the facilitator's supported kinds response.
142
+
143
+ Args:
144
+ requirements: Base payment requirements with amount set.
145
+ supported_kind: The matched SupportedKind from facilitator.
146
+ facilitator_extensions: Extensions supported by the facilitator.
147
+
148
+ Returns:
149
+ Enhanced requirements with asset and extra metadata.
150
+ """
151
+ # Convert to dict for modification
152
+ if hasattr(requirements, "model_dump"):
153
+ req = requirements.model_dump(by_alias=True)
154
+ else:
155
+ req = dict(requirements)
156
+
157
+ network = req.get("network", "")
158
+
159
+ if not is_valid_network(network):
160
+ raise ValueError(f"Unsupported network: {network}")
161
+
162
+ # If asset is not set, use the default token for the network
163
+ if not req.get("asset"):
164
+ token = self._get_default_token(network)
165
+ req["asset"] = token.contract_id
166
+
167
+ # If amount contains a decimal point, convert to atomic units
168
+ amount = req.get("amount", "")
169
+ if amount and "." in amount:
170
+ token = get_token_by_contract(network, req["asset"])
171
+ decimals = token.decimals if token else 6
172
+ req["amount"] = self._to_atomic_units(amount, decimals)
173
+
174
+ # Initialize extra map if needed
175
+ if "extra" not in req or req["extra"] is None:
176
+ req["extra"] = {}
177
+
178
+ # Add facilitator-provided extra fields (asset metadata)
179
+ if supported_kind.get("extra"):
180
+ sk_extra = supported_kind["extra"]
181
+ if "assetSymbol" in sk_extra:
182
+ req["extra"]["assetSymbol"] = sk_extra["assetSymbol"]
183
+ if "assetDecimals" in sk_extra:
184
+ req["extra"]["assetDecimals"] = sk_extra["assetDecimals"]
185
+
186
+ # Copy extension keys from supportedKind
187
+ if supported_kind.get("extra"):
188
+ for key in facilitator_extensions:
189
+ if key in supported_kind["extra"]:
190
+ req["extra"][key] = supported_kind["extra"][key]
191
+
192
+ return req
193
+
194
+ def _get_default_token(self, network: str) -> TokenInfo:
195
+ """Get the default token for a network.
196
+
197
+ Priority: configured preferred_token > network default.
198
+
199
+ Args:
200
+ network: The CAIP-2 network identifier.
201
+
202
+ Returns:
203
+ TokenInfo for the default token.
204
+ """
205
+ # If a preferred token is configured, try to use it
206
+ if self._config.preferred_token:
207
+ token = get_token_info(network, self._config.preferred_token)
208
+ if token:
209
+ return token
210
+
211
+ # Fall back to network default
212
+ config = get_network_config(network)
213
+ if config:
214
+ return config.default_token
215
+
216
+ # Final fallback (should not happen for valid networks)
217
+ from t402.schemes.near.constants import USDT_MAINNET
218
+ return USDT_MAINNET
219
+
220
+ def _parse_money_to_decimal(self, price: Union[str, int, float]) -> Decimal:
221
+ """Convert a money value to a Decimal amount.
222
+
223
+ Handles formats like "$1.50", "1.50", 1.50, etc.
224
+
225
+ Args:
226
+ price: The price value to parse.
227
+
228
+ Returns:
229
+ Decimal amount.
230
+
231
+ Raises:
232
+ ValueError: If the price format is invalid.
233
+ """
234
+ if isinstance(price, str):
235
+ clean_price = price.strip()
236
+ if clean_price.startswith("$"):
237
+ clean_price = clean_price[1:]
238
+ clean_price = clean_price.strip()
239
+
240
+ # Use the first space-separated part as the amount
241
+ parts = clean_price.split()
242
+ if parts:
243
+ try:
244
+ return Decimal(parts[0])
245
+ except Exception:
246
+ raise ValueError(f"Failed to parse price string: {price!r}")
247
+ raise ValueError("Empty price string after cleanup")
248
+ elif isinstance(price, (int, float)):
249
+ return Decimal(str(price))
250
+ else:
251
+ raise ValueError(f"Invalid price format: {price!r}")
252
+
253
+ def _default_money_conversion(self, amount: Decimal, network: str) -> AssetAmount:
254
+ """Convert a decimal amount to the default token's atomic units.
255
+
256
+ Args:
257
+ amount: Decimal amount in human-readable units.
258
+ network: The CAIP-2 network identifier.
259
+
260
+ Returns:
261
+ AssetAmount with atomic units, contract ID, and metadata.
262
+ """
263
+ token = self._get_default_token(network)
264
+ atomic_amount = self._to_atomic_units(str(amount), token.decimals)
265
+
266
+ return {
267
+ "amount": atomic_amount,
268
+ "asset": token.contract_id,
269
+ "extra": {
270
+ "symbol": token.symbol,
271
+ "decimals": token.decimals,
272
+ },
273
+ }
274
+
275
+ def _to_atomic_units(self, amount: str, decimals: int) -> str:
276
+ """Convert a decimal string amount to atomic units string.
277
+
278
+ For example, with decimals=6: "1.50" -> "1500000".
279
+
280
+ Args:
281
+ amount: The decimal amount string.
282
+ decimals: The number of decimal places for the token.
283
+
284
+ Returns:
285
+ Atomic units as a string.
286
+
287
+ Raises:
288
+ ValueError: If the amount is invalid or negative.
289
+ """
290
+ amount = amount.strip()
291
+ parsed = Decimal(amount)
292
+
293
+ if parsed < 0:
294
+ raise ValueError("Amount must be non-negative")
295
+
296
+ # Convert to atomic units
297
+ multiplier = Decimal(10) ** decimals
298
+ atomic = parsed * multiplier
299
+
300
+ # Truncate to integer (no rounding up)
301
+ atomic_int = int(atomic.to_integral_value(rounding=ROUND_DOWN))
302
+
303
+ return str(atomic_int)