t402 1.9.0__py3-none-any.whl → 1.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. t402/__init__.py +2 -1
  2. t402/bridge/client.py +13 -5
  3. t402/bridge/constants.py +3 -1
  4. t402/bridge/router.py +1 -1
  5. t402/bridge/scan.py +3 -1
  6. t402/chains.py +268 -1
  7. t402/cli.py +31 -9
  8. t402/common.py +2 -0
  9. t402/cosmos_paywall_template.py +2 -0
  10. t402/encoding.py +9 -3
  11. t402/erc4337/accounts.py +56 -51
  12. t402/erc4337/bundlers.py +105 -99
  13. t402/erc4337/paymasters.py +100 -109
  14. t402/erc4337/types.py +39 -26
  15. t402/evm_paywall_template.py +1 -1
  16. t402/fastapi/middleware.py +1 -3
  17. t402/mcp/server.py +79 -46
  18. t402/near_paywall_template.py +2 -0
  19. t402/networks.py +34 -1
  20. t402/paywall.py +1 -3
  21. t402/schemes/__init__.py +124 -0
  22. t402/schemes/aptos/__init__.py +70 -0
  23. t402/schemes/aptos/constants.py +349 -0
  24. t402/schemes/aptos/exact_direct/__init__.py +44 -0
  25. t402/schemes/aptos/exact_direct/client.py +202 -0
  26. t402/schemes/aptos/exact_direct/facilitator.py +426 -0
  27. t402/schemes/aptos/exact_direct/server.py +272 -0
  28. t402/schemes/aptos/types.py +237 -0
  29. t402/schemes/evm/__init__.py +46 -1
  30. t402/schemes/evm/exact/__init__.py +11 -0
  31. t402/schemes/evm/exact/client.py +3 -1
  32. t402/schemes/evm/exact/facilitator.py +894 -0
  33. t402/schemes/evm/exact/server.py +1 -1
  34. t402/schemes/evm/exact_legacy/__init__.py +38 -0
  35. t402/schemes/evm/exact_legacy/client.py +291 -0
  36. t402/schemes/evm/exact_legacy/facilitator.py +777 -0
  37. t402/schemes/evm/exact_legacy/server.py +231 -0
  38. t402/schemes/evm/upto/__init__.py +12 -0
  39. t402/schemes/evm/upto/client.py +6 -2
  40. t402/schemes/evm/upto/facilitator.py +625 -0
  41. t402/schemes/evm/upto/server.py +243 -0
  42. t402/schemes/evm/upto/types.py +3 -1
  43. t402/schemes/interfaces.py +6 -2
  44. t402/schemes/near/__init__.py +112 -0
  45. t402/schemes/near/constants.py +189 -0
  46. t402/schemes/near/exact_direct/__init__.py +21 -0
  47. t402/schemes/near/exact_direct/client.py +204 -0
  48. t402/schemes/near/exact_direct/facilitator.py +455 -0
  49. t402/schemes/near/exact_direct/server.py +303 -0
  50. t402/schemes/near/types.py +419 -0
  51. t402/schemes/polkadot/__init__.py +72 -0
  52. t402/schemes/polkadot/constants.py +155 -0
  53. t402/schemes/polkadot/exact_direct/__init__.py +43 -0
  54. t402/schemes/polkadot/exact_direct/client.py +235 -0
  55. t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
  56. t402/schemes/polkadot/exact_direct/server.py +292 -0
  57. t402/schemes/polkadot/types.py +385 -0
  58. t402/schemes/registry.py +6 -2
  59. t402/schemes/stacks/__init__.py +68 -0
  60. t402/schemes/stacks/constants.py +122 -0
  61. t402/schemes/stacks/exact_direct/__init__.py +43 -0
  62. t402/schemes/stacks/exact_direct/client.py +222 -0
  63. t402/schemes/stacks/exact_direct/facilitator.py +424 -0
  64. t402/schemes/stacks/exact_direct/server.py +292 -0
  65. t402/schemes/stacks/types.py +380 -0
  66. t402/schemes/svm/__init__.py +29 -0
  67. t402/schemes/svm/exact/__init__.py +35 -0
  68. t402/schemes/svm/exact/client.py +23 -0
  69. t402/schemes/svm/exact/facilitator.py +24 -0
  70. t402/schemes/svm/exact/server.py +20 -0
  71. t402/schemes/tezos/__init__.py +84 -0
  72. t402/schemes/tezos/constants.py +372 -0
  73. t402/schemes/tezos/exact_direct/__init__.py +22 -0
  74. t402/schemes/tezos/exact_direct/client.py +226 -0
  75. t402/schemes/tezos/exact_direct/facilitator.py +491 -0
  76. t402/schemes/tezos/exact_direct/server.py +277 -0
  77. t402/schemes/tezos/types.py +220 -0
  78. t402/schemes/ton/__init__.py +9 -2
  79. t402/schemes/ton/exact/__init__.py +7 -0
  80. t402/schemes/ton/exact/facilitator.py +730 -0
  81. t402/schemes/ton/exact/server.py +1 -1
  82. t402/schemes/tron/__init__.py +11 -2
  83. t402/schemes/tron/exact/__init__.py +9 -0
  84. t402/schemes/tron/exact/facilitator.py +673 -0
  85. t402/schemes/tron/exact/server.py +1 -1
  86. t402/stacks_paywall_template.py +2 -0
  87. t402/svm.py +45 -11
  88. t402/svm_paywall_template.py +1 -1
  89. t402/ton.py +5 -1
  90. t402/ton_paywall_template.py +1 -192
  91. t402/tron.py +2 -0
  92. t402/tron_paywall_template.py +2 -0
  93. t402/types.py +3 -1
  94. t402/wdk/errors.py +15 -5
  95. t402/wdk/signer.py +11 -2
  96. {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
  97. t402-1.9.1.dist-info/RECORD +125 -0
  98. t402-1.9.0.dist-info/RECORD +0 -72
  99. {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
  100. {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,272 @@
1
+ """Aptos Exact-Direct Scheme - Server Implementation.
2
+
3
+ This module provides the server-side implementation of the exact-direct payment
4
+ scheme for Aptos. It handles parsing user-friendly prices into atomic units
5
+ and enhancing payment requirements with Aptos-specific metadata.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import Any, Dict, List, Optional, Union
12
+
13
+ from t402.types import (
14
+ PaymentRequirementsV2,
15
+ Network,
16
+ )
17
+ from t402.schemes.interfaces import AssetAmount, SupportedKindDict
18
+ from t402.schemes.aptos.constants import (
19
+ SCHEME_EXACT_DIRECT,
20
+ CAIP_FAMILY,
21
+ DEFAULT_DECIMALS,
22
+ get_network_config,
23
+ get_token_by_address,
24
+ get_token_info,
25
+ parse_amount,
26
+ TokenInfo,
27
+ )
28
+
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class ExactDirectAptosServerScheme:
34
+ """Server scheme for Aptos exact-direct payments.
35
+
36
+ Handles parsing user-friendly prices (e.g., "$1.50") into atomic FA amounts
37
+ and enhancing payment requirements with Aptos-specific metadata such as
38
+ token symbol, name, and decimals.
39
+
40
+ Example:
41
+ ```python
42
+ scheme = ExactDirectAptosServerScheme()
43
+
44
+ # Parse a price
45
+ asset_amount = await scheme.parse_price("$0.10", "aptos:1")
46
+ # Returns: {"amount": "100000", "asset": "0xf73e...", "extra": {...}}
47
+
48
+ # Enhance requirements
49
+ enhanced = await scheme.enhance_requirements(
50
+ requirements,
51
+ supported_kind,
52
+ facilitator_extensions,
53
+ )
54
+ ```
55
+
56
+ Attributes:
57
+ scheme: The scheme identifier ("exact-direct").
58
+ caip_family: The CAIP-2 family pattern ("aptos:*").
59
+ """
60
+
61
+ scheme = SCHEME_EXACT_DIRECT
62
+ caip_family = CAIP_FAMILY
63
+
64
+ def __init__(self, preferred_token: Optional[str] = None) -> None:
65
+ """Initialize the Aptos exact-direct server scheme.
66
+
67
+ Args:
68
+ preferred_token: Preferred token symbol (e.g., "USDT").
69
+ Defaults to the network's default token if not specified.
70
+ """
71
+ self._preferred_token = preferred_token
72
+
73
+ async def parse_price(
74
+ self,
75
+ price: Union[str, int, float, Dict[str, Any]],
76
+ network: Network,
77
+ ) -> AssetAmount:
78
+ """Parse a user-friendly price to atomic amount and asset.
79
+
80
+ Supports multiple input formats:
81
+ - String with $ prefix: "$0.10" -> 100000 (6 decimals)
82
+ - String without prefix: "0.10" -> 100000
83
+ - Integer/float: 0.10 -> 100000
84
+ - Dict (pre-parsed TokenAmount): {"amount": "100000", "asset": "0x..."}
85
+
86
+ Args:
87
+ price: User-friendly price in any supported format.
88
+ network: CAIP-2 network identifier (e.g., "aptos:1").
89
+
90
+ Returns:
91
+ AssetAmount dict with:
92
+ - amount: Atomic amount as string
93
+ - asset: FA metadata address
94
+ - extra: Token metadata (symbol, name, decimals)
95
+
96
+ Raises:
97
+ ValueError: If price format is invalid or network is unsupported.
98
+ """
99
+ network_str = str(network)
100
+
101
+ # Validate network
102
+ config = get_network_config(network_str)
103
+ if not config:
104
+ raise ValueError(f"Unsupported Aptos network: {network}")
105
+
106
+ # Handle dict (already in TokenAmount format)
107
+ if isinstance(price, dict):
108
+ amount_val = price.get("amount", "0")
109
+ asset = price.get("asset", config.default_token.metadata_address)
110
+ extra = price.get("extra", {})
111
+
112
+ if not asset:
113
+ raise ValueError(
114
+ f"Asset address must be specified for AssetAmount on network {network}"
115
+ )
116
+
117
+ return {
118
+ "amount": str(amount_val),
119
+ "asset": asset,
120
+ "extra": extra,
121
+ }
122
+
123
+ # Parse money to decimal number
124
+ decimal_amount = self._parse_money_to_decimal(price)
125
+
126
+ # Get the appropriate token
127
+ token = self._get_default_token(network_str, config)
128
+
129
+ # Convert decimal to atomic units
130
+ amount_str = f"{decimal_amount:.{token.decimals}f}"
131
+ atomic_amount = parse_amount(amount_str, token.decimals)
132
+
133
+ return {
134
+ "amount": str(atomic_amount),
135
+ "asset": token.metadata_address,
136
+ "extra": {
137
+ "symbol": token.symbol,
138
+ "name": token.name,
139
+ "decimals": token.decimals,
140
+ },
141
+ }
142
+
143
+ async def enhance_requirements(
144
+ self,
145
+ requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
146
+ supported_kind: SupportedKindDict,
147
+ facilitator_extensions: List[str],
148
+ ) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
149
+ """Enhance payment requirements with Aptos-specific metadata.
150
+
151
+ Adds FA token metadata (symbol, name, decimals) to the extra field
152
+ and converts decimal amounts to atomic units if needed.
153
+
154
+ Args:
155
+ requirements: Base payment requirements with amount/asset set.
156
+ supported_kind: The matched SupportedKind from the facilitator.
157
+ facilitator_extensions: Extensions supported by the facilitator.
158
+
159
+ Returns:
160
+ Enhanced requirements with Aptos metadata in the extra field.
161
+
162
+ Raises:
163
+ ValueError: If the network is unsupported or amount parsing fails.
164
+ """
165
+ # Convert to dict for modification
166
+ if hasattr(requirements, "model_dump"):
167
+ req = requirements.model_dump(by_alias=True)
168
+ else:
169
+ req = dict(requirements)
170
+
171
+ network = req.get("network", "")
172
+ asset = req.get("asset", "")
173
+
174
+ # Validate network
175
+ config = get_network_config(network)
176
+ if not config:
177
+ raise ValueError(f"Unsupported Aptos network: {network}")
178
+
179
+ # Determine token info
180
+ token_info: Optional[TokenInfo] = None
181
+ if asset:
182
+ # Try to find by address
183
+ token_info = get_token_by_address(network, asset)
184
+ if not token_info:
185
+ # Use generic token with default decimals
186
+ token_info = TokenInfo(
187
+ metadata_address=asset,
188
+ symbol="UNKNOWN",
189
+ name="Unknown Token",
190
+ decimals=DEFAULT_DECIMALS,
191
+ )
192
+ else:
193
+ # Use default token
194
+ token_info = self._get_default_token(network, config)
195
+ req["asset"] = token_info.metadata_address
196
+
197
+ # Convert decimal amount to atomic units if needed
198
+ amount = req.get("amount", "")
199
+ if amount and "." in amount:
200
+ atomic = parse_amount(amount, token_info.decimals)
201
+ req["amount"] = str(atomic)
202
+
203
+ # Initialize extra map if needed
204
+ if "extra" not in req or req["extra"] is None:
205
+ req["extra"] = {}
206
+
207
+ # Add asset metadata to extra
208
+ req["extra"]["symbol"] = token_info.symbol
209
+ req["extra"]["name"] = token_info.name
210
+ req["extra"]["decimals"] = token_info.decimals
211
+
212
+ # Copy facilitator-provided extra fields
213
+ kind_extra = supported_kind.get("extra") if isinstance(supported_kind, dict) else None
214
+ if kind_extra:
215
+ if "assetSymbol" in kind_extra:
216
+ req["extra"]["assetSymbol"] = kind_extra["assetSymbol"]
217
+ if "assetDecimals" in kind_extra:
218
+ req["extra"]["assetDecimals"] = kind_extra["assetDecimals"]
219
+
220
+ # Copy specific extension keys
221
+ for key in facilitator_extensions:
222
+ if key in kind_extra:
223
+ req["extra"][key] = kind_extra[key]
224
+
225
+ return req
226
+
227
+ def _get_default_token(self, network: str, config: Any) -> TokenInfo:
228
+ """Get the default token for a network, considering preferred token config.
229
+
230
+ Args:
231
+ network: CAIP-2 network identifier.
232
+ config: NetworkConfig for the network.
233
+
234
+ Returns:
235
+ TokenInfo for the default or preferred token.
236
+ """
237
+ if self._preferred_token:
238
+ token = get_token_info(network, self._preferred_token)
239
+ if token:
240
+ return token
241
+ return config.default_token
242
+
243
+ def _parse_money_to_decimal(self, price: Union[str, int, float]) -> float:
244
+ """Convert a price value to a decimal amount.
245
+
246
+ Args:
247
+ price: Price as string (e.g., "$1.50"), int, or float.
248
+
249
+ Returns:
250
+ Decimal amount as float.
251
+
252
+ Raises:
253
+ ValueError: If the price format cannot be parsed.
254
+ """
255
+ if isinstance(price, str):
256
+ clean = price.strip()
257
+ # Remove $ prefix
258
+ if clean.startswith("$"):
259
+ clean = clean[1:].strip()
260
+ # Take the first token (handle "1.50 USDT" format)
261
+ parts = clean.split()
262
+ if parts:
263
+ try:
264
+ return float(parts[0])
265
+ except ValueError:
266
+ raise ValueError(f"Failed to parse price string: '{price}'")
267
+ raise ValueError(f"Empty price string: '{price}'")
268
+
269
+ if isinstance(price, (int, float)):
270
+ return float(price)
271
+
272
+ raise ValueError(f"Invalid price format: {price}")
@@ -0,0 +1,237 @@
1
+ """Aptos Scheme Types.
2
+
3
+ This module defines the data types used by the Aptos exact-direct payment scheme,
4
+ including the payload structure, transaction result, and signer protocols.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
10
+
11
+
12
+ @runtime_checkable
13
+ class ClientAptosSigner(Protocol):
14
+ """Protocol for Aptos client-side signing operations.
15
+
16
+ Implementations must provide the signer's address and the ability
17
+ to sign and submit transactions to the Aptos network.
18
+
19
+ Example:
20
+ ```python
21
+ class MyAptosSigner:
22
+ def __init__(self, account, client):
23
+ self._account = account
24
+ self._client = client
25
+
26
+ def address(self) -> str:
27
+ return str(self._account.address())
28
+
29
+ async def sign_and_submit(self, payload: Dict, network: str) -> str:
30
+ txn = await self._client.submit_transaction(
31
+ self._account, payload
32
+ )
33
+ return txn["hash"]
34
+ ```
35
+ """
36
+
37
+ def address(self) -> str:
38
+ """Return the signer's Aptos address (0x-prefixed hex).
39
+
40
+ Returns:
41
+ The account address as a hex string.
42
+ """
43
+ ...
44
+
45
+ async def sign_and_submit(self, payload: Dict[str, Any], network: str) -> str:
46
+ """Sign and submit a transaction to the Aptos network.
47
+
48
+ Args:
49
+ payload: Transaction payload dict with function, type_arguments, and arguments.
50
+ network: CAIP-2 network identifier (e.g., "aptos:1").
51
+
52
+ Returns:
53
+ Transaction hash as a 0x-prefixed hex string.
54
+
55
+ Raises:
56
+ Exception: If signing or submission fails.
57
+ """
58
+ ...
59
+
60
+
61
+ @runtime_checkable
62
+ class FacilitatorAptosSigner(Protocol):
63
+ """Protocol for Aptos facilitator-side operations.
64
+
65
+ Implementations must provide the ability to query transactions
66
+ from the Aptos network for verification.
67
+
68
+ Example:
69
+ ```python
70
+ import httpx
71
+
72
+ class MyAptosQuerier:
73
+ def __init__(self, rpc_url: str):
74
+ self._rpc_url = rpc_url
75
+ self._client = httpx.AsyncClient()
76
+
77
+ def get_addresses(self, network: str) -> List[str]:
78
+ return [] # No on-chain addresses needed for exact-direct
79
+
80
+ async def get_transaction(self, tx_hash: str, network: str) -> Dict:
81
+ resp = await self._client.get(
82
+ f"{self._rpc_url}/transactions/by_hash/{tx_hash}"
83
+ )
84
+ return resp.json()
85
+ ```
86
+ """
87
+
88
+ def get_addresses(self, network: str) -> List[str]:
89
+ """Return the facilitator's Aptos addresses for a network.
90
+
91
+ For exact-direct, the facilitator typically does not hold funds
92
+ and may return an empty list.
93
+
94
+ Args:
95
+ network: CAIP-2 network identifier.
96
+
97
+ Returns:
98
+ List of facilitator addresses.
99
+ """
100
+ ...
101
+
102
+ async def get_transaction(self, tx_hash: str, network: str) -> Dict[str, Any]:
103
+ """Query a transaction by hash from the Aptos network.
104
+
105
+ Args:
106
+ tx_hash: The transaction hash to query (0x-prefixed).
107
+ network: CAIP-2 network identifier.
108
+
109
+ Returns:
110
+ Transaction result dict from the Aptos REST API containing fields:
111
+ - hash: Transaction hash
112
+ - version: Ledger version
113
+ - success: Whether the transaction succeeded
114
+ - vm_status: VM execution status
115
+ - sender: Transaction sender address
116
+ - timestamp: Block timestamp in microseconds
117
+ - payload: Transaction payload
118
+ - events: Transaction events
119
+
120
+ Raises:
121
+ Exception: If the transaction is not found or the query fails.
122
+ """
123
+ ...
124
+
125
+
126
+ class ExactDirectPayload:
127
+ """Represents the payment payload for the exact-direct scheme on Aptos.
128
+
129
+ This payload contains proof that the client has already executed a
130
+ fungible asset transfer on-chain.
131
+
132
+ Attributes:
133
+ tx_hash: The transaction hash of the completed transfer.
134
+ from_address: The sender's Aptos address.
135
+ to_address: The recipient's Aptos address.
136
+ amount: The transfer amount in atomic units.
137
+ metadata_address: The FA metadata object address.
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ tx_hash: str,
143
+ from_address: str,
144
+ to_address: str,
145
+ amount: str,
146
+ metadata_address: str,
147
+ ) -> None:
148
+ self.tx_hash = tx_hash
149
+ self.from_address = from_address
150
+ self.to_address = to_address
151
+ self.amount = amount
152
+ self.metadata_address = metadata_address
153
+
154
+ def to_dict(self) -> Dict[str, Any]:
155
+ """Convert to dictionary for serialization.
156
+
157
+ Returns:
158
+ Dict with camelCase keys matching the protocol format.
159
+ """
160
+ return {
161
+ "txHash": self.tx_hash,
162
+ "from": self.from_address,
163
+ "to": self.to_address,
164
+ "amount": self.amount,
165
+ "metadataAddress": self.metadata_address,
166
+ }
167
+
168
+ @classmethod
169
+ def from_dict(cls, data: Dict[str, Any]) -> "ExactDirectPayload":
170
+ """Create an ExactDirectPayload from a dictionary.
171
+
172
+ Args:
173
+ data: Dict with payload fields (supports both camelCase and snake_case).
174
+
175
+ Returns:
176
+ ExactDirectPayload instance.
177
+
178
+ Raises:
179
+ ValueError: If required fields are missing.
180
+ """
181
+ tx_hash = data.get("txHash") or data.get("tx_hash", "")
182
+ from_address = data.get("from") or data.get("from_address", "")
183
+ to_address = data.get("to") or data.get("to_address", "")
184
+ amount = data.get("amount", "")
185
+ metadata_address = (
186
+ data.get("metadataAddress") or data.get("metadata_address", "")
187
+ )
188
+
189
+ return cls(
190
+ tx_hash=tx_hash,
191
+ from_address=from_address,
192
+ to_address=to_address,
193
+ amount=amount,
194
+ metadata_address=metadata_address,
195
+ )
196
+
197
+
198
+ def extract_transfer_details(tx: Dict[str, Any]) -> Optional[Dict[str, str]]:
199
+ """Extract fungible asset transfer details from a transaction result.
200
+
201
+ Parses the transaction payload to extract sender, recipient, amount,
202
+ and metadata address from a primary_fungible_store::transfer call.
203
+
204
+ Args:
205
+ tx: Transaction result dict from the Aptos REST API.
206
+
207
+ Returns:
208
+ Dict with 'from', 'to', 'amount', 'metadata_address' keys,
209
+ or None if the transaction is not a valid FA transfer.
210
+ """
211
+ if not tx or not tx.get("success"):
212
+ return None
213
+
214
+ payload = tx.get("payload")
215
+ if not payload or payload.get("type") != "entry_function_payload":
216
+ return None
217
+
218
+ function = payload.get("function", "")
219
+ if "primary_fungible_store::transfer" not in function:
220
+ return None
221
+
222
+ arguments = payload.get("arguments", [])
223
+ if len(arguments) < 3:
224
+ return None
225
+
226
+ metadata_address = str(arguments[0])
227
+ to_address = str(arguments[1])
228
+ amount = str(arguments[2])
229
+
230
+ sender = tx.get("sender", "")
231
+
232
+ return {
233
+ "from": sender,
234
+ "to": to_address,
235
+ "amount": amount,
236
+ "metadata_address": metadata_address,
237
+ }
@@ -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/migration/exact-legacy 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", {})