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,419 @@
1
+ """NEAR blockchain types for the T402 protocol.
2
+
3
+ This module defines the data types used by the NEAR exact-direct payment scheme,
4
+ including RPC request/response types, transaction structures, and payload types.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
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
+
16
+ # NEAR account ID validation regex.
17
+ # NEAR accounts are either implicit (64 hex chars) or named (multiple dot-separated
18
+ # segments where each segment is alphanumeric with optional hyphens/underscores).
19
+ # Examples: alice.near, sub.alice.near, usdt.tether-token.near, my_account.testnet
20
+ NEAR_ACCOUNT_ID_REGEX = re.compile(
21
+ r"^(([a-z\d]+[-_])*[a-z\d]+\.)+([a-z\d]+[-_])*[a-z\d]+$|^[0-9a-f]{64}$"
22
+ )
23
+
24
+
25
+ def is_valid_account_id(account_id: str) -> bool:
26
+ """Validate a NEAR account ID format.
27
+
28
+ NEAR account IDs follow these rules:
29
+ - Must be between 2 and 64 characters
30
+ - Can be implicit (64 hex chars) or named (dot-separated segments)
31
+ - Named segments are alphanumeric with optional hyphens/underscores
32
+
33
+ Args:
34
+ account_id: The NEAR account ID to validate.
35
+
36
+ Returns:
37
+ True if the account ID is valid.
38
+ """
39
+ if not account_id:
40
+ return False
41
+ if len(account_id) < 2 or len(account_id) > 64:
42
+ return False
43
+ return bool(NEAR_ACCOUNT_ID_REGEX.match(account_id))
44
+
45
+
46
+ # =============================================================================
47
+ # Signer Protocols
48
+ # =============================================================================
49
+
50
+
51
+ @runtime_checkable
52
+ class ClientNearSigner(Protocol):
53
+ """Protocol for NEAR client-side signing operations.
54
+
55
+ Implementations should handle key management and transaction signing
56
+ for the NEAR blockchain.
57
+
58
+ Example:
59
+ ```python
60
+ class MyNearSigner:
61
+ def account_id(self) -> str:
62
+ return "alice.near"
63
+
64
+ async def sign_and_send_transaction(
65
+ self, receiver_id: str, actions: List[Dict], network: str
66
+ ) -> str:
67
+ # Build, sign, and send the transaction
68
+ return "tx_hash_here"
69
+ ```
70
+ """
71
+
72
+ def account_id(self) -> str:
73
+ """Get the signer's NEAR account ID.
74
+
75
+ Returns:
76
+ The NEAR account ID (e.g., "alice.near").
77
+ """
78
+ ...
79
+
80
+ async def sign_and_send_transaction(
81
+ self,
82
+ receiver_id: str,
83
+ actions: List[Dict[str, Any]],
84
+ network: str,
85
+ ) -> str:
86
+ """Sign and send a NEAR transaction.
87
+
88
+ Args:
89
+ receiver_id: The contract account receiving the function call.
90
+ actions: The actions to include in the transaction.
91
+ network: The network to send the transaction on (e.g., "near:mainnet").
92
+
93
+ Returns:
94
+ Transaction hash string on success.
95
+
96
+ Raises:
97
+ Exception: If the transaction fails.
98
+ """
99
+ ...
100
+
101
+
102
+ @runtime_checkable
103
+ class FacilitatorNearSigner(Protocol):
104
+ """Protocol for NEAR facilitator-side operations.
105
+
106
+ Implementations should handle transaction querying and balance lookups
107
+ for the NEAR blockchain.
108
+
109
+ Example:
110
+ ```python
111
+ class MyNearFacilitator:
112
+ def get_addresses(self, network: str) -> List[str]:
113
+ return ["facilitator.near"]
114
+
115
+ async def query_transaction(
116
+ self, tx_hash: str, sender_id: str, network: str
117
+ ) -> Dict[str, Any]:
118
+ # Query the NEAR RPC for the transaction
119
+ return {...}
120
+ ```
121
+ """
122
+
123
+ def get_addresses(self, network: str) -> List[str]:
124
+ """Get the facilitator's NEAR account IDs for a network.
125
+
126
+ Args:
127
+ network: The CAIP-2 network identifier.
128
+
129
+ Returns:
130
+ List of NEAR account IDs.
131
+ """
132
+ ...
133
+
134
+ async def query_transaction(
135
+ self,
136
+ tx_hash: str,
137
+ sender_id: str,
138
+ network: str,
139
+ ) -> Dict[str, Any]:
140
+ """Query a transaction by hash from the NEAR RPC.
141
+
142
+ The returned dict should match the NEAR RPC tx response format:
143
+ {
144
+ "status": {"SuccessValue": "...", ...},
145
+ "transaction": {
146
+ "hash": "...",
147
+ "signer_id": "...",
148
+ "receiver_id": "...",
149
+ "actions": [...]
150
+ },
151
+ ...
152
+ }
153
+
154
+ Args:
155
+ tx_hash: The transaction hash to query.
156
+ sender_id: The sender's account ID (needed for RPC query).
157
+ network: The CAIP-2 network identifier.
158
+
159
+ Returns:
160
+ Transaction result dict from the NEAR RPC.
161
+
162
+ Raises:
163
+ Exception: If the transaction is not found or the query fails.
164
+ """
165
+ ...
166
+
167
+
168
+ # =============================================================================
169
+ # Payload Types
170
+ # =============================================================================
171
+
172
+
173
+ class ExactDirectPayload(BaseModel):
174
+ """Payload for the exact-direct scheme on NEAR.
175
+
176
+ Contains the transaction hash as proof of on-chain payment,
177
+ along with transfer details for verification.
178
+
179
+ Attributes:
180
+ tx_hash: The on-chain transaction hash.
181
+ from_account: The sender's NEAR account ID.
182
+ to_account: The recipient's NEAR account ID.
183
+ amount: The transfer amount in atomic units.
184
+ """
185
+
186
+ tx_hash: str = Field(alias="txHash")
187
+ from_account: str = Field(alias="from")
188
+ to_account: str = Field(alias="to")
189
+ amount: str
190
+
191
+ model_config = ConfigDict(
192
+ alias_generator=to_camel,
193
+ populate_by_name=True,
194
+ from_attributes=True,
195
+ )
196
+
197
+ @field_validator("amount")
198
+ @classmethod
199
+ def validate_amount(cls, v: str) -> str:
200
+ """Validate that amount is a valid integer string."""
201
+ try:
202
+ int(v)
203
+ except ValueError:
204
+ raise ValueError("amount must be an integer encoded as a string")
205
+ return v
206
+
207
+ def to_map(self) -> Dict[str, Any]:
208
+ """Convert the payload to a plain dict for inclusion in PaymentPayload.
209
+
210
+ Returns:
211
+ Dict with txHash, from, to, and amount fields.
212
+ """
213
+ return {
214
+ "txHash": self.tx_hash,
215
+ "from": self.from_account,
216
+ "to": self.to_account,
217
+ "amount": self.amount,
218
+ }
219
+
220
+ @classmethod
221
+ def from_map(cls, data: Dict[str, Any]) -> "ExactDirectPayload":
222
+ """Create an ExactDirectPayload from a plain dict.
223
+
224
+ Args:
225
+ data: Dict with txHash, from, to, and amount fields.
226
+
227
+ Returns:
228
+ ExactDirectPayload instance.
229
+
230
+ Raises:
231
+ ValueError: If required fields are missing.
232
+ """
233
+ return cls(
234
+ tx_hash=data.get("txHash", ""),
235
+ from_account=data.get("from", ""),
236
+ to_account=data.get("to", ""),
237
+ amount=data.get("amount", "0"),
238
+ )
239
+
240
+
241
+ # =============================================================================
242
+ # NEP-141 Action Types
243
+ # =============================================================================
244
+
245
+
246
+ class FtTransferArgs(BaseModel):
247
+ """Arguments for the NEP-141 ft_transfer function call.
248
+
249
+ Attributes:
250
+ receiver_id: The recipient's NEAR account ID.
251
+ amount: The transfer amount in atomic units (string).
252
+ memo: Optional memo to include with the transfer.
253
+ """
254
+
255
+ receiver_id: str
256
+ amount: str
257
+ memo: Optional[str] = None
258
+
259
+ model_config = ConfigDict(
260
+ populate_by_name=True,
261
+ from_attributes=True,
262
+ )
263
+
264
+
265
+ class FunctionCallAction(BaseModel):
266
+ """Represents a NEAR function call action.
267
+
268
+ Attributes:
269
+ method_name: The name of the contract method to call.
270
+ args: The JSON-serialized arguments.
271
+ gas: The gas to attach (in yoctoNEAR units of gas).
272
+ deposit: The deposit to attach (in yoctoNEAR).
273
+ """
274
+
275
+ method_name: str
276
+ args: str # JSON-serialized arguments
277
+ gas: int
278
+ deposit: str
279
+
280
+ model_config = ConfigDict(
281
+ populate_by_name=True,
282
+ from_attributes=True,
283
+ )
284
+
285
+ def to_action_dict(self) -> Dict[str, Any]:
286
+ """Convert to the action dict format expected by signers.
287
+
288
+ Returns:
289
+ Dict with FunctionCall action structure.
290
+ """
291
+ return {
292
+ "FunctionCall": {
293
+ "method_name": self.method_name,
294
+ "args": self.args,
295
+ "gas": self.gas,
296
+ "deposit": self.deposit,
297
+ }
298
+ }
299
+
300
+
301
+ # =============================================================================
302
+ # RPC Types
303
+ # =============================================================================
304
+
305
+
306
+ class TransactionStatus:
307
+ """Represents the status of a NEAR transaction.
308
+
309
+ Attributes:
310
+ success_value: The success value (present if transaction succeeded).
311
+ failure: The failure info (present if transaction failed).
312
+ """
313
+
314
+ def __init__(
315
+ self,
316
+ success_value: Optional[str] = None,
317
+ failure: Optional[Any] = None,
318
+ ) -> None:
319
+ self.success_value = success_value
320
+ self.failure = failure
321
+
322
+ def is_success(self) -> bool:
323
+ """Check if the transaction succeeded.
324
+
325
+ Returns:
326
+ True if the transaction has a SuccessValue and no Failure.
327
+ """
328
+ return self.success_value is not None and self.failure is None
329
+
330
+ @classmethod
331
+ def from_dict(cls, data: Dict[str, Any]) -> "TransactionStatus":
332
+ """Create a TransactionStatus from an RPC response dict.
333
+
334
+ Args:
335
+ data: The status dict from the RPC response.
336
+
337
+ Returns:
338
+ TransactionStatus instance.
339
+ """
340
+ success_value = data.get("SuccessValue")
341
+ failure = data.get("Failure")
342
+ return cls(success_value=success_value, failure=failure)
343
+
344
+
345
+ def parse_transaction_result(data: Dict[str, Any]) -> Dict[str, Any]:
346
+ """Parse a NEAR RPC transaction result into a structured format.
347
+
348
+ Extracts the status, transaction details, and actions from the raw
349
+ RPC response for easier verification.
350
+
351
+ Args:
352
+ data: The raw transaction result from the NEAR RPC.
353
+
354
+ Returns:
355
+ Parsed transaction dict with:
356
+ - status: TransactionStatus
357
+ - transaction: Dict with hash, signer_id, receiver_id, actions
358
+ - ft_transfer_args: Optional FtTransferArgs if ft_transfer found
359
+
360
+ Raises:
361
+ ValueError: If the transaction data is malformed.
362
+ """
363
+ if not data:
364
+ raise ValueError("Empty transaction data")
365
+
366
+ # Parse status
367
+ status_data = data.get("status", {})
368
+ status = TransactionStatus.from_dict(status_data)
369
+
370
+ # Parse transaction
371
+ tx_data = data.get("transaction", {})
372
+ if not tx_data:
373
+ raise ValueError("Missing transaction field in result")
374
+
375
+ receiver_id = tx_data.get("receiver_id", "")
376
+ signer_id = tx_data.get("signer_id", "")
377
+ tx_hash = tx_data.get("hash", "")
378
+ actions = tx_data.get("actions", [])
379
+
380
+ # Find ft_transfer action and parse its args
381
+ ft_transfer_args: Optional[FtTransferArgs] = None
382
+ for action in actions:
383
+ func_call = action.get("FunctionCall")
384
+ if func_call and func_call.get("method_name") == "ft_transfer":
385
+ import base64
386
+ import json
387
+
388
+ args_raw = func_call.get("args", "")
389
+ try:
390
+ # Args are base64-encoded JSON in RPC responses
391
+ args_bytes = base64.b64decode(args_raw)
392
+ args_dict = json.loads(args_bytes)
393
+ except Exception:
394
+ # Try raw JSON if base64 fails
395
+ try:
396
+ if isinstance(args_raw, str):
397
+ args_dict = json.loads(args_raw)
398
+ else:
399
+ args_dict = args_raw
400
+ except Exception:
401
+ continue
402
+
403
+ ft_transfer_args = FtTransferArgs(
404
+ receiver_id=args_dict.get("receiver_id", ""),
405
+ amount=args_dict.get("amount", "0"),
406
+ memo=args_dict.get("memo"),
407
+ )
408
+ break
409
+
410
+ return {
411
+ "status": status,
412
+ "transaction": {
413
+ "hash": tx_hash,
414
+ "signer_id": signer_id,
415
+ "receiver_id": receiver_id,
416
+ "actions": actions,
417
+ },
418
+ "ft_transfer_args": ft_transfer_args,
419
+ }
@@ -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
+ ]