t402 1.6.1__py3-none-any.whl → 1.9.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.
@@ -0,0 +1,260 @@
1
+ """TRON Exact Scheme - Client Implementation.
2
+
3
+ This module provides the client-side implementation of the exact payment scheme
4
+ for TRON network using TRC-20 token transfers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from typing import Any, Dict, Optional, Protocol, Union
11
+
12
+ from t402.types import (
13
+ PaymentRequirementsV2,
14
+ T402_VERSION_V1,
15
+ T402_VERSION_V2,
16
+ )
17
+ from t402.tron import (
18
+ TronAuthorization,
19
+ TronPaymentPayload,
20
+ DEFAULT_FEE_LIMIT,
21
+ validate_tron_address,
22
+ )
23
+
24
+
25
+ # Constants
26
+ SCHEME_EXACT = "exact"
27
+
28
+
29
+ class BlockInfo(Protocol):
30
+ """Protocol for TRON block reference info."""
31
+
32
+ @property
33
+ def ref_block_bytes(self) -> str:
34
+ """Reference block bytes (4 bytes hex)."""
35
+ ...
36
+
37
+ @property
38
+ def ref_block_hash(self) -> str:
39
+ """Reference block hash (8 bytes hex)."""
40
+ ...
41
+
42
+ @property
43
+ def expiration(self) -> int:
44
+ """Transaction expiration timestamp in milliseconds."""
45
+ ...
46
+
47
+
48
+ class TronSigner(Protocol):
49
+ """Protocol for TRON wallet signing operations.
50
+
51
+ Implementations should provide wallet address, block info retrieval,
52
+ and transaction signing capabilities.
53
+
54
+ Example implementation with tronpy:
55
+ ```python
56
+ class MyTronSigner:
57
+ def __init__(self, private_key, client):
58
+ self._private_key = private_key
59
+ self._client = client
60
+ self._address = private_key_to_address(private_key)
61
+
62
+ @property
63
+ def address(self) -> str:
64
+ return self._address
65
+
66
+ async def get_block_info(self) -> BlockInfo:
67
+ block = await self._client.get_latest_block()
68
+ return {
69
+ "ref_block_bytes": block.ref_block_bytes,
70
+ "ref_block_hash": block.ref_block_hash,
71
+ "expiration": int(time.time() * 1000) + 3600000,
72
+ }
73
+
74
+ async def sign_transaction(
75
+ self,
76
+ contract_address: str,
77
+ to: str,
78
+ amount: str,
79
+ fee_limit: int,
80
+ expiration: int,
81
+ ) -> str:
82
+ # Build and sign TRC-20 transfer transaction
83
+ return signed_transaction_hex
84
+ ```
85
+ """
86
+
87
+ @property
88
+ def address(self) -> str:
89
+ """Return the wallet address (T-prefix base58check)."""
90
+ ...
91
+
92
+ async def get_block_info(self) -> Dict[str, Any]:
93
+ """Get the current reference block info for transaction building.
94
+
95
+ Returns:
96
+ Dict with ref_block_bytes, ref_block_hash, and expiration
97
+ """
98
+ ...
99
+
100
+ async def sign_transaction(
101
+ self,
102
+ contract_address: str,
103
+ to: str,
104
+ amount: str,
105
+ fee_limit: int,
106
+ expiration: int,
107
+ ) -> str:
108
+ """Sign a TRC-20 transfer transaction.
109
+
110
+ Args:
111
+ contract_address: TRC-20 contract address
112
+ to: Recipient address
113
+ amount: Amount in smallest units
114
+ fee_limit: Fee limit in SUN
115
+ expiration: Transaction expiration in milliseconds
116
+
117
+ Returns:
118
+ Hex-encoded signed transaction
119
+ """
120
+ ...
121
+
122
+
123
+ class ExactTronClientScheme:
124
+ """Client scheme for TRON exact payments using TRC-20 transfers.
125
+
126
+ Creates signed TRC-20 transfer transactions that can be verified and
127
+ broadcast by a facilitator to complete the payment.
128
+
129
+ Example:
130
+ ```python
131
+ scheme = ExactTronClientScheme(signer=my_tron_signer)
132
+
133
+ payload = await scheme.create_payment_payload(
134
+ t402_version=2,
135
+ requirements={
136
+ "scheme": "exact",
137
+ "network": "tron:mainnet",
138
+ "asset": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
139
+ "amount": "1000000",
140
+ "payTo": "TPayToAddress...",
141
+ "maxTimeoutSeconds": 300,
142
+ },
143
+ )
144
+ ```
145
+ """
146
+
147
+ scheme = SCHEME_EXACT
148
+ caip_family = "tron:*"
149
+
150
+ def __init__(
151
+ self,
152
+ signer: TronSigner,
153
+ fee_limit: Optional[int] = None,
154
+ ):
155
+ """Initialize the TRON client scheme.
156
+
157
+ Args:
158
+ signer: TRON signer for signing transactions
159
+ fee_limit: Override fee limit in SUN (default: 100 TRX)
160
+ """
161
+ self._signer = signer
162
+ self._fee_limit = fee_limit or DEFAULT_FEE_LIMIT
163
+
164
+ @property
165
+ def address(self) -> str:
166
+ """Return the wallet address."""
167
+ return self._signer.address
168
+
169
+ async def create_payment_payload(
170
+ self,
171
+ t402_version: int,
172
+ requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
173
+ ) -> Dict[str, Any]:
174
+ """Create a payment payload for TRC-20 transfer.
175
+
176
+ Args:
177
+ t402_version: Protocol version (1 or 2)
178
+ requirements: Payment requirements
179
+
180
+ Returns:
181
+ Payment payload with signed transaction and authorization metadata
182
+ """
183
+ # Convert to dict for easier access
184
+ if hasattr(requirements, "model_dump"):
185
+ req = requirements.model_dump(by_alias=True)
186
+ else:
187
+ req = dict(requirements)
188
+
189
+ # Extract fields
190
+ network = req.get("network", "")
191
+ asset = req.get("asset", "")
192
+ amount = req.get("amount", "0")
193
+ pay_to = req.get("payTo", "")
194
+ max_timeout = req.get("maxTimeoutSeconds", 300)
195
+
196
+ # Validate required fields
197
+ if not asset:
198
+ raise ValueError("Asset (TRC-20 contract address) is required")
199
+ if not pay_to:
200
+ raise ValueError("PayTo address is required")
201
+ if not amount:
202
+ raise ValueError("Amount is required")
203
+
204
+ # Validate addresses
205
+ if not validate_tron_address(asset):
206
+ raise ValueError(f"Invalid TRC-20 contract address: {asset}")
207
+ if not validate_tron_address(pay_to):
208
+ raise ValueError(f"Invalid payTo address: {pay_to}")
209
+ if not validate_tron_address(self._signer.address):
210
+ raise ValueError(f"Invalid signer address: {self._signer.address}")
211
+
212
+ # Get block info for transaction
213
+ block_info = await self._signer.get_block_info()
214
+ ref_block_bytes = block_info.get("ref_block_bytes", "")
215
+ ref_block_hash = block_info.get("ref_block_hash", "")
216
+
217
+ # Calculate expiration
218
+ now_ms = int(time.time() * 1000)
219
+ expiration = block_info.get("expiration") or (now_ms + max_timeout * 1000)
220
+
221
+ # Sign the transaction
222
+ signed_transaction = await self._signer.sign_transaction(
223
+ contract_address=asset,
224
+ to=pay_to,
225
+ amount=amount,
226
+ fee_limit=self._fee_limit,
227
+ expiration=expiration,
228
+ )
229
+
230
+ # Build authorization metadata
231
+ authorization = TronAuthorization(
232
+ from_=self._signer.address,
233
+ to=pay_to,
234
+ contract_address=asset,
235
+ amount=amount,
236
+ expiration=expiration,
237
+ ref_block_bytes=ref_block_bytes,
238
+ ref_block_hash=ref_block_hash,
239
+ timestamp=now_ms,
240
+ )
241
+
242
+ # Build payload
243
+ payload_data = TronPaymentPayload(
244
+ signed_transaction=signed_transaction,
245
+ authorization=authorization,
246
+ )
247
+
248
+ if t402_version == T402_VERSION_V1:
249
+ return {
250
+ "t402Version": T402_VERSION_V1,
251
+ "scheme": self.scheme,
252
+ "network": network,
253
+ "payload": payload_data.model_dump(by_alias=True),
254
+ }
255
+
256
+ # V2 format
257
+ return {
258
+ "t402Version": T402_VERSION_V2,
259
+ "payload": payload_data.model_dump(by_alias=True),
260
+ }
@@ -0,0 +1,192 @@
1
+ """TRON Exact Scheme - Server Implementation.
2
+
3
+ This module provides the server-side implementation of the exact payment scheme
4
+ for TRON network.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from decimal import Decimal
10
+ from typing import Any, Dict, List, Union
11
+
12
+ from t402.types import (
13
+ PaymentRequirementsV2,
14
+ Network,
15
+ )
16
+ from t402.schemes.interfaces import AssetAmount, SupportedKindDict
17
+ from t402.tron import (
18
+ SCHEME_EXACT,
19
+ DEFAULT_DECIMALS,
20
+ get_network_config,
21
+ get_default_asset,
22
+ get_asset_info,
23
+ normalize_network as tron_normalize_network,
24
+ )
25
+
26
+
27
+ class ExactTronServerScheme:
28
+ """Server scheme for TRON exact payments.
29
+
30
+ Handles parsing user-friendly prices and enhancing payment requirements
31
+ with TRON-specific metadata for clients.
32
+
33
+ Example:
34
+ ```python
35
+ scheme = ExactTronServerScheme()
36
+
37
+ # Parse price
38
+ asset_amount = await scheme.parse_price("$0.10", "tron:mainnet")
39
+ # Returns: {"amount": "100000", "asset": "TR7N...", "extra": {...}}
40
+
41
+ # Enhance requirements
42
+ enhanced = await scheme.enhance_requirements(
43
+ requirements,
44
+ supported_kind,
45
+ facilitator_extensions,
46
+ )
47
+ ```
48
+ """
49
+
50
+ scheme = SCHEME_EXACT
51
+ caip_family = "tron:*"
52
+
53
+ async def parse_price(
54
+ self,
55
+ price: Union[str, int, float, Dict[str, Any]],
56
+ network: Network,
57
+ ) -> AssetAmount:
58
+ """Parse a user-friendly price to atomic amount and asset.
59
+
60
+ Supports:
61
+ - String with $ prefix: "$0.10" -> 100000 (6 decimals)
62
+ - String without prefix: "0.10" -> 100000
63
+ - Integer/float: 0.10 -> 100000
64
+ - Dict (TokenAmount): {"amount": "100000", "asset": "TR7N..."}
65
+
66
+ Args:
67
+ price: User-friendly price
68
+ network: Network identifier (CAIP-2 format, e.g., "tron:mainnet")
69
+
70
+ Returns:
71
+ AssetAmount dict with amount, asset, and extra metadata
72
+ """
73
+ # Normalize network
74
+ network_str = self._normalize_network(network)
75
+
76
+ # Handle dict (already in TokenAmount format)
77
+ if isinstance(price, dict):
78
+ return {
79
+ "amount": str(price.get("amount", "0")),
80
+ "asset": price.get("asset", ""),
81
+ "extra": price.get("extra", {}),
82
+ }
83
+
84
+ # Get default asset (USDT) for the network
85
+ default_asset = get_default_asset(network_str)
86
+ if not default_asset:
87
+ raise ValueError(f"Unsupported TRON network: {network}")
88
+
89
+ asset_address = default_asset["contract_address"]
90
+ decimals = default_asset.get("decimals", DEFAULT_DECIMALS)
91
+
92
+ # Parse price string/number
93
+ if isinstance(price, str):
94
+ if price.startswith("$"):
95
+ price = price[1:]
96
+ amount_decimal = Decimal(price)
97
+ else:
98
+ amount_decimal = Decimal(str(price))
99
+
100
+ # Convert to atomic units
101
+ atomic_amount = int(amount_decimal * Decimal(10 ** decimals))
102
+
103
+ # Build extra metadata
104
+ extra = {
105
+ "symbol": default_asset.get("symbol", "USDT"),
106
+ "name": default_asset.get("name", "Tether USD"),
107
+ "decimals": decimals,
108
+ }
109
+
110
+ return {
111
+ "amount": str(atomic_amount),
112
+ "asset": asset_address,
113
+ "extra": extra,
114
+ }
115
+
116
+ async def enhance_requirements(
117
+ self,
118
+ requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
119
+ supported_kind: SupportedKindDict,
120
+ facilitator_extensions: List[str],
121
+ ) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
122
+ """Enhance payment requirements with TRON-specific metadata.
123
+
124
+ Adds TRC-20 token metadata to the extra field so clients can
125
+ properly build the transfer transaction.
126
+
127
+ Args:
128
+ requirements: Base payment requirements
129
+ supported_kind: Matched SupportedKind from facilitator
130
+ facilitator_extensions: Extensions supported by facilitator
131
+
132
+ Returns:
133
+ Enhanced requirements with TRON metadata in extra
134
+ """
135
+ # Convert to dict for modification
136
+ if hasattr(requirements, "model_dump"):
137
+ req = requirements.model_dump(by_alias=True)
138
+ else:
139
+ req = dict(requirements)
140
+
141
+ network = req.get("network", "")
142
+ asset = req.get("asset", "")
143
+
144
+ # Normalize network
145
+ network_str = self._normalize_network(network)
146
+
147
+ # Ensure extra exists
148
+ if "extra" not in req or req["extra"] is None:
149
+ req["extra"] = {}
150
+
151
+ # Add TRC-20 metadata if not present
152
+ asset_info = get_asset_info(network_str, asset)
153
+ if asset_info:
154
+ if "symbol" not in req["extra"]:
155
+ req["extra"]["symbol"] = asset_info.get("symbol", "UNKNOWN")
156
+ if "name" not in req["extra"]:
157
+ req["extra"]["name"] = asset_info.get("name", "Unknown TRC20")
158
+ if "decimals" not in req["extra"]:
159
+ req["extra"]["decimals"] = asset_info.get("decimals", DEFAULT_DECIMALS)
160
+
161
+ # Add network config info
162
+ network_config = get_network_config(network_str)
163
+ if network_config:
164
+ if "endpoint" not in req["extra"]:
165
+ req["extra"]["endpoint"] = network_config.get("endpoint", "")
166
+
167
+ # Add facilitator extra data if available
168
+ if supported_kind.get("extra"):
169
+ for key, value in supported_kind["extra"].items():
170
+ if key not in req["extra"]:
171
+ req["extra"][key] = value
172
+
173
+ return req
174
+
175
+ def _normalize_network(self, network: str) -> str:
176
+ """Normalize network identifier to CAIP-2 format.
177
+
178
+ Args:
179
+ network: Network identifier
180
+
181
+ Returns:
182
+ Normalized CAIP-2 network string
183
+
184
+ Raises:
185
+ ValueError: If network is not supported
186
+ """
187
+ # Use the tron module's normalize function
188
+ try:
189
+ return tron_normalize_network(network)
190
+ except ValueError:
191
+ # Re-raise with consistent error message
192
+ raise ValueError(f"Unknown TRON network: {network}")
@@ -0,0 +1,80 @@
1
+ """Up-To Payment Scheme.
2
+
3
+ The upto scheme authorizes transfer of up to a maximum amount,
4
+ enabling usage-based billing where the final settlement amount
5
+ is determined by actual usage.
6
+
7
+ This is useful for:
8
+ - AI inference billing (pay per token)
9
+ - Metered API access (pay per request)
10
+ - Streaming services (pay per second/minute)
11
+ - Data transfer (pay per byte)
12
+
13
+ Example:
14
+ ```python
15
+ from t402.schemes.upto import (
16
+ UptoPaymentRequirements,
17
+ UptoSettlement,
18
+ create_payment_requirements,
19
+ create_settlement,
20
+ )
21
+
22
+ # Server specifies requirements
23
+ requirements = create_payment_requirements(
24
+ network="eip155:8453",
25
+ max_amount="1000000", # $1.00 max
26
+ asset="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
27
+ pay_to="0x...",
28
+ )
29
+
30
+ # After service delivery, settle for actual usage
31
+ settlement = create_settlement(
32
+ settle_amount="150000", # $0.15 actual
33
+ units_consumed=1500,
34
+ unit_price="100",
35
+ unit_type="token",
36
+ )
37
+ ```
38
+ """
39
+
40
+ from t402.schemes.upto.types import (
41
+ # Constants
42
+ SCHEME_UPTO,
43
+ DEFAULT_MIN_AMOUNT,
44
+ DEFAULT_MAX_TIMEOUT_SECONDS,
45
+ SUPPORTED_UNITS,
46
+ # Models
47
+ UptoExtra,
48
+ UptoPaymentRequirements,
49
+ UptoUsageDetails,
50
+ UptoSettlement,
51
+ UptoSettlementResponse,
52
+ UptoValidationResult,
53
+ # Type guards
54
+ is_upto_payment_requirements,
55
+ is_valid_unit,
56
+ # Factory functions
57
+ create_payment_requirements,
58
+ create_settlement,
59
+ )
60
+
61
+ __all__ = [
62
+ # Constants
63
+ "SCHEME_UPTO",
64
+ "DEFAULT_MIN_AMOUNT",
65
+ "DEFAULT_MAX_TIMEOUT_SECONDS",
66
+ "SUPPORTED_UNITS",
67
+ # Models
68
+ "UptoExtra",
69
+ "UptoPaymentRequirements",
70
+ "UptoUsageDetails",
71
+ "UptoSettlement",
72
+ "UptoSettlementResponse",
73
+ "UptoValidationResult",
74
+ # Type guards
75
+ "is_upto_payment_requirements",
76
+ "is_valid_unit",
77
+ # Factory functions
78
+ "create_payment_requirements",
79
+ "create_settlement",
80
+ ]