t402 1.9.0__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.
Files changed (134) hide show
  1. t402/__init__.py +2 -1
  2. t402/a2a/__init__.py +73 -0
  3. t402/a2a/helpers.py +158 -0
  4. t402/a2a/types.py +145 -0
  5. t402/bridge/client.py +13 -5
  6. t402/bridge/constants.py +4 -2
  7. t402/bridge/router.py +1 -1
  8. t402/bridge/scan.py +3 -1
  9. t402/chains.py +268 -1
  10. t402/cli.py +31 -9
  11. t402/common.py +2 -0
  12. t402/cosmos_paywall_template.py +2 -0
  13. t402/django/__init__.py +42 -0
  14. t402/django/middleware.py +596 -0
  15. t402/encoding.py +9 -3
  16. t402/erc4337/accounts.py +56 -51
  17. t402/erc4337/bundlers.py +105 -99
  18. t402/erc4337/paymasters.py +100 -109
  19. t402/erc4337/types.py +39 -26
  20. t402/errors.py +213 -0
  21. t402/evm_paywall_template.py +1 -1
  22. t402/facilitator.py +125 -0
  23. t402/fastapi/middleware.py +1 -3
  24. t402/mcp/constants.py +3 -6
  25. t402/mcp/server.py +501 -84
  26. t402/mcp/web3_utils.py +493 -0
  27. t402/multisig/__init__.py +120 -0
  28. t402/multisig/constants.py +54 -0
  29. t402/multisig/safe.py +441 -0
  30. t402/multisig/signature.py +228 -0
  31. t402/multisig/transaction.py +238 -0
  32. t402/multisig/types.py +108 -0
  33. t402/multisig/utils.py +77 -0
  34. t402/near_paywall_template.py +2 -0
  35. t402/networks.py +34 -1
  36. t402/paywall.py +1 -3
  37. t402/schemes/__init__.py +143 -0
  38. t402/schemes/aptos/__init__.py +70 -0
  39. t402/schemes/aptos/constants.py +349 -0
  40. t402/schemes/aptos/exact_direct/__init__.py +44 -0
  41. t402/schemes/aptos/exact_direct/client.py +202 -0
  42. t402/schemes/aptos/exact_direct/facilitator.py +426 -0
  43. t402/schemes/aptos/exact_direct/server.py +272 -0
  44. t402/schemes/aptos/types.py +237 -0
  45. t402/schemes/cosmos/__init__.py +114 -0
  46. t402/schemes/cosmos/constants.py +211 -0
  47. t402/schemes/cosmos/exact_direct/__init__.py +21 -0
  48. t402/schemes/cosmos/exact_direct/client.py +198 -0
  49. t402/schemes/cosmos/exact_direct/facilitator.py +493 -0
  50. t402/schemes/cosmos/exact_direct/server.py +315 -0
  51. t402/schemes/cosmos/types.py +501 -0
  52. t402/schemes/evm/__init__.py +46 -1
  53. t402/schemes/evm/exact/__init__.py +11 -0
  54. t402/schemes/evm/exact/client.py +3 -1
  55. t402/schemes/evm/exact/facilitator.py +894 -0
  56. t402/schemes/evm/exact/server.py +1 -1
  57. t402/schemes/evm/exact_legacy/__init__.py +38 -0
  58. t402/schemes/evm/exact_legacy/client.py +291 -0
  59. t402/schemes/evm/exact_legacy/facilitator.py +777 -0
  60. t402/schemes/evm/exact_legacy/server.py +231 -0
  61. t402/schemes/evm/upto/__init__.py +12 -0
  62. t402/schemes/evm/upto/client.py +6 -2
  63. t402/schemes/evm/upto/facilitator.py +625 -0
  64. t402/schemes/evm/upto/server.py +243 -0
  65. t402/schemes/evm/upto/types.py +3 -1
  66. t402/schemes/interfaces.py +6 -2
  67. t402/schemes/near/__init__.py +137 -0
  68. t402/schemes/near/constants.py +189 -0
  69. t402/schemes/near/exact_direct/__init__.py +21 -0
  70. t402/schemes/near/exact_direct/client.py +204 -0
  71. t402/schemes/near/exact_direct/facilitator.py +455 -0
  72. t402/schemes/near/exact_direct/server.py +303 -0
  73. t402/schemes/near/types.py +419 -0
  74. t402/schemes/near/upto/__init__.py +54 -0
  75. t402/schemes/near/upto/types.py +272 -0
  76. t402/schemes/polkadot/__init__.py +72 -0
  77. t402/schemes/polkadot/constants.py +155 -0
  78. t402/schemes/polkadot/exact_direct/__init__.py +43 -0
  79. t402/schemes/polkadot/exact_direct/client.py +235 -0
  80. t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
  81. t402/schemes/polkadot/exact_direct/server.py +292 -0
  82. t402/schemes/polkadot/types.py +385 -0
  83. t402/schemes/registry.py +6 -2
  84. t402/schemes/stacks/__init__.py +68 -0
  85. t402/schemes/stacks/constants.py +122 -0
  86. t402/schemes/stacks/exact_direct/__init__.py +43 -0
  87. t402/schemes/stacks/exact_direct/client.py +222 -0
  88. t402/schemes/stacks/exact_direct/facilitator.py +424 -0
  89. t402/schemes/stacks/exact_direct/server.py +292 -0
  90. t402/schemes/stacks/types.py +380 -0
  91. t402/schemes/svm/__init__.py +44 -0
  92. t402/schemes/svm/exact/__init__.py +35 -0
  93. t402/schemes/svm/exact/client.py +23 -0
  94. t402/schemes/svm/exact/facilitator.py +24 -0
  95. t402/schemes/svm/exact/server.py +20 -0
  96. t402/schemes/svm/upto/__init__.py +23 -0
  97. t402/schemes/svm/upto/types.py +193 -0
  98. t402/schemes/tezos/__init__.py +84 -0
  99. t402/schemes/tezos/constants.py +372 -0
  100. t402/schemes/tezos/exact_direct/__init__.py +22 -0
  101. t402/schemes/tezos/exact_direct/client.py +226 -0
  102. t402/schemes/tezos/exact_direct/facilitator.py +491 -0
  103. t402/schemes/tezos/exact_direct/server.py +277 -0
  104. t402/schemes/tezos/types.py +220 -0
  105. t402/schemes/ton/__init__.py +24 -2
  106. t402/schemes/ton/exact/__init__.py +7 -0
  107. t402/schemes/ton/exact/facilitator.py +730 -0
  108. t402/schemes/ton/exact/server.py +1 -1
  109. t402/schemes/ton/upto/__init__.py +31 -0
  110. t402/schemes/ton/upto/types.py +215 -0
  111. t402/schemes/tron/__init__.py +28 -2
  112. t402/schemes/tron/exact/__init__.py +9 -0
  113. t402/schemes/tron/exact/facilitator.py +673 -0
  114. t402/schemes/tron/exact/server.py +1 -1
  115. t402/schemes/tron/upto/__init__.py +30 -0
  116. t402/schemes/tron/upto/types.py +213 -0
  117. t402/stacks_paywall_template.py +2 -0
  118. t402/starlette/__init__.py +38 -0
  119. t402/starlette/middleware.py +522 -0
  120. t402/svm.py +45 -11
  121. t402/svm_paywall_template.py +1 -1
  122. t402/ton.py +6 -2
  123. t402/ton_paywall_template.py +1 -192
  124. t402/tron.py +2 -0
  125. t402/tron_paywall_template.py +2 -0
  126. t402/types.py +103 -3
  127. t402/wdk/chains.py +1 -1
  128. t402/wdk/errors.py +15 -5
  129. t402/wdk/signer.py +11 -2
  130. {t402-1.9.0.dist-info → t402-1.10.0.dist-info}/METADATA +42 -1
  131. t402-1.10.0.dist-info/RECORD +156 -0
  132. t402-1.9.0.dist-info/RECORD +0 -72
  133. {t402-1.9.0.dist-info → t402-1.10.0.dist-info}/WHEEL +0 -0
  134. {t402-1.9.0.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
+ )
@@ -4,20 +4,55 @@ This package provides payment scheme implementations for EVM-compatible
4
4
  blockchains (Ethereum, Base, Avalanche, etc.).
