t402 1.6.1__py3-none-any.whl → 1.9.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 +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 +164 -0
- t402/schemes/evm/__init__.py +46 -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/evm/upto/__init__.py +58 -0
- t402/schemes/evm/upto/client.py +240 -0
- t402/schemes/evm/upto/types.py +305 -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/schemes/upto/__init__.py +80 -0
- t402/schemes/upto/types.py +376 -0
- t402/types.py +178 -8
- {t402-1.6.1.dist-info → t402-1.9.0.dist-info}/METADATA +1 -1
- {t402-1.6.1.dist-info → t402-1.9.0.dist-info}/RECORD +32 -11
- {t402-1.6.1.dist-info → t402-1.9.0.dist-info}/WHEEL +0 -0
- {t402-1.6.1.dist-info → t402-1.9.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""EVM Up-To Payment Scheme.
|
|
2
|
+
|
|
3
|
+
This package provides the upto payment scheme implementation for EVM networks
|
|
4
|
+
using EIP-2612 Permit for gasless token approvals.
|
|
5
|
+
|
|
6
|
+
The upto scheme allows clients to authorize a maximum amount that can be
|
|
7
|
+
settled later based on actual usage.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from t402.schemes.evm.upto.types import (
|
|
11
|
+
# Type definitions
|
|
12
|
+
PERMIT_TYPES,
|
|
13
|
+
PERMIT_DOMAIN_TYPES,
|
|
14
|
+
# Models
|
|
15
|
+
PermitSignature,
|
|
16
|
+
PermitAuthorization,
|
|
17
|
+
UptoEIP2612Payload,
|
|
18
|
+
UptoCompactPayload,
|
|
19
|
+
UptoEvmExtra,
|
|
20
|
+
UptoEvmSettlement,
|
|
21
|
+
UptoEvmUsageDetails,
|
|
22
|
+
# Type guards
|
|
23
|
+
is_eip2612_payload,
|
|
24
|
+
# Helper functions
|
|
25
|
+
create_permit_domain,
|
|
26
|
+
create_permit_message,
|
|
27
|
+
payload_from_dict,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
from t402.schemes.evm.upto.client import (
|
|
31
|
+
UptoEvmClientScheme,
|
|
32
|
+
create_payment_nonce,
|
|
33
|
+
SCHEME_UPTO,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Constants
|
|
38
|
+
"SCHEME_UPTO",
|
|
39
|
+
"PERMIT_TYPES",
|
|
40
|
+
"PERMIT_DOMAIN_TYPES",
|
|
41
|
+
# Client
|
|
42
|
+
"UptoEvmClientScheme",
|
|
43
|
+
"create_payment_nonce",
|
|
44
|
+
# Types
|
|
45
|
+
"PermitSignature",
|
|
46
|
+
"PermitAuthorization",
|
|
47
|
+
"UptoEIP2612Payload",
|
|
48
|
+
"UptoCompactPayload",
|
|
49
|
+
"UptoEvmExtra",
|
|
50
|
+
"UptoEvmSettlement",
|
|
51
|
+
"UptoEvmUsageDetails",
|
|
52
|
+
# Type guards
|
|
53
|
+
"is_eip2612_payload",
|
|
54
|
+
# Helper functions
|
|
55
|
+
"create_permit_domain",
|
|
56
|
+
"create_permit_message",
|
|
57
|
+
"payload_from_dict",
|
|
58
|
+
]
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""EVM Up-To Scheme - Client Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the client-side implementation of the upto payment scheme
|
|
4
|
+
for EVM networks using EIP-2612 Permit.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import secrets
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Dict, Optional, Union
|
|
12
|
+
|
|
13
|
+
from t402.types import PaymentRequirementsV2
|
|
14
|
+
from t402.chains import get_chain_id
|
|
15
|
+
from t402.schemes.evm.exact.client import EvmSigner
|
|
16
|
+
from t402.schemes.upto.types import UptoPaymentRequirements
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Constants
|
|
20
|
+
SCHEME_UPTO = "upto"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_payment_nonce() -> bytes:
|
|
24
|
+
"""Create a random 32-byte payment nonce."""
|
|
25
|
+
return secrets.token_bytes(32)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UptoEvmClientScheme:
|
|
29
|
+
"""Client scheme for EVM upto payments using EIP-2612.
|
|
30
|
+
|
|
31
|
+
This scheme creates payment payloads using EIP-2612 Permit,
|
|
32
|
+
which allows gasless token approvals for up-to payments.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
```python
|
|
36
|
+
from eth_account import Account
|
|
37
|
+
|
|
38
|
+
# Create signer from private key
|
|
39
|
+
account = Account.from_key("0x...")
|
|
40
|
+
|
|
41
|
+
# Create scheme
|
|
42
|
+
scheme = UptoEvmClientScheme(account)
|
|
43
|
+
|
|
44
|
+
# Create payment payload
|
|
45
|
+
payload = await scheme.create_payment_payload(
|
|
46
|
+
t402_version=2,
|
|
47
|
+
requirements=requirements,
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
scheme = SCHEME_UPTO
|
|
53
|
+
caip_family = "eip155:*"
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
signer: EvmSigner,
|
|
58
|
+
router_address: Optional[str] = None,
|
|
59
|
+
):
|
|
60
|
+
"""Initialize with an EVM signer.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
signer: Any object implementing the EvmSigner protocol
|
|
64
|
+
router_address: Optional default router contract address
|
|
65
|
+
"""
|
|
66
|
+
self._signer = signer
|
|
67
|
+
self._router_address = router_address
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def address(self) -> str:
|
|
71
|
+
"""Get the signer's address."""
|
|
72
|
+
return self._signer.address
|
|
73
|
+
|
|
74
|
+
async def create_payment_payload(
|
|
75
|
+
self,
|
|
76
|
+
t402_version: int,
|
|
77
|
+
requirements: Union[UptoPaymentRequirements, PaymentRequirementsV2, Dict[str, Any]],
|
|
78
|
+
) -> Dict[str, Any]:
|
|
79
|
+
"""Create a payment payload for EVM upto scheme.
|
|
80
|
+
|
|
81
|
+
Creates an EIP-2612 Permit authorization and signs it.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
t402_version: Protocol version (1 or 2)
|
|
85
|
+
requirements: Payment requirements with maxAmount, asset, payTo, etc.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dict with t402Version and payload containing permit signature
|
|
89
|
+
and authorization data.
|
|
90
|
+
"""
|
|
91
|
+
# Extract requirements (handle both model and dict)
|
|
92
|
+
if hasattr(requirements, "model_dump"):
|
|
93
|
+
req = requirements.model_dump(by_alias=True)
|
|
94
|
+
else:
|
|
95
|
+
req = dict(requirements)
|
|
96
|
+
|
|
97
|
+
# Get network and chain ID
|
|
98
|
+
network = req.get("network", "")
|
|
99
|
+
chain_id = self._get_chain_id(network)
|
|
100
|
+
|
|
101
|
+
# Get maxAmount for upto scheme
|
|
102
|
+
max_amount = req.get("maxAmount") or req.get("max_amount", "0")
|
|
103
|
+
|
|
104
|
+
# Get router/spender address
|
|
105
|
+
extra = req.get("extra", {})
|
|
106
|
+
router_address = (
|
|
107
|
+
extra.get("routerAddress")
|
|
108
|
+
or extra.get("router_address")
|
|
109
|
+
or self._router_address
|
|
110
|
+
or req.get("payTo") # Fallback to payTo
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Get asset address
|
|
114
|
+
asset = req.get("asset", "")
|
|
115
|
+
|
|
116
|
+
# Get timeout
|
|
117
|
+
max_timeout = req.get("maxTimeoutSeconds") or req.get("max_timeout_seconds", 300)
|
|
118
|
+
|
|
119
|
+
# Get EIP-712 domain info
|
|
120
|
+
token_name = extra.get("name", "USD Coin")
|
|
121
|
+
token_version = extra.get("version", "2")
|
|
122
|
+
|
|
123
|
+
# Create payment nonce
|
|
124
|
+
payment_nonce = create_payment_nonce()
|
|
125
|
+
|
|
126
|
+
# Calculate deadline
|
|
127
|
+
deadline = int(time.time()) + max_timeout
|
|
128
|
+
|
|
129
|
+
# Get permit nonce from token contract (would need RPC call in production)
|
|
130
|
+
# For now, use 0 as placeholder - real implementation needs contract call
|
|
131
|
+
permit_nonce = 0
|
|
132
|
+
|
|
133
|
+
# Create authorization
|
|
134
|
+
authorization = {
|
|
135
|
+
"owner": self._signer.address,
|
|
136
|
+
"spender": router_address,
|
|
137
|
+
"value": str(max_amount),
|
|
138
|
+
"deadline": str(deadline),
|
|
139
|
+
"nonce": permit_nonce,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Sign the permit
|
|
143
|
+
signature = self._sign_permit(
|
|
144
|
+
authorization=authorization,
|
|
145
|
+
chain_id=chain_id,
|
|
146
|
+
asset_address=asset,
|
|
147
|
+
token_name=token_name,
|
|
148
|
+
token_version=token_version,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Build payload
|
|
152
|
+
payload = {
|
|
153
|
+
"signature": signature,
|
|
154
|
+
"authorization": authorization,
|
|
155
|
+
"paymentNonce": f"0x{payment_nonce.hex()}",
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"t402Version": t402_version,
|
|
160
|
+
"payload": payload,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def _get_chain_id(self, network: str) -> int:
|
|
164
|
+
"""Get chain ID from network identifier."""
|
|
165
|
+
if network.startswith("eip155:"):
|
|
166
|
+
return int(network.split(":")[1])
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
return get_chain_id(network)
|
|
170
|
+
except (KeyError, ValueError):
|
|
171
|
+
raise ValueError(f"Unknown network: {network}")
|
|
172
|
+
|
|
173
|
+
def _sign_permit(
|
|
174
|
+
self,
|
|
175
|
+
authorization: Dict[str, Any],
|
|
176
|
+
chain_id: int,
|
|
177
|
+
asset_address: str,
|
|
178
|
+
token_name: str,
|
|
179
|
+
token_version: str,
|
|
180
|
+
) -> Dict[str, Any]:
|
|
181
|
+
"""Sign an EIP-2612 Permit.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
authorization: Permit authorization data
|
|
185
|
+
chain_id: EVM chain ID
|
|
186
|
+
asset_address: Token contract address
|
|
187
|
+
token_name: Token name for EIP-712 domain
|
|
188
|
+
token_version: Token version for EIP-712 domain
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Signature as dict with v, r, s components
|
|
192
|
+
"""
|
|
193
|
+
# Build EIP-712 typed data
|
|
194
|
+
domain = {
|
|
195
|
+
"name": token_name,
|
|
196
|
+
"version": token_version,
|
|
197
|
+
"chainId": chain_id,
|
|
198
|
+
"verifyingContract": asset_address,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
types = {
|
|
202
|
+
"Permit": [
|
|
203
|
+
{"name": "owner", "type": "address"},
|
|
204
|
+
{"name": "spender", "type": "address"},
|
|
205
|
+
{"name": "value", "type": "uint256"},
|
|
206
|
+
{"name": "nonce", "type": "uint256"},
|
|
207
|
+
{"name": "deadline", "type": "uint256"},
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
message = {
|
|
212
|
+
"owner": authorization["owner"],
|
|
213
|
+
"spender": authorization["spender"],
|
|
214
|
+
"value": int(authorization["value"]),
|
|
215
|
+
"nonce": authorization["nonce"],
|
|
216
|
+
"deadline": int(authorization["deadline"]),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# Sign
|
|
220
|
+
signed = self._signer.sign_typed_data(
|
|
221
|
+
domain_data=domain,
|
|
222
|
+
message_types=types,
|
|
223
|
+
message_data=message,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Extract signature components
|
|
227
|
+
sig_hex = signed.signature.hex()
|
|
228
|
+
if sig_hex.startswith("0x"):
|
|
229
|
+
sig_hex = sig_hex[2:]
|
|
230
|
+
|
|
231
|
+
# Split into v, r, s
|
|
232
|
+
r = f"0x{sig_hex[:64]}"
|
|
233
|
+
s = f"0x{sig_hex[64:128]}"
|
|
234
|
+
v = int(sig_hex[128:], 16) if len(sig_hex) > 128 else 27
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
"v": v,
|
|
238
|
+
"r": r,
|
|
239
|
+
"s": s,
|
|
240
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
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(description="Combined EIP-2612 permit signature (65 bytes hex)")
|
|
102
|
+
authorization: PermitAuthorization = Field(description="Permit parameters")
|
|
103
|
+
payment_nonce: str = Field(
|
|
104
|
+
alias="paymentNonce",
|
|
105
|
+
description="Unique nonce to prevent replay attacks",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
model_config = ConfigDict(
|
|
109
|
+
alias_generator=to_camel,
|
|
110
|
+
populate_by_name=True,
|
|
111
|
+
from_attributes=True,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class UptoEvmExtra(BaseModel):
|
|
116
|
+
"""EVM-specific extra fields for the upto scheme."""
|
|
117
|
+
|
|
118
|
+
name: str = Field(description="EIP-712 domain name (token name)")
|
|
119
|
+
version: str = Field(description="EIP-712 domain version")
|
|
120
|
+
router_address: Optional[str] = Field(
|
|
121
|
+
default=None,
|
|
122
|
+
alias="routerAddress",
|
|
123
|
+
description="Upto router contract address",
|
|
124
|
+
)
|
|
125
|
+
unit: Optional[str] = Field(
|
|
126
|
+
default=None,
|
|
127
|
+
description="Billing unit (e.g., 'token', 'request')",
|
|
128
|
+
)
|
|
129
|
+
unit_price: Optional[str] = Field(
|
|
130
|
+
default=None,
|
|
131
|
+
alias="unitPrice",
|
|
132
|
+
description="Price per unit in smallest denomination",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
model_config = ConfigDict(
|
|
136
|
+
alias_generator=to_camel,
|
|
137
|
+
populate_by_name=True,
|
|
138
|
+
from_attributes=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class UptoEvmSettlement(BaseModel):
|
|
143
|
+
"""EVM-specific settlement request."""
|
|
144
|
+
|
|
145
|
+
settle_amount: str = Field(
|
|
146
|
+
alias="settleAmount",
|
|
147
|
+
description="Actual amount to settle",
|
|
148
|
+
)
|
|
149
|
+
usage_details: Optional["UptoEvmUsageDetails"] = Field(
|
|
150
|
+
default=None,
|
|
151
|
+
alias="usageDetails",
|
|
152
|
+
description="Optional usage information",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
model_config = ConfigDict(
|
|
156
|
+
alias_generator=to_camel,
|
|
157
|
+
populate_by_name=True,
|
|
158
|
+
from_attributes=True,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class UptoEvmUsageDetails(BaseModel):
|
|
163
|
+
"""Usage details for EVM settlement."""
|
|
164
|
+
|
|
165
|
+
units_consumed: Optional[int] = Field(
|
|
166
|
+
default=None,
|
|
167
|
+
alias="unitsConsumed",
|
|
168
|
+
description="Number of units consumed",
|
|
169
|
+
)
|
|
170
|
+
unit_price: Optional[str] = Field(
|
|
171
|
+
default=None,
|
|
172
|
+
alias="unitPrice",
|
|
173
|
+
description="Price per unit used",
|
|
174
|
+
)
|
|
175
|
+
unit_type: Optional[str] = Field(
|
|
176
|
+
default=None,
|
|
177
|
+
alias="unitType",
|
|
178
|
+
description="Type of unit",
|
|
179
|
+
)
|
|
180
|
+
start_time: Optional[int] = Field(
|
|
181
|
+
default=None,
|
|
182
|
+
alias="startTime",
|
|
183
|
+
description="Start timestamp",
|
|
184
|
+
)
|
|
185
|
+
end_time: Optional[int] = Field(
|
|
186
|
+
default=None,
|
|
187
|
+
alias="endTime",
|
|
188
|
+
description="End timestamp",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
model_config = ConfigDict(
|
|
192
|
+
alias_generator=to_camel,
|
|
193
|
+
populate_by_name=True,
|
|
194
|
+
from_attributes=True,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# Update forward reference
|
|
199
|
+
UptoEvmSettlement.model_rebuild()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def is_eip2612_payload(data: Dict[str, Any]) -> bool:
|
|
203
|
+
"""Check if the given data represents an EIP-2612 permit payload.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
data: Dictionary to check
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
True if data has the correct EIP-2612 structure
|
|
210
|
+
"""
|
|
211
|
+
if not isinstance(data, dict):
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
sig = data.get("signature")
|
|
215
|
+
auth = data.get("authorization")
|
|
216
|
+
|
|
217
|
+
if not sig or not auth:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
# Check signature structure (should be object with v, r, s)
|
|
221
|
+
if not isinstance(sig, dict):
|
|
222
|
+
return False
|
|
223
|
+
if not all(k in sig for k in ["v", "r", "s"]):
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
# Check authorization structure
|
|
227
|
+
if not isinstance(auth, dict):
|
|
228
|
+
return False
|
|
229
|
+
required_auth_fields = ["owner", "spender", "value", "deadline"]
|
|
230
|
+
if not all(k in auth for k in required_auth_fields):
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def create_permit_domain(
|
|
237
|
+
name: str,
|
|
238
|
+
version: str,
|
|
239
|
+
chain_id: int,
|
|
240
|
+
token_address: str,
|
|
241
|
+
) -> Dict[str, Any]:
|
|
242
|
+
"""Create an EIP-712 domain for permit signing.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
name: Token name
|
|
246
|
+
version: Token version
|
|
247
|
+
chain_id: Chain ID
|
|
248
|
+
token_address: Token contract address
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
EIP-712 domain dictionary
|
|
252
|
+
"""
|
|
253
|
+
return {
|
|
254
|
+
"name": name,
|
|
255
|
+
"version": version,
|
|
256
|
+
"chainId": chain_id,
|
|
257
|
+
"verifyingContract": token_address,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def create_permit_message(authorization: PermitAuthorization) -> Dict[str, Any]:
|
|
262
|
+
"""Create an EIP-712 message for permit signing.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
authorization: Permit authorization parameters
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
EIP-712 message dictionary
|
|
269
|
+
"""
|
|
270
|
+
return {
|
|
271
|
+
"owner": authorization.owner,
|
|
272
|
+
"spender": authorization.spender,
|
|
273
|
+
"value": int(authorization.value),
|
|
274
|
+
"nonce": authorization.nonce,
|
|
275
|
+
"deadline": int(authorization.deadline),
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def payload_from_dict(data: Dict[str, Any]) -> UptoEIP2612Payload:
|
|
280
|
+
"""Create an UptoEIP2612Payload from a dictionary.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
data: Dictionary containing payload data
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
UptoEIP2612Payload instance
|
|
287
|
+
"""
|
|
288
|
+
sig_data = data.get("signature", {})
|
|
289
|
+
auth_data = data.get("authorization", {})
|
|
290
|
+
|
|
291
|
+
return UptoEIP2612Payload(
|
|
292
|
+
signature=PermitSignature(
|
|
293
|
+
v=sig_data.get("v", 0),
|
|
294
|
+
r=sig_data.get("r", ""),
|
|
295
|
+
s=sig_data.get("s", ""),
|
|
296
|
+
),
|
|
297
|
+
authorization=PermitAuthorization(
|
|
298
|
+
owner=auth_data.get("owner", ""),
|
|
299
|
+
spender=auth_data.get("spender", ""),
|
|
300
|
+
value=auth_data.get("value", ""),
|
|
301
|
+
deadline=auth_data.get("deadline", ""),
|
|
302
|
+
nonce=auth_data.get("nonce", 0),
|
|
303
|
+
),
|
|
304
|
+
payment_nonce=data.get("paymentNonce", ""),
|
|
305
|
+
)
|