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.
- 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 +164 -1
- 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 +67 -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 +70 -0
- t402/schemes/evm/upto/client.py +244 -0
- t402/schemes/evm/upto/facilitator.py +625 -0
- t402/schemes/evm/upto/server.py +243 -0
- t402/schemes/evm/upto/types.py +307 -0
- 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/schemes/upto/__init__.py +80 -0
- t402/schemes/upto/types.py +376 -0
- 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 +4 -2
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
- t402-1.9.1.dist-info/RECORD +125 -0
- t402-1.7.1.dist-info/RECORD +0 -67
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""EVM Up-To Scheme - Server Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the server-side implementation of the upto payment scheme
|
|
4
|
+
for EVM networks using EIP-2612 Permit.
|
|
5
|
+
|
|
6
|
+
The server parses user-friendly prices into atomic token amounts and enhances
|
|
7
|
+
payment requirements with EIP-712 domain information needed by clients to
|
|
8
|
+
sign Permit authorizations.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from decimal import Decimal
|
|
14
|
+
from typing import Any, Dict, List, Optional, Union
|
|
15
|
+
|
|
16
|
+
from t402.types import (
|
|
17
|
+
PaymentRequirementsV2,
|
|
18
|
+
Network,
|
|
19
|
+
)
|
|
20
|
+
from t402.schemes.interfaces import AssetAmount, SupportedKindDict
|
|
21
|
+
from t402.chains import (
|
|
22
|
+
get_chain_id,
|
|
23
|
+
get_token_decimals,
|
|
24
|
+
get_token_name,
|
|
25
|
+
get_token_version,
|
|
26
|
+
get_default_token_address,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Constants
|
|
31
|
+
SCHEME_UPTO = "upto"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UptoEvmServerScheme:
|
|
35
|
+
"""Server scheme for EVM upto payments using EIP-2612 Permit.
|
|
36
|
+
|
|
37
|
+
Handles parsing user-friendly prices and enhancing payment requirements
|
|
38
|
+
with EIP-712 Permit domain information needed for clients to sign
|
|
39
|
+
gasless token approvals.
|
|
40
|
+
|
|
41
|
+
The upto scheme allows clients to authorize a maximum amount (maxAmount)
|
|
42
|
+
that the facilitator can settle up to, enabling usage-based billing.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
```python
|
|
46
|
+
scheme = UptoEvmServerScheme()
|
|
47
|
+
|
|
48
|
+
# Parse price to get asset amount info
|
|
49
|
+
asset_amount = await scheme.parse_price("$1.00", "eip155:8453")
|
|
50
|
+
# Returns: {"amount": "1000000", "asset": "0x833589...", "extra": {...}}
|
|
51
|
+
|
|
52
|
+
# Enhance requirements with EIP-712 domain info
|
|
53
|
+
enhanced = await scheme.enhance_requirements(
|
|
54
|
+
requirements,
|
|
55
|
+
supported_kind,
|
|
56
|
+
facilitator_extensions,
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
scheme = SCHEME_UPTO
|
|
62
|
+
caip_family = "eip155:*"
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
router_address: Optional[str] = None,
|
|
67
|
+
):
|
|
68
|
+
"""Initialize the server scheme.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
router_address: Optional default router/spender contract address.
|
|
72
|
+
If provided, it will be included in enhanced requirements.
|
|
73
|
+
"""
|
|
74
|
+
self._router_address = router_address
|
|
75
|
+
|
|
76
|
+
async def parse_price(
|
|
77
|
+
self,
|
|
78
|
+
price: Union[str, int, float, Dict[str, Any]],
|
|
79
|
+
network: Network,
|
|
80
|
+
) -> AssetAmount:
|
|
81
|
+
"""Parse a user-friendly price to atomic amount and asset.
|
|
82
|
+
|
|
83
|
+
For the upto scheme, this returns the maxAmount the client should
|
|
84
|
+
authorize. The actual settled amount may be less.
|
|
85
|
+
|
|
86
|
+
Supports:
|
|
87
|
+
- String with $ prefix: "$1.00" -> 1000000 (6 decimals)
|
|
88
|
+
- String without prefix: "1.00" -> 1000000
|
|
89
|
+
- Integer/float: 1.00 -> 1000000
|
|
90
|
+
- Dict (TokenAmount): {"amount": "1000000", "asset": "0x..."}
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
price: User-friendly price (represents the max amount)
|
|
94
|
+
network: Network identifier (CAIP-2 format, e.g., "eip155:8453")
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
AssetAmount dict with amount, asset, and extra metadata
|
|
98
|
+
containing EIP-712 domain info.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: If price format is invalid or network is unsupported
|
|
102
|
+
"""
|
|
103
|
+
chain_id = self._get_chain_id(network)
|
|
104
|
+
|
|
105
|
+
# Handle dict (already in TokenAmount format)
|
|
106
|
+
if isinstance(price, dict):
|
|
107
|
+
return {
|
|
108
|
+
"amount": str(price.get("amount", "0")),
|
|
109
|
+
"asset": price.get("asset", ""),
|
|
110
|
+
"extra": price.get("extra", {}),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Get chain ID as string for token lookups
|
|
114
|
+
chain_id_str = str(chain_id)
|
|
115
|
+
|
|
116
|
+
# Get default token for the network
|
|
117
|
+
# Try USDT0 first, fall back to USDT, then USDC
|
|
118
|
+
try:
|
|
119
|
+
asset_address = get_default_token_address(chain_id_str, "usdt0")
|
|
120
|
+
except (ValueError, KeyError):
|
|
121
|
+
try:
|
|
122
|
+
asset_address = get_default_token_address(chain_id_str, "usdt")
|
|
123
|
+
except (ValueError, KeyError):
|
|
124
|
+
try:
|
|
125
|
+
asset_address = get_default_token_address(chain_id_str, "usdc")
|
|
126
|
+
except (ValueError, KeyError):
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"Unknown network: no known token for chain {chain_id_str}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
decimals = get_token_decimals(chain_id_str, asset_address)
|
|
132
|
+
|
|
133
|
+
# Parse price string/number
|
|
134
|
+
if isinstance(price, str):
|
|
135
|
+
if price.startswith("$"):
|
|
136
|
+
price = price[1:]
|
|
137
|
+
amount_decimal = Decimal(price)
|
|
138
|
+
else:
|
|
139
|
+
amount_decimal = Decimal(str(price))
|
|
140
|
+
|
|
141
|
+
# Convert to atomic units
|
|
142
|
+
atomic_amount = int(amount_decimal * Decimal(10**decimals))
|
|
143
|
+
|
|
144
|
+
# Get EIP-712 domain info for Permit signing
|
|
145
|
+
extra: Dict[str, Any] = {
|
|
146
|
+
"name": get_token_name(chain_id_str, asset_address),
|
|
147
|
+
"version": get_token_version(chain_id_str, asset_address),
|
|
148
|
+
"decimals": decimals,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# Include router address if configured
|
|
152
|
+
if self._router_address:
|
|
153
|
+
extra["routerAddress"] = self._router_address
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"amount": str(atomic_amount),
|
|
157
|
+
"asset": asset_address,
|
|
158
|
+
"extra": extra,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async def enhance_requirements(
|
|
162
|
+
self,
|
|
163
|
+
requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
|
|
164
|
+
supported_kind: SupportedKindDict,
|
|
165
|
+
facilitator_extensions: List[str],
|
|
166
|
+
) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
|
|
167
|
+
"""Enhance payment requirements with EVM Permit-specific metadata.
|
|
168
|
+
|
|
169
|
+
Adds EIP-712 domain information (token name, version) and optionally
|
|
170
|
+
the router/spender address to the extra field so clients can properly
|
|
171
|
+
sign the EIP-2612 Permit authorization.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
requirements: Base payment requirements (with maxAmount/amount set)
|
|
175
|
+
supported_kind: Matched SupportedKind from facilitator's /supported
|
|
176
|
+
facilitator_extensions: Extensions supported by facilitator
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Enhanced requirements with EIP-712 Permit domain info in extra
|
|
180
|
+
"""
|
|
181
|
+
# Convert to dict for modification
|
|
182
|
+
if hasattr(requirements, "model_dump"):
|
|
183
|
+
req = requirements.model_dump(by_alias=True)
|
|
184
|
+
else:
|
|
185
|
+
req = dict(requirements)
|
|
186
|
+
|
|
187
|
+
network = req.get("network", "")
|
|
188
|
+
asset = req.get("asset", "")
|
|
189
|
+
|
|
190
|
+
# Get chain ID as string
|
|
191
|
+
chain_id = str(self._get_chain_id(network))
|
|
192
|
+
|
|
193
|
+
# Ensure extra exists
|
|
194
|
+
if "extra" not in req or req["extra"] is None:
|
|
195
|
+
req["extra"] = {}
|
|
196
|
+
|
|
197
|
+
# Add EIP-712 domain info if not present
|
|
198
|
+
if "name" not in req["extra"]:
|
|
199
|
+
try:
|
|
200
|
+
req["extra"]["name"] = get_token_name(chain_id, asset)
|
|
201
|
+
except (ValueError, KeyError):
|
|
202
|
+
# If token not found in known tokens, use a sensible default
|
|
203
|
+
req["extra"]["name"] = "TetherToken"
|
|
204
|
+
|
|
205
|
+
if "version" not in req["extra"]:
|
|
206
|
+
try:
|
|
207
|
+
req["extra"]["version"] = get_token_version(chain_id, asset)
|
|
208
|
+
except (ValueError, KeyError):
|
|
209
|
+
req["extra"]["version"] = "1"
|
|
210
|
+
|
|
211
|
+
# Add router address if configured and not already present
|
|
212
|
+
if self._router_address and "routerAddress" not in req["extra"]:
|
|
213
|
+
req["extra"]["routerAddress"] = self._router_address
|
|
214
|
+
|
|
215
|
+
# Add facilitator extra data if available
|
|
216
|
+
if supported_kind.get("extra"):
|
|
217
|
+
for key, value in supported_kind["extra"].items():
|
|
218
|
+
if key not in req["extra"]:
|
|
219
|
+
req["extra"][key] = value
|
|
220
|
+
|
|
221
|
+
return req
|
|
222
|
+
|
|
223
|
+
def _get_chain_id(self, network: str) -> int:
|
|
224
|
+
"""Get chain ID from network identifier.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
network: Network identifier (CAIP-2 or legacy format)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Chain ID as integer
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
ValueError: If the network format is unrecognized
|
|
234
|
+
"""
|
|
235
|
+
# Handle CAIP-2 format (eip155:8453)
|
|
236
|
+
if network.startswith("eip155:"):
|
|
237
|
+
return int(network.split(":")[1])
|
|
238
|
+
|
|
239
|
+
# Handle legacy format
|
|
240
|
+
try:
|
|
241
|
+
return int(get_chain_id(network))
|
|
242
|
+
except (KeyError, ValueError):
|
|
243
|
+
raise ValueError(f"Unknown network: {network}")
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""EVM Up-To Scheme Types.
|
|
2
|
+
|
|
3
|
+
EVM-specific types for the up-to payment scheme using EIP-2612 Permit.
|
|
4
|
+
The Permit standard allows gasless token approvals via signature.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
from pydantic.alias_generators import to_camel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# EIP-712 Permit type definitions
|
|
15
|
+
PERMIT_TYPES: Dict[str, List[Dict[str, str]]] = {
|
|
16
|
+
"Permit": [
|
|
17
|
+
{"name": "owner", "type": "address"},
|
|
18
|
+
{"name": "spender", "type": "address"},
|
|
19
|
+
{"name": "value", "type": "uint256"},
|
|
20
|
+
{"name": "nonce", "type": "uint256"},
|
|
21
|
+
{"name": "deadline", "type": "uint256"},
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
PERMIT_DOMAIN_TYPES: List[Dict[str, str]] = [
|
|
26
|
+
{"name": "name", "type": "string"},
|
|
27
|
+
{"name": "version", "type": "string"},
|
|
28
|
+
{"name": "chainId", "type": "uint256"},
|
|
29
|
+
{"name": "verifyingContract", "type": "address"},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PermitSignature(BaseModel):
|
|
34
|
+
"""EIP-2612 permit signature components."""
|
|
35
|
+
|
|
36
|
+
v: int = Field(description="Recovery id")
|
|
37
|
+
r: str = Field(description="First 32 bytes of signature")
|
|
38
|
+
s: str = Field(description="Second 32 bytes of signature")
|
|
39
|
+
|
|
40
|
+
model_config = ConfigDict(
|
|
41
|
+
alias_generator=to_camel,
|
|
42
|
+
populate_by_name=True,
|
|
43
|
+
from_attributes=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PermitAuthorization(BaseModel):
|
|
48
|
+
"""EIP-2612 permit authorization parameters."""
|
|
49
|
+
|
|
50
|
+
owner: str = Field(description="Token owner address")
|
|
51
|
+
spender: str = Field(description="Address authorized to spend (router contract)")
|
|
52
|
+
value: str = Field(description="Maximum authorized value")
|
|
53
|
+
deadline: str = Field(description="Permit deadline (unix timestamp)")
|
|
54
|
+
nonce: int = Field(description="Permit nonce from token contract")
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(
|
|
57
|
+
alias_generator=to_camel,
|
|
58
|
+
populate_by_name=True,
|
|
59
|
+
from_attributes=True,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class UptoEIP2612Payload(BaseModel):
|
|
64
|
+
"""Up-to payment payload using EIP-2612 Permit."""
|
|
65
|
+
|
|
66
|
+
signature: PermitSignature = Field(description="Permit signature components")
|
|
67
|
+
authorization: PermitAuthorization = Field(description="Permit parameters")
|
|
68
|
+
payment_nonce: str = Field(
|
|
69
|
+
alias="paymentNonce",
|
|
70
|
+
description="Unique nonce to prevent replay attacks",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
model_config = ConfigDict(
|
|
74
|
+
alias_generator=to_camel,
|
|
75
|
+
populate_by_name=True,
|
|
76
|
+
from_attributes=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
80
|
+
"""Convert to dictionary for JSON serialization."""
|
|
81
|
+
return {
|
|
82
|
+
"signature": {
|
|
83
|
+
"v": self.signature.v,
|
|
84
|
+
"r": self.signature.r,
|
|
85
|
+
"s": self.signature.s,
|
|
86
|
+
},
|
|
87
|
+
"authorization": {
|
|
88
|
+
"owner": self.authorization.owner,
|
|
89
|
+
"spender": self.authorization.spender,
|
|
90
|
+
"value": self.authorization.value,
|
|
91
|
+
"deadline": self.authorization.deadline,
|
|
92
|
+
"nonce": self.authorization.nonce,
|
|
93
|
+
},
|
|
94
|
+
"paymentNonce": self.payment_nonce,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class UptoCompactPayload(BaseModel):
|
|
99
|
+
"""Alternative payload with combined signature."""
|
|
100
|
+
|
|
101
|
+
signature: str = Field(
|
|
102
|
+
description="Combined EIP-2612 permit signature (65 bytes hex)"
|
|
103
|
+
)
|
|
104
|
+
authorization: PermitAuthorization = Field(description="Permit parameters")
|
|
105
|
+
payment_nonce: str = Field(
|
|
106
|
+
alias="paymentNonce",
|
|
107
|
+
description="Unique nonce to prevent replay attacks",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
model_config = ConfigDict(
|
|
111
|
+
alias_generator=to_camel,
|
|
112
|
+
populate_by_name=True,
|
|
113
|
+
from_attributes=True,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class UptoEvmExtra(BaseModel):
|
|
118
|
+
"""EVM-specific extra fields for the upto scheme."""
|
|
119
|
+
|
|
120
|
+
name: str = Field(description="EIP-712 domain name (token name)")
|
|
121
|
+
version: str = Field(description="EIP-712 domain version")
|
|
122
|
+
router_address: Optional[str] = Field(
|
|
123
|
+
default=None,
|
|
124
|
+
alias="routerAddress",
|
|
125
|
+
description="Upto router contract address",
|
|
126
|
+
)
|
|
127
|
+
unit: Optional[str] = Field(
|
|
128
|
+
default=None,
|
|
129
|
+
description="Billing unit (e.g., 'token', 'request')",
|
|
130
|
+
)
|
|
131
|
+
unit_price: Optional[str] = Field(
|
|
132
|
+
default=None,
|
|
133
|
+
alias="unitPrice",
|
|
134
|
+
description="Price per unit in smallest denomination",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
model_config = ConfigDict(
|
|
138
|
+
alias_generator=to_camel,
|
|
139
|
+
populate_by_name=True,
|
|
140
|
+
from_attributes=True,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class UptoEvmSettlement(BaseModel):
|
|
145
|
+
"""EVM-specific settlement request."""
|
|
146
|
+
|
|
147
|
+
settle_amount: str = Field(
|
|
148
|
+
alias="settleAmount",
|
|
149
|
+
description="Actual amount to settle",
|
|
150
|
+
)
|
|
151
|
+
usage_details: Optional["UptoEvmUsageDetails"] = Field(
|
|
152
|
+
default=None,
|
|
153
|
+
alias="usageDetails",
|
|
154
|
+
description="Optional usage information",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
model_config = ConfigDict(
|
|
158
|
+
alias_generator=to_camel,
|
|
159
|
+
populate_by_name=True,
|
|
160
|
+
from_attributes=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class UptoEvmUsageDetails(BaseModel):
|
|
165
|
+
"""Usage details for EVM settlement."""
|
|
166
|
+
|
|
167
|
+
units_consumed: Optional[int] = Field(
|
|
168
|
+
default=None,
|
|
169
|
+
alias="unitsConsumed",
|
|
170
|
+
description="Number of units consumed",
|
|
171
|
+
)
|
|
172
|
+
unit_price: Optional[str] = Field(
|
|
173
|
+
default=None,
|
|
174
|
+
alias="unitPrice",
|
|
175
|
+
description="Price per unit used",
|
|
176
|
+
)
|
|
177
|
+
unit_type: Optional[str] = Field(
|
|
178
|
+
default=None,
|
|
179
|
+
alias="unitType",
|
|
180
|
+
description="Type of unit",
|
|
181
|
+
)
|
|
182
|
+
start_time: Optional[int] = Field(
|
|
183
|
+
default=None,
|
|
184
|
+
alias="startTime",
|
|
185
|
+
description="Start timestamp",
|
|
186
|
+
)
|
|
187
|
+
end_time: Optional[int] = Field(
|
|
188
|
+
default=None,
|
|
189
|
+
alias="endTime",
|
|
190
|
+
description="End timestamp",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
model_config = ConfigDict(
|
|
194
|
+
alias_generator=to_camel,
|
|
195
|
+
populate_by_name=True,
|
|
196
|
+
from_attributes=True,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Update forward reference
|
|
201
|
+
UptoEvmSettlement.model_rebuild()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def is_eip2612_payload(data: Dict[str, Any]) -> bool:
|
|
205
|
+
"""Check if the given data represents an EIP-2612 permit payload.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
data: Dictionary to check
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if data has the correct EIP-2612 structure
|
|
212
|
+
"""
|
|
213
|
+
if not isinstance(data, dict):
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
sig = data.get("signature")
|
|
217
|
+
auth = data.get("authorization")
|
|
218
|
+
|
|
219
|
+
if not sig or not auth:
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
# Check signature structure (should be object with v, r, s)
|
|
223
|
+
if not isinstance(sig, dict):
|
|
224
|
+
return False
|
|
225
|
+
if not all(k in sig for k in ["v", "r", "s"]):
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
# Check authorization structure
|
|
229
|
+
if not isinstance(auth, dict):
|
|
230
|
+
return False
|
|
231
|
+
required_auth_fields = ["owner", "spender", "value", "deadline"]
|
|
232
|
+
if not all(k in auth for k in required_auth_fields):
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def create_permit_domain(
|
|
239
|
+
name: str,
|
|
240
|
+
version: str,
|
|
241
|
+
chain_id: int,
|
|
242
|
+
token_address: str,
|
|
243
|
+
) -> Dict[str, Any]:
|
|
244
|
+
"""Create an EIP-712 domain for permit signing.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
name: Token name
|
|
248
|
+
version: Token version
|
|
249
|
+
chain_id: Chain ID
|
|
250
|
+
token_address: Token contract address
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
EIP-712 domain dictionary
|
|
254
|
+
"""
|
|
255
|
+
return {
|
|
256
|
+
"name": name,
|
|
257
|
+
"version": version,
|
|
258
|
+
"chainId": chain_id,
|
|
259
|
+
"verifyingContract": token_address,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def create_permit_message(authorization: PermitAuthorization) -> Dict[str, Any]:
|
|
264
|
+
"""Create an EIP-712 message for permit signing.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
authorization: Permit authorization parameters
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
EIP-712 message dictionary
|
|
271
|
+
"""
|
|
272
|
+
return {
|
|
273
|
+
"owner": authorization.owner,
|
|
274
|
+
"spender": authorization.spender,
|
|
275
|
+
"value": int(authorization.value),
|
|
276
|
+
"nonce": authorization.nonce,
|
|
277
|
+
"deadline": int(authorization.deadline),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def payload_from_dict(data: Dict[str, Any]) -> UptoEIP2612Payload:
|
|
282
|
+
"""Create an UptoEIP2612Payload from a dictionary.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
data: Dictionary containing payload data
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
UptoEIP2612Payload instance
|
|
289
|
+
"""
|
|
290
|
+
sig_data = data.get("signature", {})
|
|
291
|
+
auth_data = data.get("authorization", {})
|
|
292
|
+
|
|
293
|
+
return UptoEIP2612Payload(
|
|
294
|
+
signature=PermitSignature(
|
|
295
|
+
v=sig_data.get("v", 0),
|
|
296
|
+
r=sig_data.get("r", ""),
|
|
297
|
+
s=sig_data.get("s", ""),
|
|
298
|
+
),
|
|
299
|
+
authorization=PermitAuthorization(
|
|
300
|
+
owner=auth_data.get("owner", ""),
|
|
301
|
+
spender=auth_data.get("spender", ""),
|
|
302
|
+
value=auth_data.get("value", ""),
|
|
303
|
+
deadline=auth_data.get("deadline", ""),
|
|
304
|
+
nonce=auth_data.get("nonce", 0),
|
|
305
|
+
),
|
|
306
|
+
payment_nonce=data.get("paymentNonce", ""),
|
|
307
|
+
)
|
t402/schemes/interfaces.py
CHANGED
|
@@ -25,9 +25,13 @@ from t402.types import (
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
# Type aliases for clarity
|
|
28
|
-
Price = Union[
|
|
28
|
+
Price = Union[
|
|
29
|
+
str, int, float, Dict[str, Any]
|
|
30
|
+
] # e.g., "$0.10", 0.10, {"amount": "100000", "asset": "..."}
|
|
29
31
|
AssetAmount = Dict[str, Any] # {"amount": str, "asset": str, "extra"?: dict}
|
|
30
|
-
SupportedKindDict = Dict[
|
|
32
|
+
SupportedKindDict = Dict[
|
|
33
|
+
str, Any
|
|
34
|
+
] # {"t402Version": int, "scheme": str, "network": str, "extra"?: dict}
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
@runtime_checkable
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""NEAR Blockchain Payment Schemes.
|
|
2
|
+
|
|
3
|
+
This package provides payment scheme implementations for the NEAR blockchain.
|
|
4
|
+
|
|
5
|
+
Supported schemes:
|
|
6
|
+
- exact-direct: Client executes NEP-141 ft_transfer, tx hash used as proof.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
```python
|
|
10
|
+
from t402.schemes.near import (
|
|
11
|
+
# Client
|
|
12
|
+
ExactDirectNearClientScheme,
|
|
13
|
+
ExactDirectNearClientConfig,
|
|
14
|
+
# Server
|
|
15
|
+
ExactDirectNearServerScheme,
|
|
16
|
+
ExactDirectNearServerConfig,
|
|
17
|
+
# Facilitator
|
|
18
|
+
ExactDirectNearFacilitatorScheme,
|
|
19
|
+
ExactDirectNearFacilitatorConfig,
|
|
20
|
+
# Signer protocols
|
|
21
|
+
ClientNearSigner,
|
|
22
|
+
FacilitatorNearSigner,
|
|
23
|
+
# Constants
|
|
24
|
+
SCHEME_EXACT_DIRECT,
|
|
25
|
+
NEAR_MAINNET,
|
|
26
|
+
NEAR_TESTNET,
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from t402.schemes.near.exact_direct import (
|
|
32
|
+
ExactDirectNearClientScheme,
|
|
33
|
+
ExactDirectNearServerScheme,
|
|
34
|
+
ExactDirectNearFacilitatorScheme,
|
|
35
|
+
)
|
|
36
|
+
from t402.schemes.near.exact_direct.client import ExactDirectNearClientConfig
|
|
37
|
+
from t402.schemes.near.exact_direct.server import ExactDirectNearServerConfig
|
|
38
|
+
from t402.schemes.near.exact_direct.facilitator import ExactDirectNearFacilitatorConfig
|
|
39
|
+
from t402.schemes.near.types import (
|
|
40
|
+
ClientNearSigner,
|
|
41
|
+
FacilitatorNearSigner,
|
|
42
|
+
ExactDirectPayload,
|
|
43
|
+
FtTransferArgs,
|
|
44
|
+
is_valid_account_id,
|
|
45
|
+
)
|
|
46
|
+
from t402.schemes.near.constants import (
|
|
47
|
+
SCHEME_EXACT_DIRECT,
|
|
48
|
+
NEAR_MAINNET,
|
|
49
|
+
NEAR_TESTNET,
|
|
50
|
+
NEAR_MAINNET_RPC,
|
|
51
|
+
NEAR_TESTNET_RPC,
|
|
52
|
+
CAIP_FAMILY,
|
|
53
|
+
DEFAULT_GAS,
|
|
54
|
+
DEFAULT_GAS_INT,
|
|
55
|
+
STORAGE_DEPOSIT,
|
|
56
|
+
FUNCTION_FT_TRANSFER,
|
|
57
|
+
USDT_MAINNET,
|
|
58
|
+
USDT_TESTNET,
|
|
59
|
+
USDC_MAINNET,
|
|
60
|
+
USDC_TESTNET,
|
|
61
|
+
TokenInfo,
|
|
62
|
+
NetworkConfig,
|
|
63
|
+
get_network_config,
|
|
64
|
+
get_token_info,
|
|
65
|
+
get_token_by_contract,
|
|
66
|
+
is_valid_network,
|
|
67
|
+
get_supported_networks,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
# Scheme implementations
|
|
72
|
+
"ExactDirectNearClientScheme",
|
|
73
|
+
"ExactDirectNearServerScheme",
|
|
74
|
+
"ExactDirectNearFacilitatorScheme",
|
|
75
|
+
# Configurations
|
|
76
|
+
"ExactDirectNearClientConfig",
|
|
77
|
+
"ExactDirectNearServerConfig",
|
|
78
|
+
"ExactDirectNearFacilitatorConfig",
|
|
79
|
+
# Signer protocols
|
|
80
|
+
"ClientNearSigner",
|
|
81
|
+
"FacilitatorNearSigner",
|
|
82
|
+
# Payload types
|
|
83
|
+
"ExactDirectPayload",
|
|
84
|
+
"FtTransferArgs",
|
|
85
|
+
# Validation
|
|
86
|
+
"is_valid_account_id",
|
|
87
|
+
"is_valid_network",
|
|
88
|
+
# Constants
|
|
89
|
+
"SCHEME_EXACT_DIRECT",
|
|
90
|
+
"NEAR_MAINNET",
|
|
91
|
+
"NEAR_TESTNET",
|
|
92
|
+
"NEAR_MAINNET_RPC",
|
|
93
|
+
"NEAR_TESTNET_RPC",
|
|
94
|
+
"CAIP_FAMILY",
|
|
95
|
+
"DEFAULT_GAS",
|
|
96
|
+
"DEFAULT_GAS_INT",
|
|
97
|
+
"STORAGE_DEPOSIT",
|
|
98
|
+
"FUNCTION_FT_TRANSFER",
|
|
99
|
+
# Token definitions
|
|
100
|
+
"USDT_MAINNET",
|
|
101
|
+
"USDT_TESTNET",
|
|
102
|
+
"USDC_MAINNET",
|
|
103
|
+
"USDC_TESTNET",
|
|
104
|
+
# Data classes
|
|
105
|
+
"TokenInfo",
|
|
106
|
+
"NetworkConfig",
|
|
107
|
+
# Lookup functions
|
|
108
|
+
"get_network_config",
|
|
109
|
+
"get_token_info",
|
|
110
|
+
"get_token_by_contract",
|
|
111
|
+
"get_supported_networks",
|
|
112
|
+
]
|