t402 1.9.1__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 +1 -1
- t402/a2a/__init__.py +73 -0
- t402/a2a/helpers.py +158 -0
- t402/a2a/types.py +145 -0
- t402/bridge/constants.py +1 -1
- t402/django/__init__.py +42 -0
- t402/django/middleware.py +596 -0
- t402/errors.py +213 -0
- t402/facilitator.py +125 -0
- t402/mcp/constants.py +3 -6
- t402/mcp/server.py +428 -44
- 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/schemes/__init__.py +19 -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 +1 -1
- t402/schemes/evm/exact_legacy/server.py +1 -1
- t402/schemes/near/__init__.py +25 -0
- t402/schemes/near/upto/__init__.py +54 -0
- t402/schemes/near/upto/types.py +272 -0
- t402/schemes/svm/__init__.py +15 -0
- t402/schemes/svm/upto/__init__.py +23 -0
- t402/schemes/svm/upto/types.py +193 -0
- t402/schemes/ton/__init__.py +15 -0
- t402/schemes/ton/upto/__init__.py +31 -0
- t402/schemes/ton/upto/types.py +215 -0
- t402/schemes/tron/__init__.py +21 -4
- t402/schemes/tron/upto/__init__.py +30 -0
- t402/schemes/tron/upto/types.py +213 -0
- t402/starlette/__init__.py +38 -0
- t402/starlette/middleware.py +522 -0
- t402/ton.py +1 -1
- t402/ton_paywall_template.py +1 -1
- t402/types.py +100 -2
- t402/wdk/chains.py +1 -1
- {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/METADATA +3 -3
- {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/RECORD +51 -20
- {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/WHEEL +0 -0
- {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""Cosmos/Noble blockchain types for the T402 protocol.
|
|
2
|
+
|
|
3
|
+
This module defines the data types used by the Cosmos exact-direct payment scheme,
|
|
4
|
+
including transaction structures, payload types, and signer protocols.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
13
|
+
from pydantic.alias_generators import to_camel
|
|
14
|
+
|
|
15
|
+
from t402.schemes.cosmos.constants import MSG_TYPE_SEND
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# Signer Protocols
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@runtime_checkable
|
|
24
|
+
class ClientCosmosSigner(Protocol):
|
|
25
|
+
"""Protocol for Cosmos client-side signing operations.
|
|
26
|
+
|
|
27
|
+
Implementations should handle key management and transaction signing
|
|
28
|
+
for the Cosmos blockchain.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
class MyCosmosSigner:
|
|
33
|
+
def address(self) -> str:
|
|
34
|
+
return "noble1abc..."
|
|
35
|
+
|
|
36
|
+
async def send_tokens(
|
|
37
|
+
self, network: str, to: str, amount: str, denom: str
|
|
38
|
+
) -> str:
|
|
39
|
+
# Build, sign, and send the transaction
|
|
40
|
+
return "tx_hash_here"
|
|
41
|
+
```
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def address(self) -> str:
|
|
45
|
+
"""Get the signer's Cosmos address.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The bech32-encoded address (e.g., "noble1abc...").
|
|
49
|
+
"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
async def send_tokens(
|
|
53
|
+
self,
|
|
54
|
+
network: str,
|
|
55
|
+
to: str,
|
|
56
|
+
amount: str,
|
|
57
|
+
denom: str,
|
|
58
|
+
) -> str:
|
|
59
|
+
"""Send tokens and return the transaction hash.
|
|
60
|
+
|
|
61
|
+
This is a pre-signed direct transfer (exact-direct scheme).
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
network: The CAIP-2 network identifier (e.g., "cosmos:noble-1").
|
|
65
|
+
to: The recipient's bech32 address.
|
|
66
|
+
amount: The amount in atomic units (e.g., "1000000" for 1 USDC).
|
|
67
|
+
denom: The token denomination (e.g., "uusdc").
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Transaction hash string on success.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
Exception: If the transaction fails.
|
|
74
|
+
"""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@runtime_checkable
|
|
79
|
+
class FacilitatorCosmosSigner(Protocol):
|
|
80
|
+
"""Protocol for Cosmos facilitator-side operations.
|
|
81
|
+
|
|
82
|
+
Implementations should handle transaction querying and balance lookups
|
|
83
|
+
for the Cosmos blockchain.
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
```python
|
|
87
|
+
class MyCosmosRPC:
|
|
88
|
+
def get_addresses(self, network: str) -> List[str]:
|
|
89
|
+
return ["noble1facilitator..."]
|
|
90
|
+
|
|
91
|
+
async def query_transaction(
|
|
92
|
+
self, network: str, tx_hash: str
|
|
93
|
+
) -> TransactionResult:
|
|
94
|
+
# Query the Cosmos REST API for the transaction
|
|
95
|
+
return TransactionResult(...)
|
|
96
|
+
|
|
97
|
+
async def get_balance(
|
|
98
|
+
self, network: str, address: str, denom: str
|
|
99
|
+
) -> str:
|
|
100
|
+
return "1000000"
|
|
101
|
+
```
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def get_addresses(self, network: str) -> List[str]:
|
|
105
|
+
"""Get the facilitator's Cosmos addresses for a network.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
network: The CAIP-2 network identifier.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of bech32-encoded addresses.
|
|
112
|
+
"""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
async def query_transaction(
|
|
116
|
+
self,
|
|
117
|
+
network: str,
|
|
118
|
+
tx_hash: str,
|
|
119
|
+
) -> TransactionResult:
|
|
120
|
+
"""Query a transaction by hash from the Cosmos REST API.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
network: The CAIP-2 network identifier.
|
|
124
|
+
tx_hash: The transaction hash to query.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
TransactionResult with the transaction details.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
Exception: If the transaction is not found or the query fails.
|
|
131
|
+
"""
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
async def get_balance(
|
|
135
|
+
self,
|
|
136
|
+
network: str,
|
|
137
|
+
address: str,
|
|
138
|
+
denom: str,
|
|
139
|
+
) -> str:
|
|
140
|
+
"""Get the token balance for an account.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
network: The CAIP-2 network identifier.
|
|
144
|
+
address: The bech32-encoded address.
|
|
145
|
+
denom: The token denomination (e.g., "uusdc").
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Balance in atomic units as a string.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
Exception: If the balance query fails.
|
|
152
|
+
"""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# =============================================================================
|
|
157
|
+
# Transaction Types
|
|
158
|
+
# =============================================================================
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Coin:
|
|
162
|
+
"""Represents a Cosmos SDK Coin.
|
|
163
|
+
|
|
164
|
+
Attributes:
|
|
165
|
+
denom: The token denomination.
|
|
166
|
+
amount: The amount in atomic units.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self, denom: str, amount: str) -> None:
|
|
170
|
+
self.denom = denom
|
|
171
|
+
self.amount = amount
|
|
172
|
+
|
|
173
|
+
def to_dict(self) -> Dict[str, str]:
|
|
174
|
+
"""Convert to a dict.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Dict with denom and amount fields.
|
|
178
|
+
"""
|
|
179
|
+
return {"denom": self.denom, "amount": self.amount}
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def from_dict(cls, data: Dict[str, Any]) -> Coin:
|
|
183
|
+
"""Create a Coin from a dict.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
data: Dict with denom and amount fields.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Coin instance.
|
|
190
|
+
"""
|
|
191
|
+
return cls(
|
|
192
|
+
denom=data.get("denom", ""),
|
|
193
|
+
amount=data.get("amount", "0"),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class MsgSend:
|
|
198
|
+
"""Represents a Cosmos SDK MsgSend message.
|
|
199
|
+
|
|
200
|
+
Attributes:
|
|
201
|
+
type: The message type URL (should be /cosmos.bank.v1beta1.MsgSend).
|
|
202
|
+
from_address: The sender's bech32 address.
|
|
203
|
+
to_address: The recipient's bech32 address.
|
|
204
|
+
amount: List of Coin objects.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
type: str,
|
|
210
|
+
from_address: str,
|
|
211
|
+
to_address: str,
|
|
212
|
+
amount: List[Coin],
|
|
213
|
+
) -> None:
|
|
214
|
+
self.type = type
|
|
215
|
+
self.from_address = from_address
|
|
216
|
+
self.to_address = to_address
|
|
217
|
+
self.amount = amount
|
|
218
|
+
|
|
219
|
+
def get_amount_by_denom(self, denom: str) -> str:
|
|
220
|
+
"""Get the amount for a specific denomination.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
denom: The token denomination to look for.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Amount string if found, empty string otherwise.
|
|
227
|
+
"""
|
|
228
|
+
for coin in self.amount:
|
|
229
|
+
if coin.denom == denom:
|
|
230
|
+
return coin.amount
|
|
231
|
+
return ""
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def from_dict(cls, data: Dict[str, Any]) -> Optional[MsgSend]:
|
|
235
|
+
"""Parse a message dict into a MsgSend.
|
|
236
|
+
|
|
237
|
+
Returns None if the message is not a MsgSend type.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
data: Dict representing a Cosmos message.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
MsgSend instance if it is a bank send message, None otherwise.
|
|
244
|
+
"""
|
|
245
|
+
msg_type = data.get("@type", "")
|
|
246
|
+
if msg_type != MSG_TYPE_SEND:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
coins = []
|
|
250
|
+
for coin_data in data.get("amount", []):
|
|
251
|
+
coins.append(Coin.from_dict(coin_data))
|
|
252
|
+
|
|
253
|
+
return cls(
|
|
254
|
+
type=msg_type,
|
|
255
|
+
from_address=data.get("from_address", ""),
|
|
256
|
+
to_address=data.get("to_address", ""),
|
|
257
|
+
amount=coins,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class TxEvent:
|
|
262
|
+
"""Represents an event in a transaction log.
|
|
263
|
+
|
|
264
|
+
Attributes:
|
|
265
|
+
type: The event type.
|
|
266
|
+
attributes: List of key-value attribute dicts.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
def __init__(self, type: str, attributes: List[Dict[str, str]]) -> None:
|
|
270
|
+
self.type = type
|
|
271
|
+
self.attributes = attributes
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def from_dict(cls, data: Dict[str, Any]) -> TxEvent:
|
|
275
|
+
"""Create a TxEvent from a dict.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
data: Dict with type and attributes fields.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
TxEvent instance.
|
|
282
|
+
"""
|
|
283
|
+
return cls(
|
|
284
|
+
type=data.get("type", ""),
|
|
285
|
+
attributes=data.get("attributes", []),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class TxLog:
|
|
290
|
+
"""Represents a transaction log.
|
|
291
|
+
|
|
292
|
+
Attributes:
|
|
293
|
+
msg_index: The message index.
|
|
294
|
+
log: The log string.
|
|
295
|
+
events: List of TxEvent objects.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
def __init__(self, msg_index: int, log: str, events: List[TxEvent]) -> None:
|
|
299
|
+
self.msg_index = msg_index
|
|
300
|
+
self.log = log
|
|
301
|
+
self.events = events
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def from_dict(cls, data: Dict[str, Any]) -> TxLog:
|
|
305
|
+
"""Create a TxLog from a dict.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
data: Dict with msg_index, log, and events fields.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
TxLog instance.
|
|
312
|
+
"""
|
|
313
|
+
events = [TxEvent.from_dict(e) for e in data.get("events", [])]
|
|
314
|
+
return cls(
|
|
315
|
+
msg_index=data.get("msg_index", 0),
|
|
316
|
+
log=data.get("log", ""),
|
|
317
|
+
events=events,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class TransactionResult:
|
|
322
|
+
"""Represents the result of a Cosmos transaction query.
|
|
323
|
+
|
|
324
|
+
Attributes:
|
|
325
|
+
tx_hash: The transaction hash.
|
|
326
|
+
height: The block height.
|
|
327
|
+
code: The response code (0 = success).
|
|
328
|
+
raw_log: The raw log string.
|
|
329
|
+
logs: List of TxLog objects.
|
|
330
|
+
gas_wanted: The gas requested.
|
|
331
|
+
gas_used: The gas consumed.
|
|
332
|
+
messages: List of raw message dicts from the transaction body.
|
|
333
|
+
timestamp: The block timestamp.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
def __init__(
|
|
337
|
+
self,
|
|
338
|
+
tx_hash: str = "",
|
|
339
|
+
height: str = "",
|
|
340
|
+
code: int = 0,
|
|
341
|
+
raw_log: str = "",
|
|
342
|
+
logs: Optional[List[TxLog]] = None,
|
|
343
|
+
gas_wanted: str = "",
|
|
344
|
+
gas_used: str = "",
|
|
345
|
+
messages: Optional[List[Dict[str, Any]]] = None,
|
|
346
|
+
timestamp: str = "",
|
|
347
|
+
) -> None:
|
|
348
|
+
self.tx_hash = tx_hash
|
|
349
|
+
self.height = height
|
|
350
|
+
self.code = code
|
|
351
|
+
self.raw_log = raw_log
|
|
352
|
+
self.logs = logs or []
|
|
353
|
+
self.gas_wanted = gas_wanted
|
|
354
|
+
self.gas_used = gas_used
|
|
355
|
+
self.messages = messages or []
|
|
356
|
+
self.timestamp = timestamp
|
|
357
|
+
|
|
358
|
+
def is_success(self) -> bool:
|
|
359
|
+
"""Check if the transaction succeeded.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
True if the response code is 0.
|
|
363
|
+
"""
|
|
364
|
+
return self.code == 0
|
|
365
|
+
|
|
366
|
+
def find_msg_send(self) -> Optional[MsgSend]:
|
|
367
|
+
"""Find the first MsgSend message in the transaction.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
MsgSend if found, None otherwise.
|
|
371
|
+
"""
|
|
372
|
+
for msg_data in self.messages:
|
|
373
|
+
msg = MsgSend.from_dict(msg_data)
|
|
374
|
+
if msg is not None:
|
|
375
|
+
return msg
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
@classmethod
|
|
379
|
+
def from_dict(cls, data: Dict[str, Any]) -> TransactionResult:
|
|
380
|
+
"""Create a TransactionResult from a REST API response dict.
|
|
381
|
+
|
|
382
|
+
Handles both the direct format and the nested tx_response format.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
data: Dict from the Cosmos REST API.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
TransactionResult instance.
|
|
389
|
+
"""
|
|
390
|
+
# Handle nested REST API response format
|
|
391
|
+
tx_response = data.get("tx_response", data)
|
|
392
|
+
tx_wrapper = data.get("tx", {})
|
|
393
|
+
|
|
394
|
+
# Parse logs
|
|
395
|
+
logs = []
|
|
396
|
+
for log_data in tx_response.get("logs", []):
|
|
397
|
+
logs.append(TxLog.from_dict(log_data))
|
|
398
|
+
|
|
399
|
+
# Parse messages from tx body
|
|
400
|
+
messages: List[Dict[str, Any]] = []
|
|
401
|
+
body = tx_wrapper.get("body", {})
|
|
402
|
+
raw_messages = body.get("messages", [])
|
|
403
|
+
for raw_msg in raw_messages:
|
|
404
|
+
if isinstance(raw_msg, dict):
|
|
405
|
+
messages.append(raw_msg)
|
|
406
|
+
elif isinstance(raw_msg, (str, bytes)):
|
|
407
|
+
try:
|
|
408
|
+
messages.append(json.loads(raw_msg))
|
|
409
|
+
except (json.JSONDecodeError, TypeError):
|
|
410
|
+
continue
|
|
411
|
+
|
|
412
|
+
return cls(
|
|
413
|
+
tx_hash=tx_response.get("txhash", tx_response.get("tx_hash", "")),
|
|
414
|
+
height=tx_response.get("height", ""),
|
|
415
|
+
code=tx_response.get("code", 0),
|
|
416
|
+
raw_log=tx_response.get("raw_log", ""),
|
|
417
|
+
logs=logs,
|
|
418
|
+
gas_wanted=tx_response.get("gas_wanted", ""),
|
|
419
|
+
gas_used=tx_response.get("gas_used", ""),
|
|
420
|
+
messages=messages,
|
|
421
|
+
timestamp=tx_response.get("timestamp", ""),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
# =============================================================================
|
|
426
|
+
# Payload Types
|
|
427
|
+
# =============================================================================
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class ExactDirectPayload(BaseModel):
|
|
431
|
+
"""Payload for the exact-direct scheme on Cosmos.
|
|
432
|
+
|
|
433
|
+
Contains the transaction hash as proof of on-chain payment,
|
|
434
|
+
along with transfer details for verification.
|
|
435
|
+
|
|
436
|
+
Attributes:
|
|
437
|
+
tx_hash: The on-chain transaction hash.
|
|
438
|
+
from_address: The sender's bech32 address.
|
|
439
|
+
to_address: The recipient's bech32 address.
|
|
440
|
+
amount: The transfer amount in atomic units.
|
|
441
|
+
denom: The token denomination (optional, defaults to "uusdc").
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
tx_hash: str = Field(alias="txHash")
|
|
445
|
+
from_address: str = Field(alias="from")
|
|
446
|
+
to_address: str = Field(alias="to")
|
|
447
|
+
amount: str
|
|
448
|
+
denom: str = ""
|
|
449
|
+
|
|
450
|
+
model_config = ConfigDict(
|
|
451
|
+
alias_generator=to_camel,
|
|
452
|
+
populate_by_name=True,
|
|
453
|
+
from_attributes=True,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
@field_validator("amount")
|
|
457
|
+
@classmethod
|
|
458
|
+
def validate_amount(cls, v: str) -> str:
|
|
459
|
+
"""Validate that amount is a valid integer string."""
|
|
460
|
+
try:
|
|
461
|
+
int(v)
|
|
462
|
+
except ValueError:
|
|
463
|
+
raise ValueError("amount must be an integer encoded as a string")
|
|
464
|
+
return v
|
|
465
|
+
|
|
466
|
+
def to_map(self) -> Dict[str, Any]:
|
|
467
|
+
"""Convert the payload to a plain dict for inclusion in PaymentPayload.
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Dict with txHash, from, to, amount, and optionally denom fields.
|
|
471
|
+
"""
|
|
472
|
+
m: Dict[str, Any] = {
|
|
473
|
+
"txHash": self.tx_hash,
|
|
474
|
+
"from": self.from_address,
|
|
475
|
+
"to": self.to_address,
|
|
476
|
+
"amount": self.amount,
|
|
477
|
+
}
|
|
478
|
+
if self.denom:
|
|
479
|
+
m["denom"] = self.denom
|
|
480
|
+
return m
|
|
481
|
+
|
|
482
|
+
@classmethod
|
|
483
|
+
def from_map(cls, data: Dict[str, Any]) -> ExactDirectPayload:
|
|
484
|
+
"""Create an ExactDirectPayload from a plain dict.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
data: Dict with txHash, from, to, amount, and optionally denom fields.
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
ExactDirectPayload instance.
|
|
491
|
+
|
|
492
|
+
Raises:
|
|
493
|
+
ValueError: If required fields are missing.
|
|
494
|
+
"""
|
|
495
|
+
return cls(
|
|
496
|
+
tx_hash=data.get("txHash", ""),
|
|
497
|
+
from_address=data.get("from", ""),
|
|
498
|
+
to_address=data.get("to", ""),
|
|
499
|
+
amount=data.get("amount", "0"),
|
|
500
|
+
denom=data.get("denom", ""),
|
|
501
|
+
)
|
t402/schemes/evm/__init__.py
CHANGED
|
@@ -22,7 +22,7 @@ Supported schemes:
|
|
|
22
22
|
1. Replace `ExactLegacyEvmClientScheme` with `ExactEvmClientScheme`
|
|
23
23
|
2. Replace `ExactLegacyEvmServerScheme` with `ExactEvmServerScheme`
|
|
24
24
|
3. Use USDT0 token addresses instead of legacy USDT
|
|
25
|
-
4. See https://docs.t402.io/migration
|
|
25
|
+
4. See https://docs.t402.io/advanced/migration-v1-to-v2 for details
|
|
26
26
|
|
|
27
27
|
**USDT0 Advantages:**
|
|
28
28
|
- Single signature (no approve transaction)
|
|
@@ -10,7 +10,7 @@ for EVM networks using the approve + transferFrom pattern.
|
|
|
10
10
|
**Migration Guide:**
|
|
11
11
|
- Replace: `SCHEME_EXACT_LEGACY` with `SCHEME_EXACT`
|
|
12
12
|
- Replace: legacy USDT tokens with USDT0 tokens
|
|
13
|
-
- See https://docs.t402.io/migration
|
|
13
|
+
- See https://docs.t402.io/advanced/migration-v1-to-v2 for full migration guide
|
|
14
14
|
|
|
15
15
|
**Why Migrate:**
|
|
16
16
|
1. Gasless transfers: USDT0 supports EIP-3009, eliminating gas costs for users
|
t402/schemes/near/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ This package provides payment scheme implementations for the NEAR blockchain.
|
|
|
4
4
|
|
|
5
5
|
Supported schemes:
|
|
6
6
|
- exact-direct: Client executes NEP-141 ft_transfer, tx hash used as proof.
|
|
7
|
+
- upto: Escrow-based pattern for usage-based billing (ft_transfer to facilitator).
|
|
7
8
|
|
|
8
9
|
Usage:
|
|
9
10
|
```python
|
|
@@ -24,6 +25,13 @@ Usage:
|
|
|
24
25
|
SCHEME_EXACT_DIRECT,
|
|
25
26
|
NEAR_MAINNET,
|
|
26
27
|
NEAR_TESTNET,
|
|
28
|
+
# Upto types
|
|
29
|
+
UptoNearAuthorization,
|
|
30
|
+
UptoNearPayload,
|
|
31
|
+
UptoNearExtra,
|
|
32
|
+
UptoNearSettlement,
|
|
33
|
+
is_upto_near_payload,
|
|
34
|
+
upto_payload_from_dict,
|
|
27
35
|
)
|
|
28
36
|
```
|
|
29
37
|
"""
|
|
@@ -43,6 +51,14 @@ from t402.schemes.near.types import (
|
|
|
43
51
|
FtTransferArgs,
|
|
44
52
|
is_valid_account_id,
|
|
45
53
|
)
|
|
54
|
+
from t402.schemes.near.upto import (
|
|
55
|
+
UptoNearAuthorization,
|
|
56
|
+
UptoNearPayload,
|
|
57
|
+
UptoNearExtra,
|
|
58
|
+
UptoNearSettlement,
|
|
59
|
+
is_upto_near_payload,
|
|
60
|
+
upto_payload_from_dict,
|
|
61
|
+
)
|
|
46
62
|
from t402.schemes.near.constants import (
|
|
47
63
|
SCHEME_EXACT_DIRECT,
|
|
48
64
|
NEAR_MAINNET,
|
|
@@ -82,6 +98,15 @@ __all__ = [
|
|
|
82
98
|
# Payload types
|
|
83
99
|
"ExactDirectPayload",
|
|
84
100
|
"FtTransferArgs",
|
|
101
|
+
# Upto types
|
|
102
|
+
"UptoNearAuthorization",
|
|
103
|
+
"UptoNearPayload",
|
|
104
|
+
"UptoNearExtra",
|
|
105
|
+
"UptoNearSettlement",
|
|
106
|
+
# Upto type guards
|
|
107
|
+
"is_upto_near_payload",
|
|
108
|
+
# Upto factory functions
|
|
109
|
+
"upto_payload_from_dict",
|
|
85
110
|
# Validation
|
|
86
111
|
"is_valid_account_id",
|
|
87
112
|
"is_valid_network",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""NEAR Up-To Payment Scheme Types.
|
|
2
|
+
|
|
3
|
+
This package provides the upto payment scheme types for NEAR networks
|
|
4
|
+
using an escrow pattern with NEP-141 ft_transfer.
|
|
5
|
+
|
|
6
|
+
The upto scheme allows clients to authorize a maximum amount that can be
|
|
7
|
+
settled later based on actual usage. Since NEAR NEP-141 tokens don't support
|
|
8
|
+
native approve/transferFrom, this uses an escrow pattern:
|
|
9
|
+
|
|
10
|
+
1. Client ft_transfers maxAmount to the facilitator
|
|
11
|
+
2. Facilitator verifies the transfer
|
|
12
|
+
3. Facilitator forwards settleAmount to payTo and refunds the rest
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
```python
|
|
16
|
+
from t402.schemes.near.upto import (
|
|
17
|
+
UptoNearAuthorization,
|
|
18
|
+
UptoNearPayload,
|
|
19
|
+
UptoNearExtra,
|
|
20
|
+
UptoNearSettlement,
|
|
21
|
+
is_upto_near_payload,
|
|
22
|
+
upto_payload_from_dict,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Check if data is an upto NEAR payload
|
|
26
|
+
if is_upto_near_payload(data):
|
|
27
|
+
payload = upto_payload_from_dict(data)
|
|
28
|
+
print(payload.tx_hash)
|
|
29
|
+
print(payload.authorization.max_amount)
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from t402.schemes.near.upto.types import (
|
|
34
|
+
UptoNearAuthorization,
|
|
35
|
+
UptoNearPayload,
|
|
36
|
+
UptoNearExtra,
|
|
37
|
+
UptoNearSettlement,
|
|
38
|
+
UptoNearUsageDetails,
|
|
39
|
+
is_upto_near_payload,
|
|
40
|
+
upto_payload_from_dict,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
# Models
|
|
45
|
+
"UptoNearAuthorization",
|
|
46
|
+
"UptoNearPayload",
|
|
47
|
+
"UptoNearExtra",
|
|
48
|
+
"UptoNearSettlement",
|
|
49
|
+
"UptoNearUsageDetails",
|
|
50
|
+
# Type guards
|
|
51
|
+
"is_upto_near_payload",
|
|
52
|
+
# Factory functions
|
|
53
|
+
"upto_payload_from_dict",
|
|
54
|
+
]
|