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.
- t402/__init__.py +2 -1
- t402/bridge/client.py +13 -5
- t402/bridge/constants.py +3 -1
- 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/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/evm_paywall_template.py +1 -1
- t402/fastapi/middleware.py +1 -3
- t402/mcp/server.py +79 -46
- t402/near_paywall_template.py +2 -0
- t402/networks.py +34 -1
- t402/paywall.py +1 -3
- t402/schemes/__init__.py +124 -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/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 +112 -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/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 +29 -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/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 +9 -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/tron/__init__.py +11 -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/stacks_paywall_template.py +2 -0
- t402/svm.py +45 -11
- t402/svm_paywall_template.py +1 -1
- t402/ton.py +5 -1
- t402/ton_paywall_template.py +1 -192
- t402/tron.py +2 -0
- t402/tron_paywall_template.py +2 -0
- t402/types.py +3 -1
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
- t402-1.9.1.dist-info/RECORD +125 -0
- t402-1.9.0.dist-info/RECORD +0 -72
- {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
- {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
t402/schemes/evm/exact/server.py
CHANGED
|
@@ -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
|
|
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
|