t402 1.6.1__py3-none-any.whl → 1.7.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 +169 -2
- t402/common.py +37 -8
- t402/encoding.py +298 -1
- t402/facilitator.py +1 -1
- t402/fastapi/__init__.py +79 -0
- t402/fastapi/dependencies.py +398 -0
- t402/fastapi/middleware.py +665 -130
- t402/schemes/__init__.py +125 -0
- t402/schemes/evm/__init__.py +25 -0
- t402/schemes/evm/exact/__init__.py +29 -0
- t402/schemes/evm/exact/client.py +265 -0
- t402/schemes/evm/exact/server.py +181 -0
- t402/schemes/interfaces.py +401 -0
- t402/schemes/registry.py +477 -0
- t402/schemes/ton/__init__.py +22 -0
- t402/schemes/ton/exact/__init__.py +27 -0
- t402/schemes/ton/exact/client.py +343 -0
- t402/schemes/ton/exact/server.py +201 -0
- t402/schemes/tron/__init__.py +22 -0
- t402/schemes/tron/exact/__init__.py +27 -0
- t402/schemes/tron/exact/client.py +260 -0
- t402/schemes/tron/exact/server.py +192 -0
- t402/types.py +178 -8
- {t402-1.6.1.dist-info → t402-1.7.1.dist-info}/METADATA +1 -1
- {t402-1.6.1.dist-info → t402-1.7.1.dist-info}/RECORD +27 -11
- {t402-1.6.1.dist-info → t402-1.7.1.dist-info}/WHEEL +0 -0
- {t402-1.6.1.dist-info → t402-1.7.1.dist-info}/entry_points.txt +0 -0
t402/schemes/__init__.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""T402 Payment Scheme Architecture.
|
|
2
|
+
|
|
3
|
+
This package provides the interfaces and registry for managing payment schemes
|
|
4
|
+
in the T402 protocol.
|
|
5
|
+
|
|
6
|
+
Modules:
|
|
7
|
+
interfaces: Abstract interfaces for scheme implementations
|
|
8
|
+
registry: Scheme registration and lookup functionality
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
```python
|
|
12
|
+
from t402.schemes import (
|
|
13
|
+
# Interfaces (for implementing new schemes)
|
|
14
|
+
SchemeNetworkClient,
|
|
15
|
+
SchemeNetworkServer,
|
|
16
|
+
SchemeNetworkFacilitator,
|
|
17
|
+
|
|
18
|
+
# Registry classes
|
|
19
|
+
SchemeRegistry,
|
|
20
|
+
ClientSchemeRegistry,
|
|
21
|
+
ServerSchemeRegistry,
|
|
22
|
+
FacilitatorSchemeRegistry,
|
|
23
|
+
|
|
24
|
+
# Global registry access
|
|
25
|
+
get_client_registry,
|
|
26
|
+
get_server_registry,
|
|
27
|
+
get_facilitator_registry,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Register a scheme
|
|
31
|
+
registry = get_client_registry()
|
|
32
|
+
registry.register("eip155:8453", my_evm_client)
|
|
33
|
+
|
|
34
|
+
# Or create your own registry
|
|
35
|
+
my_registry = ClientSchemeRegistry()
|
|
36
|
+
my_registry.register("eip155:*", my_evm_client) # Wildcard for all EVM
|
|
37
|
+
```
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from t402.schemes.interfaces import (
|
|
41
|
+
# Type aliases
|
|
42
|
+
Price,
|
|
43
|
+
AssetAmount,
|
|
44
|
+
SupportedKindDict,
|
|
45
|
+
# Protocols (duck typing)
|
|
46
|
+
SchemeNetworkClient,
|
|
47
|
+
SchemeNetworkServer,
|
|
48
|
+
SchemeNetworkFacilitator,
|
|
49
|
+
# Abstract Base Classes (inheritance)
|
|
50
|
+
BaseSchemeNetworkClient,
|
|
51
|
+
BaseSchemeNetworkServer,
|
|
52
|
+
BaseSchemeNetworkFacilitator,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
from t402.schemes.registry import (
|
|
56
|
+
# Core registry
|
|
57
|
+
SchemeRegistry,
|
|
58
|
+
# Typed registries
|
|
59
|
+
ClientSchemeRegistry,
|
|
60
|
+
ServerSchemeRegistry,
|
|
61
|
+
FacilitatorSchemeRegistry,
|
|
62
|
+
# Global registry accessors
|
|
63
|
+
get_client_registry,
|
|
64
|
+
get_server_registry,
|
|
65
|
+
get_facilitator_registry,
|
|
66
|
+
reset_global_registries,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# EVM Schemes
|
|
70
|
+
from t402.schemes.evm import (
|
|
71
|
+
ExactEvmClientScheme,
|
|
72
|
+
ExactEvmServerScheme,
|
|
73
|
+
EvmSigner,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# TON Schemes
|
|
77
|
+
from t402.schemes.ton import (
|
|
78
|
+
ExactTonClientScheme,
|
|
79
|
+
ExactTonServerScheme,
|
|
80
|
+
TonSigner,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# TRON Schemes
|
|
84
|
+
from t402.schemes.tron import (
|
|
85
|
+
ExactTronClientScheme,
|
|
86
|
+
ExactTronServerScheme,
|
|
87
|
+
TronSigner,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
__all__ = [
|
|
91
|
+
# Type aliases
|
|
92
|
+
"Price",
|
|
93
|
+
"AssetAmount",
|
|
94
|
+
"SupportedKindDict",
|
|
95
|
+
# Protocols
|
|
96
|
+
"SchemeNetworkClient",
|
|
97
|
+
"SchemeNetworkServer",
|
|
98
|
+
"SchemeNetworkFacilitator",
|
|
99
|
+
# ABCs
|
|
100
|
+
"BaseSchemeNetworkClient",
|
|
101
|
+
"BaseSchemeNetworkServer",
|
|
102
|
+
"BaseSchemeNetworkFacilitator",
|
|
103
|
+
# Registry classes
|
|
104
|
+
"SchemeRegistry",
|
|
105
|
+
"ClientSchemeRegistry",
|
|
106
|
+
"ServerSchemeRegistry",
|
|
107
|
+
"FacilitatorSchemeRegistry",
|
|
108
|
+
# Global registry functions
|
|
109
|
+
"get_client_registry",
|
|
110
|
+
"get_server_registry",
|
|
111
|
+
"get_facilitator_registry",
|
|
112
|
+
"reset_global_registries",
|
|
113
|
+
# EVM Schemes
|
|
114
|
+
"ExactEvmClientScheme",
|
|
115
|
+
"ExactEvmServerScheme",
|
|
116
|
+
"EvmSigner",
|
|
117
|
+
# TON Schemes
|
|
118
|
+
"ExactTonClientScheme",
|
|
119
|
+
"ExactTonServerScheme",
|
|
120
|
+
"TonSigner",
|
|
121
|
+
# TRON Schemes
|
|
122
|
+
"ExactTronClientScheme",
|
|
123
|
+
"ExactTronServerScheme",
|
|
124
|
+
"TronSigner",
|
|
125
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""EVM Blockchain Payment Schemes.
|
|
2
|
+
|
|
3
|
+
This package provides payment scheme implementations for EVM-compatible
|
|
4
|
+
blockchains (Ethereum, Base, Avalanche, etc.).
|
|
5
|
+
|
|
6
|
+
Supported schemes:
|
|
7
|
+
- exact: EIP-3009 TransferWithAuthorization
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from t402.schemes.evm.exact import (
|
|
11
|
+
ExactEvmClientScheme,
|
|
12
|
+
ExactEvmServerScheme,
|
|
13
|
+
EvmSigner,
|
|
14
|
+
create_nonce,
|
|
15
|
+
SCHEME_EXACT,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
# Exact scheme
|
|
20
|
+
"ExactEvmClientScheme",
|
|
21
|
+
"ExactEvmServerScheme",
|
|
22
|
+
"EvmSigner",
|
|
23
|
+
"create_nonce",
|
|
24
|
+
"SCHEME_EXACT",
|
|
25
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""EVM Exact Payment Scheme.
|
|
2
|
+
|
|
3
|
+
This package provides the exact payment scheme implementation for EVM networks
|
|
4
|
+
using EIP-3009 TransferWithAuthorization.
|
|
5
|
+
|
|
6
|
+
The exact scheme allows users to authorize token transfers that can be executed
|
|
7
|
+
by a facilitator, enabling gasless payments.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from t402.schemes.evm.exact.client import (
|
|
11
|
+
ExactEvmClientScheme,
|
|
12
|
+
EvmSigner,
|
|
13
|
+
create_nonce,
|
|
14
|
+
SCHEME_EXACT,
|
|
15
|
+
)
|
|
16
|
+
from t402.schemes.evm.exact.server import (
|
|
17
|
+
ExactEvmServerScheme,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
# Client
|
|
22
|
+
"ExactEvmClientScheme",
|
|
23
|
+
"EvmSigner",
|
|
24
|
+
"create_nonce",
|
|
25
|
+
# Server
|
|
26
|
+
"ExactEvmServerScheme",
|
|
27
|
+
# Constants
|
|
28
|
+
"SCHEME_EXACT",
|
|
29
|
+
]
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""EVM Exact Scheme - Client Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the client-side implementation of the exact payment scheme
|
|
4
|
+
for EVM networks using EIP-3009 TransferWithAuthorization.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import secrets
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Dict, Protocol, Union, runtime_checkable
|
|
12
|
+
|
|
13
|
+
from t402.types import (
|
|
14
|
+
PaymentRequirementsV2,
|
|
15
|
+
T402_VERSION_V1,
|
|
16
|
+
)
|
|
17
|
+
from t402.chains import get_chain_id
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Constants
|
|
21
|
+
SCHEME_EXACT = "exact"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@runtime_checkable
|
|
25
|
+
class EvmSigner(Protocol):
|
|
26
|
+
"""Protocol for EVM signing operations."""
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def address(self) -> str:
|
|
30
|
+
"""Get the signer's address."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def sign_typed_data(
|
|
34
|
+
self,
|
|
35
|
+
domain_data: Dict[str, Any],
|
|
36
|
+
message_types: Dict[str, Any],
|
|
37
|
+
message_data: Dict[str, Any],
|
|
38
|
+
) -> Any:
|
|
39
|
+
"""Sign EIP-712 typed data."""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_nonce() -> bytes:
|
|
44
|
+
"""Create a random 32-byte nonce for authorization signatures."""
|
|
45
|
+
return secrets.token_bytes(32)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ExactEvmClientScheme:
|
|
49
|
+
"""Client scheme for EVM exact payments using EIP-3009.
|
|
50
|
+
|
|
51
|
+
This scheme creates payment payloads using EIP-3009 TransferWithAuthorization,
|
|
52
|
+
which allows gasless token transfers with signature authorization.
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
```python
|
|
56
|
+
from eth_account import Account
|
|
57
|
+
|
|
58
|
+
# Create signer from private key
|
|
59
|
+
account = Account.from_key("0x...")
|
|
60
|
+
|
|
61
|
+
# Create scheme
|
|
62
|
+
scheme = ExactEvmClientScheme(account)
|
|
63
|
+
|
|
64
|
+
# Create payment payload
|
|
65
|
+
payload = await scheme.create_payment_payload(
|
|
66
|
+
t402_version=2,
|
|
67
|
+
requirements=requirements,
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
scheme = SCHEME_EXACT
|
|
73
|
+
caip_family = "eip155:*"
|
|
74
|
+
|
|
75
|
+
def __init__(self, signer: EvmSigner):
|
|
76
|
+
"""Initialize with an EVM signer.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
signer: Any object implementing the EvmSigner protocol
|
|
80
|
+
(e.g., eth_account.Account)
|
|
81
|
+
"""
|
|
82
|
+
self._signer = signer
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def address(self) -> str:
|
|
86
|
+
"""Get the signer's address."""
|
|
87
|
+
return self._signer.address
|
|
88
|
+
|
|
89
|
+
async def create_payment_payload(
|
|
90
|
+
self,
|
|
91
|
+
t402_version: int,
|
|
92
|
+
requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
|
|
93
|
+
) -> Dict[str, Any]:
|
|
94
|
+
"""Create a payment payload for EVM exact scheme.
|
|
95
|
+
|
|
96
|
+
Creates an EIP-3009 TransferWithAuthorization and signs it.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
t402_version: Protocol version (1 or 2)
|
|
100
|
+
requirements: Payment requirements with amount, asset, payTo, etc.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dict with t402Version, scheme, network, and payload containing
|
|
104
|
+
signature and authorization data.
|
|
105
|
+
"""
|
|
106
|
+
# Extract requirements (handle both model and dict)
|
|
107
|
+
if hasattr(requirements, "model_dump"):
|
|
108
|
+
req = requirements.model_dump(by_alias=True)
|
|
109
|
+
else:
|
|
110
|
+
req = dict(requirements)
|
|
111
|
+
|
|
112
|
+
# Get network and chain ID
|
|
113
|
+
network = req.get("network", "")
|
|
114
|
+
chain_id = self._get_chain_id(network)
|
|
115
|
+
|
|
116
|
+
# Get amount - V2 uses 'amount', V1 uses 'maxAmountRequired'
|
|
117
|
+
amount = req.get("amount") or req.get("maxAmountRequired", "0")
|
|
118
|
+
|
|
119
|
+
# Get pay_to address
|
|
120
|
+
pay_to = req.get("payTo") or req.get("pay_to", "")
|
|
121
|
+
|
|
122
|
+
# Get asset address
|
|
123
|
+
asset = req.get("asset", "")
|
|
124
|
+
|
|
125
|
+
# Get timeout
|
|
126
|
+
max_timeout = req.get("maxTimeoutSeconds") or req.get("max_timeout_seconds", 300)
|
|
127
|
+
|
|
128
|
+
# Get EIP-712 domain info from extra
|
|
129
|
+
extra = req.get("extra", {})
|
|
130
|
+
token_name = extra.get("name", "USD Coin")
|
|
131
|
+
token_version = extra.get("version", "2")
|
|
132
|
+
|
|
133
|
+
# Create authorization
|
|
134
|
+
nonce = create_nonce()
|
|
135
|
+
now = int(time.time())
|
|
136
|
+
valid_after = str(now - 60) # 60 seconds buffer
|
|
137
|
+
valid_before = str(now + max_timeout)
|
|
138
|
+
|
|
139
|
+
authorization = {
|
|
140
|
+
"from": self._signer.address,
|
|
141
|
+
"to": pay_to,
|
|
142
|
+
"value": str(amount),
|
|
143
|
+
"validAfter": valid_after,
|
|
144
|
+
"validBefore": valid_before,
|
|
145
|
+
"nonce": nonce,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Sign the authorization
|
|
149
|
+
signature = self._sign_authorization(
|
|
150
|
+
authorization=authorization,
|
|
151
|
+
chain_id=chain_id,
|
|
152
|
+
asset_address=asset,
|
|
153
|
+
token_name=token_name,
|
|
154
|
+
token_version=token_version,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Convert nonce to hex string
|
|
158
|
+
authorization["nonce"] = f"0x{nonce.hex()}"
|
|
159
|
+
|
|
160
|
+
# Build payload based on version
|
|
161
|
+
if t402_version == T402_VERSION_V1:
|
|
162
|
+
return {
|
|
163
|
+
"t402Version": t402_version,
|
|
164
|
+
"scheme": self.scheme,
|
|
165
|
+
"network": network,
|
|
166
|
+
"payload": {
|
|
167
|
+
"signature": signature,
|
|
168
|
+
"authorization": authorization,
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
else:
|
|
172
|
+
# V2 - return just the partial payload
|
|
173
|
+
return {
|
|
174
|
+
"t402Version": t402_version,
|
|
175
|
+
"payload": {
|
|
176
|
+
"signature": signature,
|
|
177
|
+
"authorization": authorization,
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
def _get_chain_id(self, network: str) -> int:
|
|
182
|
+
"""Get chain ID from network identifier.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
network: Network identifier (CAIP-2 or legacy format)
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Chain ID as integer
|
|
189
|
+
"""
|
|
190
|
+
# Handle CAIP-2 format (eip155:8453)
|
|
191
|
+
if network.startswith("eip155:"):
|
|
192
|
+
return int(network.split(":")[1])
|
|
193
|
+
|
|
194
|
+
# Handle legacy format
|
|
195
|
+
try:
|
|
196
|
+
return get_chain_id(network)
|
|
197
|
+
except (KeyError, ValueError):
|
|
198
|
+
raise ValueError(f"Unknown network: {network}")
|
|
199
|
+
|
|
200
|
+
def _sign_authorization(
|
|
201
|
+
self,
|
|
202
|
+
authorization: Dict[str, Any],
|
|
203
|
+
chain_id: int,
|
|
204
|
+
asset_address: str,
|
|
205
|
+
token_name: str,
|
|
206
|
+
token_version: str,
|
|
207
|
+
) -> str:
|
|
208
|
+
"""Sign an EIP-3009 authorization.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
authorization: Authorization data
|
|
212
|
+
chain_id: EVM chain ID
|
|
213
|
+
asset_address: Token contract address
|
|
214
|
+
token_name: Token name for EIP-712 domain
|
|
215
|
+
token_version: Token version for EIP-712 domain
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Signature as hex string
|
|
219
|
+
"""
|
|
220
|
+
# Get nonce as bytes
|
|
221
|
+
nonce = authorization["nonce"]
|
|
222
|
+
if isinstance(nonce, str):
|
|
223
|
+
nonce = bytes.fromhex(nonce.replace("0x", ""))
|
|
224
|
+
|
|
225
|
+
# Build EIP-712 typed data
|
|
226
|
+
domain = {
|
|
227
|
+
"name": token_name,
|
|
228
|
+
"version": token_version,
|
|
229
|
+
"chainId": chain_id,
|
|
230
|
+
"verifyingContract": asset_address,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
types = {
|
|
234
|
+
"TransferWithAuthorization": [
|
|
235
|
+
{"name": "from", "type": "address"},
|
|
236
|
+
{"name": "to", "type": "address"},
|
|
237
|
+
{"name": "value", "type": "uint256"},
|
|
238
|
+
{"name": "validAfter", "type": "uint256"},
|
|
239
|
+
{"name": "validBefore", "type": "uint256"},
|
|
240
|
+
{"name": "nonce", "type": "bytes32"},
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
message = {
|
|
245
|
+
"from": authorization["from"],
|
|
246
|
+
"to": authorization["to"],
|
|
247
|
+
"value": int(authorization["value"]),
|
|
248
|
+
"validAfter": int(authorization["validAfter"]),
|
|
249
|
+
"validBefore": int(authorization["validBefore"]),
|
|
250
|
+
"nonce": nonce,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Sign
|
|
254
|
+
signed = self._signer.sign_typed_data(
|
|
255
|
+
domain_data=domain,
|
|
256
|
+
message_types=types,
|
|
257
|
+
message_data=message,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Extract signature
|
|
261
|
+
signature = signed.signature.hex()
|
|
262
|
+
if not signature.startswith("0x"):
|
|
263
|
+
signature = f"0x{signature}"
|
|
264
|
+
|
|
265
|
+
return signature
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""EVM Exact Scheme - Server Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the server-side implementation of the exact payment scheme
|
|
4
|
+
for EVM networks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from decimal import Decimal
|
|
10
|
+
from typing import Any, Dict, List, Union
|
|
11
|
+
|
|
12
|
+
from t402.types import (
|
|
13
|
+
PaymentRequirementsV2,
|
|
14
|
+
Network,
|
|
15
|
+
)
|
|
16
|
+
from t402.schemes.interfaces import AssetAmount, SupportedKindDict
|
|
17
|
+
from t402.chains import (
|
|
18
|
+
get_chain_id,
|
|
19
|
+
get_token_decimals,
|
|
20
|
+
get_token_name,
|
|
21
|
+
get_token_version,
|
|
22
|
+
get_default_token_address,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Constants
|
|
27
|
+
SCHEME_EXACT = "exact"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ExactEvmServerScheme:
|
|
31
|
+
"""Server scheme for EVM exact payments.
|
|
32
|
+
|
|
33
|
+
Handles parsing user-friendly prices and enhancing payment requirements
|
|
34
|
+
with EIP-712 domain information needed for clients.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
```python
|
|
38
|
+
scheme = ExactEvmServerScheme()
|
|
39
|
+
|
|
40
|
+
# Parse price
|
|
41
|
+
asset_amount = await scheme.parse_price("$0.10", "eip155:8453")
|
|
42
|
+
# Returns: {"amount": "100000", "asset": "0x833589...", "extra": {...}}
|
|
43
|
+
|
|
44
|
+
# Enhance requirements
|
|
45
|
+
enhanced = await scheme.enhance_requirements(
|
|
46
|
+
requirements,
|
|
47
|
+
supported_kind,
|
|
48
|
+
facilitator_extensions,
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
scheme = SCHEME_EXACT
|
|
54
|
+
caip_family = "eip155:*"
|
|
55
|
+
|
|
56
|
+
async def parse_price(
|
|
57
|
+
self,
|
|
58
|
+
price: Union[str, int, float, Dict[str, Any]],
|
|
59
|
+
network: Network,
|
|
60
|
+
) -> AssetAmount:
|
|
61
|
+
"""Parse a user-friendly price to atomic amount and asset.
|
|
62
|
+
|
|
63
|
+
Supports:
|
|
64
|
+
- String with $ prefix: "$0.10" -> 100000 (6 decimals)
|
|
65
|
+
- String without prefix: "0.10" -> 100000
|
|
66
|
+
- Integer/float: 0.10 -> 100000
|
|
67
|
+
- Dict (TokenAmount): {"amount": "100000", "asset": "0x..."}
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
price: User-friendly price
|
|
71
|
+
network: Network identifier (CAIP-2 format)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
AssetAmount dict with amount, asset, and extra metadata
|
|
75
|
+
"""
|
|
76
|
+
chain_id = self._get_chain_id(network)
|
|
77
|
+
|
|
78
|
+
# Handle dict (already in TokenAmount format)
|
|
79
|
+
if isinstance(price, dict):
|
|
80
|
+
return {
|
|
81
|
+
"amount": str(price.get("amount", "0")),
|
|
82
|
+
"asset": price.get("asset", ""),
|
|
83
|
+
"extra": price.get("extra", {}),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Get default token (USDC) for the network
|
|
87
|
+
chain_id_str = str(chain_id)
|
|
88
|
+
asset_address = get_default_token_address(chain_id_str, "usdc")
|
|
89
|
+
decimals = get_token_decimals(chain_id_str, asset_address)
|
|
90
|
+
|
|
91
|
+
# Parse price string/number
|
|
92
|
+
if isinstance(price, str):
|
|
93
|
+
if price.startswith("$"):
|
|
94
|
+
price = price[1:]
|
|
95
|
+
amount_decimal = Decimal(price)
|
|
96
|
+
else:
|
|
97
|
+
amount_decimal = Decimal(str(price))
|
|
98
|
+
|
|
99
|
+
# Convert to atomic units
|
|
100
|
+
atomic_amount = int(amount_decimal * Decimal(10 ** decimals))
|
|
101
|
+
|
|
102
|
+
# Get EIP-712 domain info
|
|
103
|
+
extra = {
|
|
104
|
+
"name": get_token_name(chain_id_str, asset_address),
|
|
105
|
+
"version": get_token_version(chain_id_str, asset_address),
|
|
106
|
+
"decimals": decimals,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"amount": str(atomic_amount),
|
|
111
|
+
"asset": asset_address,
|
|
112
|
+
"extra": extra,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async def enhance_requirements(
|
|
116
|
+
self,
|
|
117
|
+
requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
|
|
118
|
+
supported_kind: SupportedKindDict,
|
|
119
|
+
facilitator_extensions: List[str],
|
|
120
|
+
) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
|
|
121
|
+
"""Enhance payment requirements with EVM-specific metadata.
|
|
122
|
+
|
|
123
|
+
Adds EIP-712 domain information to the extra field so clients
|
|
124
|
+
can properly sign the authorization.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
requirements: Base payment requirements
|
|
128
|
+
supported_kind: Matched SupportedKind from facilitator
|
|
129
|
+
facilitator_extensions: Extensions supported by facilitator
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Enhanced requirements with EIP-712 domain in extra
|
|
133
|
+
"""
|
|
134
|
+
# Convert to dict for modification
|
|
135
|
+
if hasattr(requirements, "model_dump"):
|
|
136
|
+
req = requirements.model_dump(by_alias=True)
|
|
137
|
+
else:
|
|
138
|
+
req = dict(requirements)
|
|
139
|
+
|
|
140
|
+
network = req.get("network", "")
|
|
141
|
+
asset = req.get("asset", "")
|
|
142
|
+
|
|
143
|
+
# Get chain ID as string
|
|
144
|
+
chain_id = str(self._get_chain_id(network))
|
|
145
|
+
|
|
146
|
+
# Ensure extra exists
|
|
147
|
+
if "extra" not in req or req["extra"] is None:
|
|
148
|
+
req["extra"] = {}
|
|
149
|
+
|
|
150
|
+
# Add EIP-712 domain info if not present
|
|
151
|
+
if "name" not in req["extra"]:
|
|
152
|
+
req["extra"]["name"] = get_token_name(chain_id, asset)
|
|
153
|
+
if "version" not in req["extra"]:
|
|
154
|
+
req["extra"]["version"] = get_token_version(chain_id, asset)
|
|
155
|
+
|
|
156
|
+
# Add facilitator extra data if available
|
|
157
|
+
if supported_kind.get("extra"):
|
|
158
|
+
for key, value in supported_kind["extra"].items():
|
|
159
|
+
if key not in req["extra"]:
|
|
160
|
+
req["extra"][key] = value
|
|
161
|
+
|
|
162
|
+
return req
|
|
163
|
+
|
|
164
|
+
def _get_chain_id(self, network: str) -> int:
|
|
165
|
+
"""Get chain ID from network identifier.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
network: Network identifier (CAIP-2 or legacy format)
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Chain ID as integer
|
|
172
|
+
"""
|
|
173
|
+
# Handle CAIP-2 format (eip155:8453)
|
|
174
|
+
if network.startswith("eip155:"):
|
|
175
|
+
return int(network.split(":")[1])
|
|
176
|
+
|
|
177
|
+
# Handle legacy format
|
|
178
|
+
try:
|
|
179
|
+
return get_chain_id(network)
|
|
180
|
+
except (KeyError, ValueError):
|
|
181
|
+
raise ValueError(f"Unknown network: {network}")
|