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.
Files changed (51) hide show
  1. t402/__init__.py +1 -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/constants.py +1 -1
  6. t402/django/__init__.py +42 -0
  7. t402/django/middleware.py +596 -0
  8. t402/errors.py +213 -0
  9. t402/facilitator.py +125 -0
  10. t402/mcp/constants.py +3 -6
  11. t402/mcp/server.py +428 -44
  12. t402/mcp/web3_utils.py +493 -0
  13. t402/multisig/__init__.py +120 -0
  14. t402/multisig/constants.py +54 -0
  15. t402/multisig/safe.py +441 -0
  16. t402/multisig/signature.py +228 -0
  17. t402/multisig/transaction.py +238 -0
  18. t402/multisig/types.py +108 -0
  19. t402/multisig/utils.py +77 -0
  20. t402/schemes/__init__.py +19 -0
  21. t402/schemes/cosmos/__init__.py +114 -0
  22. t402/schemes/cosmos/constants.py +211 -0
  23. t402/schemes/cosmos/exact_direct/__init__.py +21 -0
  24. t402/schemes/cosmos/exact_direct/client.py +198 -0
  25. t402/schemes/cosmos/exact_direct/facilitator.py +493 -0
  26. t402/schemes/cosmos/exact_direct/server.py +315 -0
  27. t402/schemes/cosmos/types.py +501 -0
  28. t402/schemes/evm/__init__.py +1 -1
  29. t402/schemes/evm/exact_legacy/server.py +1 -1
  30. t402/schemes/near/__init__.py +25 -0
  31. t402/schemes/near/upto/__init__.py +54 -0
  32. t402/schemes/near/upto/types.py +272 -0
  33. t402/schemes/svm/__init__.py +15 -0
  34. t402/schemes/svm/upto/__init__.py +23 -0
  35. t402/schemes/svm/upto/types.py +193 -0
  36. t402/schemes/ton/__init__.py +15 -0
  37. t402/schemes/ton/upto/__init__.py +31 -0
  38. t402/schemes/ton/upto/types.py +215 -0
  39. t402/schemes/tron/__init__.py +21 -4
  40. t402/schemes/tron/upto/__init__.py +30 -0
  41. t402/schemes/tron/upto/types.py +213 -0
  42. t402/starlette/__init__.py +38 -0
  43. t402/starlette/middleware.py +522 -0
  44. t402/ton.py +1 -1
  45. t402/ton_paywall_template.py +1 -1
  46. t402/types.py +100 -2
  47. t402/wdk/chains.py +1 -1
  48. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/METADATA +3 -3
  49. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/RECORD +51 -20
  50. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/WHEEL +0 -0
  51. {t402-1.9.1.dist-info → t402-1.10.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,215 @@
1
+ """TON Up-To Scheme Types.
2
+
3
+ TON-specific types for the up-to payment scheme using an escrow pattern.
4
+ The client transfers maxAmount to the facilitator's holding address,
5
+ and the facilitator forwards settleAmount to payTo and refunds the rest.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, Optional
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+ from pydantic.alias_generators import to_camel
13
+
14
+
15
+ class UptoTonAuthorization(BaseModel):
16
+ """TON upto authorization metadata.
17
+
18
+ Contains all parameters for verifying the signed transfer message.
19
+ """
20
+
21
+ from_address: str = Field(
22
+ alias="from",
23
+ description="Sender wallet address (friendly format, bounceable)",
24
+ )
25
+ facilitator: str = Field(
26
+ description="Facilitator holding address that receives the initial transfer",
27
+ )
28
+ jetton_master: str = Field(
29
+ alias="jettonMaster",
30
+ description="Jetton master contract address",
31
+ )
32
+ max_amount: str = Field(
33
+ alias="maxAmount",
34
+ description="Maximum authorized amount in smallest units",
35
+ )
36
+ ton_amount: str = Field(
37
+ alias="tonAmount",
38
+ description="Gas amount in nanoTON",
39
+ )
40
+ valid_until: int = Field(
41
+ alias="validUntil",
42
+ description="Unix timestamp (seconds) until which the message is valid",
43
+ )
44
+ seqno: int = Field(
45
+ description="Wallet sequence number for replay protection",
46
+ )
47
+ query_id: str = Field(
48
+ alias="queryId",
49
+ description="Unique message ID (as string for large numbers)",
50
+ )
51
+
52
+ model_config = ConfigDict(
53
+ alias_generator=to_camel,
54
+ populate_by_name=True,
55
+ from_attributes=True,
56
+ )
57
+
58
+
59
+ class UptoTonPayload(BaseModel):
60
+ """TON upto payment payload.
61
+
62
+ Contains a signed transfer message to the facilitator's holding address.
63
+ The facilitator broadcasts the transfer, then forwards settleAmount to payTo
64
+ and refunds (maxAmount - settleAmount) back to the client.
65
+ """
66
+
67
+ signed_boc: str = Field(
68
+ alias="signedBoc",
69
+ description="Base64 encoded signed external message (BOC format)",
70
+ )
71
+ authorization: UptoTonAuthorization = Field(
72
+ description="Transfer authorization metadata for verification",
73
+ )
74
+ payment_nonce: str = Field(
75
+ alias="paymentNonce",
76
+ description="Unique nonce for replay protection (hex string)",
77
+ )
78
+
79
+ model_config = ConfigDict(
80
+ alias_generator=to_camel,
81
+ populate_by_name=True,
82
+ from_attributes=True,
83
+ )
84
+
85
+ def to_dict(self) -> Dict[str, Any]:
86
+ """Convert to dictionary for JSON serialization.
87
+
88
+ Returns:
89
+ Dictionary with camelCase keys matching the wire format.
90
+ """
91
+ return {
92
+ "signedBoc": self.signed_boc,
93
+ "authorization": {
94
+ "from": self.authorization.from_address,
95
+ "facilitator": self.authorization.facilitator,
96
+ "jettonMaster": self.authorization.jetton_master,
97
+ "maxAmount": self.authorization.max_amount,
98
+ "tonAmount": self.authorization.ton_amount,
99
+ "validUntil": self.authorization.valid_until,
100
+ "seqno": self.authorization.seqno,
101
+ "queryId": self.authorization.query_id,
102
+ },
103
+ "paymentNonce": self.payment_nonce,
104
+ }
105
+
106
+
107
+ class UptoTonExtra(BaseModel):
108
+ """TON-specific extra fields for the upto scheme.
109
+
110
+ Included in PaymentRequirements.extra to provide upto-specific parameters.
111
+ """
112
+
113
+ facilitator: Optional[str] = Field(
114
+ default=None,
115
+ description="Facilitator address that will receive the initial transfer",
116
+ )
117
+ max_amount: Optional[str] = Field(
118
+ default=None,
119
+ alias="maxAmount",
120
+ description="Maximum payment amount authorized",
121
+ )
122
+ min_amount: Optional[str] = Field(
123
+ default=None,
124
+ alias="minAmount",
125
+ description="Minimum acceptable settlement amount",
126
+ )
127
+ unit: Optional[str] = Field(
128
+ default=None,
129
+ description="Billing unit (e.g., 'token', 'request', 'second')",
130
+ )
131
+ unit_price: Optional[str] = Field(
132
+ default=None,
133
+ alias="unitPrice",
134
+ description="Price per unit in smallest denomination",
135
+ )
136
+
137
+ model_config = ConfigDict(
138
+ alias_generator=to_camel,
139
+ populate_by_name=True,
140
+ from_attributes=True,
141
+ )
142
+
143
+
144
+ def is_upto_ton_payload(data: Any) -> bool:
145
+ """Check if the given data represents a TON upto payload.
146
+
147
+ Validates the presence and types of required fields including signedBoc,
148
+ authorization (with from, facilitator, jettonMaster, maxAmount), and paymentNonce.
149
+
150
+ Args:
151
+ data: Dictionary or object to check.
152
+
153
+ Returns:
154
+ True if data has the correct TON upto payload structure.
155
+ """
156
+ if not isinstance(data, dict):
157
+ return False
158
+
159
+ signed_boc = data.get("signedBoc")
160
+ payment_nonce = data.get("paymentNonce")
161
+ auth = data.get("authorization")
162
+
163
+ if not signed_boc or not isinstance(signed_boc, str):
164
+ return False
165
+ if not payment_nonce or not isinstance(payment_nonce, str):
166
+ return False
167
+
168
+ if not auth or not isinstance(auth, dict):
169
+ return False
170
+
171
+ # Check required authorization fields
172
+ required_str_fields = ["from", "facilitator", "jettonMaster", "maxAmount", "tonAmount", "queryId"]
173
+ for field in required_str_fields:
174
+ val = auth.get(field)
175
+ if not isinstance(val, str):
176
+ return False
177
+
178
+ # from and facilitator must not be empty
179
+ if not auth.get("from") or not auth.get("facilitator"):
180
+ return False
181
+
182
+ # Check numeric fields
183
+ if not isinstance(auth.get("validUntil"), (int, float)):
184
+ return False
185
+ if not isinstance(auth.get("seqno"), (int, float)):
186
+ return False
187
+
188
+ return True
189
+
190
+
191
+ def upto_payload_from_dict(data: Dict[str, Any]) -> UptoTonPayload:
192
+ """Create an UptoTonPayload from a dictionary.
193
+
194
+ Args:
195
+ data: Dictionary containing payload data with camelCase keys.
196
+
197
+ Returns:
198
+ UptoTonPayload instance.
199
+ """
200
+ auth_data = data.get("authorization", {})
201
+
202
+ return UptoTonPayload(
203
+ signed_boc=data.get("signedBoc", ""),
204
+ authorization=UptoTonAuthorization(
205
+ from_address=auth_data.get("from", ""),
206
+ facilitator=auth_data.get("facilitator", ""),
207
+ jetton_master=auth_data.get("jettonMaster", ""),
208
+ max_amount=auth_data.get("maxAmount", ""),
209
+ ton_amount=auth_data.get("tonAmount", ""),
210
+ valid_until=auth_data.get("validUntil", 0),
211
+ seqno=auth_data.get("seqno", 0),
212
+ query_id=auth_data.get("queryId", ""),
213
+ ),
214
+ payment_nonce=data.get("paymentNonce", ""),
215
+ )
@@ -4,6 +4,7 @@ This package provides payment scheme implementations for TRON blockchain.
4
4
 
5
5
  Supported schemes:
6
6
  - exact: TRC-20 token transfers with signed transactions
7
+ - upto: TRC-20 approve + transferFrom for authorized maximum-amount payments
7
8
  """
8
9
 
9
10
  from t402.schemes.tron.exact import (
@@ -16,16 +17,32 @@ from t402.schemes.tron.exact import (
16
17
  SCHEME_EXACT,
17
18
  )
18
19
 
20
+ from t402.schemes.tron.upto import (
21
+ UptoTronAuthorization,
22
+ UptoTronPayload,
23
+ UptoTronExtra,
24
+ is_upto_tron_payload,
25
+ upto_payload_from_dict,
26
+ )
27
+
19
28
  __all__ = [
20
- # Client
29
+ # Exact - Client
21
30
  "ExactTronClientScheme",
22
31
  "TronSigner",
23
- # Server
32
+ # Exact - Server
24
33
  "ExactTronServerScheme",
25
- # Facilitator
34
+ # Exact - Facilitator
26
35
  "ExactTronFacilitatorScheme",
27
36
  "ExactTronFacilitatorConfig",
28
37
  "FacilitatorTronSigner",
29
- # Constants
38
+ # Exact - Constants
30
39
  "SCHEME_EXACT",
40
+ # Upto - Types
41
+ "UptoTronAuthorization",
42
+ "UptoTronPayload",
43
+ "UptoTronExtra",
44
+ # Upto - Type guards
45
+ "is_upto_tron_payload",
46
+ # Upto - Helpers
47
+ "upto_payload_from_dict",
31
48
  ]
@@ -0,0 +1,30 @@
1
+ """TRON Up-To Payment Scheme.
2
+
3
+ This package provides the upto payment scheme types for TRON networks
4
+ using TRC-20 approve + transferFrom for authorized maximum-amount payments.
5
+
6
+ The upto scheme allows clients to authorize a maximum amount that can be
7
+ settled later based on actual usage.
8
+ """
9
+
10
+ from t402.schemes.tron.upto.types import (
11
+ # Models
12
+ UptoTronAuthorization,
13
+ UptoTronPayload,
14
+ UptoTronExtra,
15
+ # Type guards
16
+ is_upto_tron_payload,
17
+ # Helper functions
18
+ upto_payload_from_dict,
19
+ )
20
+
21
+ __all__ = [
22
+ # Types
23
+ "UptoTronAuthorization",
24
+ "UptoTronPayload",
25
+ "UptoTronExtra",
26
+ # Type guards
27
+ "is_upto_tron_payload",
28
+ # Helper functions
29
+ "upto_payload_from_dict",
30
+ ]
@@ -0,0 +1,213 @@
1
+ """TRON Up-To Scheme Types.
2
+
3
+ TRON-specific types for the up-to payment scheme using TRC-20 approve + transferFrom.
4
+ The approve pattern allows the facilitator to execute a transferFrom up to the
5
+ authorized maximum amount on behalf of the payer.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, Optional
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+ from pydantic.alias_generators import to_camel
13
+
14
+
15
+ class UptoTronAuthorization(BaseModel):
16
+ """TRC-20 approve authorization metadata.
17
+
18
+ Contains all information needed to verify the approve transaction
19
+ without parsing the signed transaction.
20
+ """
21
+
22
+ owner: str = Field(description="Token owner address (T-prefix base58check)")
23
+ spender: str = Field(description="Approved spender address - facilitator (T-prefix base58check)")
24
+ contract_address: str = Field(
25
+ alias="contractAddress",
26
+ description="TRC-20 contract address (T-prefix base58check)",
27
+ )
28
+ max_amount: str = Field(
29
+ alias="maxAmount",
30
+ description="Maximum approved amount in smallest units (as string)",
31
+ )
32
+ expiration: int = Field(
33
+ description="Transaction expiration timestamp (milliseconds since epoch)",
34
+ )
35
+ ref_block_bytes: str = Field(
36
+ alias="refBlockBytes",
37
+ description="Reference block bytes (hex string)",
38
+ )
39
+ ref_block_hash: str = Field(
40
+ alias="refBlockHash",
41
+ description="Reference block hash (hex string)",
42
+ )
43
+ timestamp: int = Field(
44
+ description="Transaction timestamp (milliseconds since epoch)",
45
+ )
46
+
47
+ model_config = ConfigDict(
48
+ alias_generator=to_camel,
49
+ populate_by_name=True,
50
+ from_attributes=True,
51
+ )
52
+
53
+
54
+ class UptoTronPayload(BaseModel):
55
+ """TRON upto payment payload.
56
+
57
+ Contains a signed TRC-20 approve transaction that authorizes the
58
+ facilitator to transfer up to maxAmount of tokens on behalf of the payer.
59
+ """
60
+
61
+ signed_transaction: str = Field(
62
+ alias="signedTransaction",
63
+ description="Hex-encoded signed approve transaction",
64
+ )
65
+ authorization: UptoTronAuthorization = Field(
66
+ description="Approve transaction authorization metadata",
67
+ )
68
+ payment_nonce: str = Field(
69
+ alias="paymentNonce",
70
+ description="Unique nonce for replay protection (hex string)",
71
+ )
72
+
73
+ model_config = ConfigDict(
74
+ alias_generator=to_camel,
75
+ populate_by_name=True,
76
+ from_attributes=True,
77
+ )
78
+
79
+ def to_dict(self) -> Dict[str, Any]:
80
+ """Convert to dictionary for JSON serialization.
81
+
82
+ Returns:
83
+ Dictionary with camelCase keys matching the Go/TypeScript wire format.
84
+ """
85
+ return {
86
+ "signedTransaction": self.signed_transaction,
87
+ "authorization": {
88
+ "owner": self.authorization.owner,
89
+ "spender": self.authorization.spender,
90
+ "contractAddress": self.authorization.contract_address,
91
+ "maxAmount": self.authorization.max_amount,
92
+ "expiration": self.authorization.expiration,
93
+ "refBlockBytes": self.authorization.ref_block_bytes,
94
+ "refBlockHash": self.authorization.ref_block_hash,
95
+ "timestamp": self.authorization.timestamp,
96
+ },
97
+ "paymentNonce": self.payment_nonce,
98
+ }
99
+
100
+
101
+ class UptoTronExtra(BaseModel):
102
+ """TRON-specific extra fields for upto payment requirements.
103
+
104
+ Included in the PaymentRequirements.extra field to communicate
105
+ upto-specific parameters to the client.
106
+ """
107
+
108
+ max_amount: Optional[str] = Field(
109
+ default=None,
110
+ alias="maxAmount",
111
+ description="Maximum payment amount authorized",
112
+ )
113
+ min_amount: Optional[str] = Field(
114
+ default=None,
115
+ alias="minAmount",
116
+ description="Minimum acceptable settlement amount",
117
+ )
118
+ unit: Optional[str] = Field(
119
+ default=None,
120
+ description="Billing unit (e.g., 'token', 'request', 'second')",
121
+ )
122
+ unit_price: Optional[str] = Field(
123
+ default=None,
124
+ alias="unitPrice",
125
+ description="Price per unit in smallest denomination",
126
+ )
127
+ spender_address: Optional[str] = Field(
128
+ default=None,
129
+ alias="spenderAddress",
130
+ description="Facilitator address that will be approved as spender",
131
+ )
132
+
133
+ model_config = ConfigDict(
134
+ alias_generator=to_camel,
135
+ populate_by_name=True,
136
+ from_attributes=True,
137
+ )
138
+
139
+
140
+ def is_upto_tron_payload(data: Dict[str, Any]) -> bool:
141
+ """Check if the given data represents an upto TRON payload.
142
+
143
+ Validates that the data has the correct structure for a TRON upto
144
+ approve payload, including nested authorization fields.
145
+
146
+ Args:
147
+ data: Dictionary to check
148
+
149
+ Returns:
150
+ True if data has the correct upto TRON payload structure
151
+ """
152
+ if not isinstance(data, dict):
153
+ return False
154
+
155
+ # Check top-level fields
156
+ if not isinstance(data.get("signedTransaction"), str) or not data["signedTransaction"]:
157
+ return False
158
+
159
+ if not isinstance(data.get("paymentNonce"), str) or not data["paymentNonce"]:
160
+ return False
161
+
162
+ auth = data.get("authorization")
163
+ if not isinstance(auth, dict):
164
+ return False
165
+
166
+ # Check required authorization fields
167
+ required_string_fields = ["owner", "spender", "contractAddress", "maxAmount"]
168
+ for field in required_string_fields:
169
+ if not isinstance(auth.get(field), str) or not auth[field]:
170
+ return False
171
+
172
+ # Check numeric authorization fields
173
+ if not isinstance(auth.get("expiration"), (int, float)):
174
+ return False
175
+
176
+ if not isinstance(auth.get("timestamp"), (int, float)):
177
+ return False
178
+
179
+ # refBlockBytes and refBlockHash should be strings
180
+ if not isinstance(auth.get("refBlockBytes"), str):
181
+ return False
182
+
183
+ if not isinstance(auth.get("refBlockHash"), str):
184
+ return False
185
+
186
+ return True
187
+
188
+
189
+ def upto_payload_from_dict(data: Dict[str, Any]) -> UptoTronPayload:
190
+ """Create an UptoTronPayload from a dictionary.
191
+
192
+ Args:
193
+ data: Dictionary containing payload data with camelCase keys
194
+
195
+ Returns:
196
+ UptoTronPayload instance
197
+ """
198
+ auth_data = data.get("authorization", {})
199
+
200
+ return UptoTronPayload(
201
+ signed_transaction=data.get("signedTransaction", ""),
202
+ authorization=UptoTronAuthorization(
203
+ owner=auth_data.get("owner", ""),
204
+ spender=auth_data.get("spender", ""),
205
+ contract_address=auth_data.get("contractAddress", ""),
206
+ max_amount=auth_data.get("maxAmount", ""),
207
+ expiration=auth_data.get("expiration", 0),
208
+ ref_block_bytes=auth_data.get("refBlockBytes", ""),
209
+ ref_block_hash=auth_data.get("refBlockHash", ""),
210
+ timestamp=auth_data.get("timestamp", 0),
211
+ ),
212
+ payment_nonce=data.get("paymentNonce", ""),
213
+ )
@@ -0,0 +1,38 @@
1
+ """Starlette Integration for T402 Payment Protocol.
2
+
3
+ This package provides Starlette ASGI middleware for integrating T402 payments
4
+ into Starlette applications.
5
+
6
+ Features:
7
+ - PaymentMiddleware: Class-based middleware for protecting routes
8
+ - V1 and V2 protocol support
9
+ - Automatic settlement on successful responses
10
+ - Browser paywall support
11
+
12
+ Usage:
13
+ ```python
14
+ from starlette.applications import Starlette
15
+ from t402.starlette import PaymentMiddleware
16
+
17
+ app = Starlette()
18
+ payment = PaymentMiddleware(app)
19
+ payment.add(
20
+ path="/api/*",
21
+ price="$0.10",
22
+ pay_to_address="0x1234...",
23
+ network="eip155:8453",
24
+ )
25
+ ```
26
+ """
27
+
28
+ from t402.starlette.middleware import (
29
+ PaymentMiddleware,
30
+ PaymentConfig,
31
+ PaymentDetails,
32
+ )
33
+
34
+ __all__ = [
35
+ "PaymentMiddleware",
36
+ "PaymentConfig",
37
+ "PaymentDetails",
38
+ ]