t402 1.9.0__py3-none-any.whl → 1.10.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 +2 -1
- t402/a2a/__init__.py +73 -0
- t402/a2a/helpers.py +158 -0
- t402/a2a/types.py +145 -0
- t402/bridge/client.py +13 -5
- t402/bridge/constants.py +4 -2
- t402/bridge/router.py +1 -1
- t402/bridge/scan.py +3 -1
- t402/chains.py +268 -1
- t402/cli.py +31 -9
- t402/common.py +2 -0
- t402/cosmos_paywall_template.py +2 -0
- t402/django/__init__.py +42 -0
- t402/django/middleware.py +596 -0
- t402/encoding.py +9 -3
- t402/erc4337/accounts.py +56 -51
- t402/erc4337/bundlers.py +105 -99
- t402/erc4337/paymasters.py +100 -109
- t402/erc4337/types.py +39 -26
- t402/errors.py +213 -0
- t402/evm_paywall_template.py +1 -1
- t402/facilitator.py +125 -0
- t402/fastapi/middleware.py +1 -3
- t402/mcp/constants.py +3 -6
- t402/mcp/server.py +501 -84
- t402/mcp/web3_utils.py +493 -0
- t402/multisig/__init__.py +120 -0
- t402/multisig/constants.py +54 -0
- t402/multisig/safe.py +441 -0
- t402/multisig/signature.py +228 -0
- t402/multisig/transaction.py +238 -0
- t402/multisig/types.py +108 -0
- t402/multisig/utils.py +77 -0
- t402/near_paywall_template.py +2 -0
- t402/networks.py +34 -1
- t402/paywall.py +1 -3
- t402/schemes/__init__.py +143 -0
- t402/schemes/aptos/__init__.py +70 -0
- t402/schemes/aptos/constants.py +349 -0
- t402/schemes/aptos/exact_direct/__init__.py +44 -0
- t402/schemes/aptos/exact_direct/client.py +202 -0
- t402/schemes/aptos/exact_direct/facilitator.py +426 -0
- t402/schemes/aptos/exact_direct/server.py +272 -0
- t402/schemes/aptos/types.py +237 -0
- t402/schemes/cosmos/__init__.py +114 -0
- t402/schemes/cosmos/constants.py +211 -0
- t402/schemes/cosmos/exact_direct/__init__.py +21 -0
- t402/schemes/cosmos/exact_direct/client.py +198 -0
- t402/schemes/cosmos/exact_direct/facilitator.py +493 -0
- t402/schemes/cosmos/exact_direct/server.py +315 -0
- t402/schemes/cosmos/types.py +501 -0
- t402/schemes/evm/__init__.py +46 -1
- t402/schemes/evm/exact/__init__.py +11 -0
- t402/schemes/evm/exact/client.py +3 -1
- t402/schemes/evm/exact/facilitator.py +894 -0
- t402/schemes/evm/exact/server.py +1 -1
- t402/schemes/evm/exact_legacy/__init__.py +38 -0
- t402/schemes/evm/exact_legacy/client.py +291 -0
- t402/schemes/evm/exact_legacy/facilitator.py +777 -0
- t402/schemes/evm/exact_legacy/server.py +231 -0
- t402/schemes/evm/upto/__init__.py +12 -0
- t402/schemes/evm/upto/client.py +6 -2
- t402/schemes/evm/upto/facilitator.py +625 -0
- t402/schemes/evm/upto/server.py +243 -0
- t402/schemes/evm/upto/types.py +3 -1
- t402/schemes/interfaces.py +6 -2
- t402/schemes/near/__init__.py +137 -0
- t402/schemes/near/constants.py +189 -0
- t402/schemes/near/exact_direct/__init__.py +21 -0
- t402/schemes/near/exact_direct/client.py +204 -0
- t402/schemes/near/exact_direct/facilitator.py +455 -0
- t402/schemes/near/exact_direct/server.py +303 -0
- t402/schemes/near/types.py +419 -0
- t402/schemes/near/upto/__init__.py +54 -0
- t402/schemes/near/upto/types.py +272 -0
- t402/schemes/polkadot/__init__.py +72 -0
- t402/schemes/polkadot/constants.py +155 -0
- t402/schemes/polkadot/exact_direct/__init__.py +43 -0
- t402/schemes/polkadot/exact_direct/client.py +235 -0
- t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
- t402/schemes/polkadot/exact_direct/server.py +292 -0
- t402/schemes/polkadot/types.py +385 -0
- t402/schemes/registry.py +6 -2
- t402/schemes/stacks/__init__.py +68 -0
- t402/schemes/stacks/constants.py +122 -0
- t402/schemes/stacks/exact_direct/__init__.py +43 -0
- t402/schemes/stacks/exact_direct/client.py +222 -0
- t402/schemes/stacks/exact_direct/facilitator.py +424 -0
- t402/schemes/stacks/exact_direct/server.py +292 -0
- t402/schemes/stacks/types.py +380 -0
- t402/schemes/svm/__init__.py +44 -0
- t402/schemes/svm/exact/__init__.py +35 -0
- t402/schemes/svm/exact/client.py +23 -0
- t402/schemes/svm/exact/facilitator.py +24 -0
- t402/schemes/svm/exact/server.py +20 -0
- t402/schemes/svm/upto/__init__.py +23 -0
- t402/schemes/svm/upto/types.py +193 -0
- t402/schemes/tezos/__init__.py +84 -0
- t402/schemes/tezos/constants.py +372 -0
- t402/schemes/tezos/exact_direct/__init__.py +22 -0
- t402/schemes/tezos/exact_direct/client.py +226 -0
- t402/schemes/tezos/exact_direct/facilitator.py +491 -0
- t402/schemes/tezos/exact_direct/server.py +277 -0
- t402/schemes/tezos/types.py +220 -0
- t402/schemes/ton/__init__.py +24 -2
- t402/schemes/ton/exact/__init__.py +7 -0
- t402/schemes/ton/exact/facilitator.py +730 -0
- t402/schemes/ton/exact/server.py +1 -1
- t402/schemes/ton/upto/__init__.py +31 -0
- t402/schemes/ton/upto/types.py +215 -0
- t402/schemes/tron/__init__.py +28 -2
- t402/schemes/tron/exact/__init__.py +9 -0
- t402/schemes/tron/exact/facilitator.py +673 -0
- t402/schemes/tron/exact/server.py +1 -1
- t402/schemes/tron/upto/__init__.py +30 -0
- t402/schemes/tron/upto/types.py +213 -0
- t402/stacks_paywall_template.py +2 -0
- t402/starlette/__init__.py +38 -0
- t402/starlette/middleware.py +522 -0
- t402/svm.py +45 -11
- t402/svm_paywall_template.py +1 -1
- t402/ton.py +6 -2
- t402/ton_paywall_template.py +1 -192
- t402/tron.py +2 -0
- t402/tron_paywall_template.py +2 -0
- t402/types.py +103 -3
- t402/wdk/chains.py +1 -1
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.9.0.dist-info → t402-1.10.0.dist-info}/METADATA +42 -1
- t402-1.10.0.dist-info/RECORD +156 -0
- t402-1.9.0.dist-info/RECORD +0 -72
- {t402-1.9.0.dist-info → t402-1.10.0.dist-info}/WHEEL +0 -0
- {t402-1.9.0.dist-info → t402-1.10.0.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)
|