5
5
 
6
6
  Supported schemes:
7
- - exact: EIP-3009 TransferWithAuthorization
7
+ - exact: EIP-3009 TransferWithAuthorization (recommended)
8
+ - exact-legacy: approve + transferFrom (DEPRECATED - see deprecation notice below)
8
9
  - upto: EIP-2612 Permit (usage-based billing)
10
+
11
+ .. deprecated:: 2.3.0
12
+ The **exact-legacy** scheme is deprecated and will be removed in v3.0.0.
13
+
14
+ The exact-legacy scheme uses the traditional approve + transferFrom pattern,
15
+ which has several drawbacks compared to the "exact" scheme with USDT0:
16
+
17
+ 1. **Two transactions required**: Users must approve then transfer
18
+ 2. **Gas costs**: Users pay gas for both transactions
19
+ 3. **Limited availability**: Legacy USDT only on specific chains
20
+
21
+ **Migration Guide:**
22
+ 1. Replace `ExactLegacyEvmClientScheme` with `ExactEvmClientScheme`
23
+ 2. Replace `ExactLegacyEvmServerScheme` with `ExactEvmServerScheme`
24
+ 3. Use USDT0 token addresses instead of legacy USDT
25
+ 4. See https://docs.t402.io/advanced/migration-v1-to-v2 for details
26
+
27
+ **USDT0 Advantages:**
28
+ - Single signature (no approve transaction)
29
+ - Gasless via EIP-3009
30
+ - Available on 19+ chains via LayerZero
31
+ - Cross-chain bridging support
9
32
  """
10
33
 
11
34
  from t402.schemes.evm.exact import (
12
35
  ExactEvmClientScheme,
13
36
  ExactEvmServerScheme,
37
+ ExactEvmFacilitatorScheme,
38
+ FacilitatorEvmSigner,
39
+ EvmVerifyResult,
40
+ EvmTransactionConfirmation,
14
41
  EvmSigner,
15
42
  create_nonce,
16
43
  SCHEME_EXACT,
17
44
  )
18
45
 
46
+ from t402.schemes.evm.exact_legacy import (
47
+ ExactLegacyEvmClientScheme,
48
+ ExactLegacyEvmServerScheme,
49
+ SCHEME_EXACT_LEGACY,
50
+ )
51
+
19
52
  from t402.schemes.evm.upto import (
20
53
  UptoEvmClientScheme,
54
+ UptoEvmServerScheme,
55
+ UptoEvmFacilitatorScheme,
21
56
  create_payment_nonce,
22
57
  SCHEME_UPTO,
23
58
  PermitSignature,
@@ -31,11 +66,21 @@ __all__ = [
31
66
  # Exact scheme
32
67
  "ExactEvmClientScheme",
33
68
  "ExactEvmServerScheme",
69
+ "ExactEvmFacilitatorScheme",
70
+ "FacilitatorEvmSigner",
71
+ "EvmVerifyResult",
72
+ "EvmTransactionConfirmation",
34
73
  "EvmSigner",
35
74
  "create_nonce",
36
75
  "SCHEME_EXACT",
76
+ # Exact-Legacy scheme
77
+ "ExactLegacyEvmClientScheme",
78
+ "ExactLegacyEvmServerScheme",
79
+ "SCHEME_EXACT_LEGACY",
37
80
  # Upto scheme
38
81
  "UptoEvmClientScheme",
82
+ "UptoEvmServerScheme",
83
+ "UptoEvmFacilitatorScheme",
39
84
  "create_payment_nonce",
40
85
  "SCHEME_UPTO",
41
86
  "PermitSignature",
@@ -16,6 +16,12 @@ from t402.schemes.evm.exact.client import (
16
16
  from t402.schemes.evm.exact.server import (
17
17
  ExactEvmServerScheme,
18
18
  )
19
+ from t402.schemes.evm.exact.facilitator import (
20
+ ExactEvmFacilitatorScheme,
21
+ FacilitatorEvmSigner,
22
+ EvmVerifyResult,
23
+ EvmTransactionConfirmation,
24
+ )
19
25
 
20
26
  __all__ = [
21
27
  # Client
@@ -24,6 +30,11 @@ __all__ = [
24
30
  "create_nonce",
25
31
  # Server
26
32
  "ExactEvmServerScheme",
33
+ # Facilitator
34
+ "ExactEvmFacilitatorScheme",
35
+ "FacilitatorEvmSigner",
36
+ "EvmVerifyResult",
37
+ "EvmTransactionConfirmation",
27
38
  # Constants
28
39
  "SCHEME_EXACT",
29
40
  ]
@@ -123,7 +123,9 @@ class ExactEvmClientScheme:
123
123
  asset = req.get("asset", "")
124
124
 
125
125
  # Get timeout
126
- max_timeout = req.get("maxTimeoutSeconds") or req.get("max_timeout_seconds", 300)
126
+ max_timeout = req.get("maxTimeoutSeconds") or req.get(
127
+ "max_timeout_seconds", 300
128
+ )
127
129
 
128
130
  # Get EIP-712 domain info from extra
129
131
  extra = req.get("extra", {})