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,211 @@
1
+ """Cosmos/Noble blockchain constants for the T402 protocol.
2
+
3
+ This module contains network configurations, token denominations,
4
+ and other constants used by the Cosmos exact-direct payment scheme.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Dict, Optional
10
+
11
+
12
+ # Scheme identifier
13
+ SCHEME_EXACT_DIRECT = "exact-direct"
14
+
15
+ # CAIP-2 network identifiers
16
+ # Noble is a Cosmos app-chain specifically built for native USDC issuance
17
+ COSMOS_NOBLE_MAINNET = "cosmos:noble-1"
18
+ COSMOS_NOBLE_TESTNET = "cosmos:grand-1"
19
+
20
+ # RPC endpoints
21
+ NOBLE_MAINNET_RPC = "https://noble-rpc.polkachu.com"
22
+ NOBLE_TESTNET_RPC = "https://rpc.testnet.noble.strange.love"
23
+
24
+ # REST API endpoints
25
+ NOBLE_MAINNET_REST = "https://noble-api.polkachu.com"
26
+ NOBLE_TESTNET_REST = "https://api.testnet.noble.strange.love"
27
+
28
+ # Address prefix (bech32)
29
+ NOBLE_BECH32_PREFIX = "noble"
30
+
31
+ # USDC denom on Noble (micro USDC: 1 USDC = 1,000,000 uusdc)
32
+ USDC_DENOM = "uusdc"
33
+
34
+ # Gas settings
35
+ DEFAULT_GAS_LIMIT = 200000
36
+ DEFAULT_GAS_PRICE = "0.025uusdc"
37
+ DEFAULT_FEE_AMOUNT = "5000" # 0.005 USDC
38
+ DEFAULT_FEE_DENOM = USDC_DENOM
39
+
40
+ # Message types
41
+ MSG_TYPE_SEND = "/cosmos.bank.v1beta1.MsgSend"
42
+ MSG_TYPE_MULTI_SEND = "/cosmos.bank.v1beta1.MsgMultiSend"
43
+
44
+ # CAIP family pattern
45
+ CAIP_FAMILY = "cosmos:*"
46
+
47
+
48
+ class TokenInfo:
49
+ """Contains information about a Cosmos token.
50
+
51
+ Attributes:
52
+ denom: The token denomination (e.g., "uusdc").
53
+ symbol: The token symbol (e.g., "USDC").
54
+ decimals: The number of decimal places for the token.
55
+ """
56
+
57
+ def __init__(self, denom: str, symbol: str, decimals: int) -> None:
58
+ self.denom = denom
59
+ self.symbol = symbol
60
+ self.decimals = decimals
61
+
62
+ def __repr__(self) -> str:
63
+ return f"TokenInfo(denom={self.denom!r}, symbol={self.symbol!r}, decimals={self.decimals})"
64
+
65
+
66
+ class NetworkConfig:
67
+ """Network-specific configuration for Cosmos.
68
+
69
+ Attributes:
70
+ chain_id: The Cosmos chain ID (e.g., "noble-1").
71
+ rpc_url: The RPC endpoint URL.
72
+ rest_url: The REST API endpoint URL.
73
+ bech32_prefix: The bech32 address prefix (e.g., "noble").
74
+ default_token: The default token for this network.
75
+ """
76
+
77
+ def __init__(
78
+ self,
79
+ chain_id: str,
80
+ rpc_url: str,
81
+ rest_url: str,
82
+ bech32_prefix: str,
83
+ default_token: TokenInfo,
84
+ ) -> None:
85
+ self.chain_id = chain_id
86
+ self.rpc_url = rpc_url
87
+ self.rest_url = rest_url
88
+ self.bech32_prefix = bech32_prefix
89
+ self.default_token = default_token
90
+
91
+
92
+ # Token definitions
93
+ USDC_TOKEN = TokenInfo(
94
+ denom=USDC_DENOM,
95
+ symbol="USDC",
96
+ decimals=6,
97
+ )
98
+
99
+ # Network configurations
100
+ NETWORK_CONFIGS: Dict[str, NetworkConfig] = {
101
+ COSMOS_NOBLE_MAINNET: NetworkConfig(
102
+ chain_id="noble-1",
103
+ rpc_url=NOBLE_MAINNET_RPC,
104
+ rest_url=NOBLE_MAINNET_REST,
105
+ bech32_prefix=NOBLE_BECH32_PREFIX,
106
+ default_token=USDC_TOKEN,
107
+ ),
108
+ COSMOS_NOBLE_TESTNET: NetworkConfig(
109
+ chain_id="grand-1",
110
+ rpc_url=NOBLE_TESTNET_RPC,
111
+ rest_url=NOBLE_TESTNET_REST,
112
+ bech32_prefix=NOBLE_BECH32_PREFIX,
113
+ default_token=USDC_TOKEN,
114
+ ),
115
+ }
116
+
117
+ # Token registry: network -> symbol -> TokenInfo
118
+ TOKEN_REGISTRY: Dict[str, Dict[str, TokenInfo]] = {
119
+ COSMOS_NOBLE_MAINNET: {
120
+ "USDC": USDC_TOKEN,
121
+ },
122
+ COSMOS_NOBLE_TESTNET: {
123
+ "USDC": USDC_TOKEN,
124
+ },
125
+ }
126
+
127
+
128
+ def get_network_config(network: str) -> Optional[NetworkConfig]:
129
+ """Get the configuration for a Cosmos network.
130
+
131
+ Args:
132
+ network: The CAIP-2 network identifier (e.g., "cosmos:noble-1").
133
+
134
+ Returns:
135
+ NetworkConfig if the network is supported, None otherwise.
136
+ """
137
+ return NETWORK_CONFIGS.get(network)
138
+
139
+
140
+ def is_valid_network(network: str) -> bool:
141
+ """Check if a network identifier is a supported Cosmos network.
142
+
143
+ Args:
144
+ network: The CAIP-2 network identifier.
145
+
146
+ Returns:
147
+ True if the network is supported.
148
+ """
149
+ return network in NETWORK_CONFIGS
150
+
151
+
152
+ def get_token_info(network: str, symbol: str) -> Optional[TokenInfo]:
153
+ """Get token info for a network and symbol.
154
+
155
+ Args:
156
+ network: The CAIP-2 network identifier.
157
+ symbol: The token symbol (e.g., "USDC").
158
+
159
+ Returns:
160
+ TokenInfo if found, None otherwise.
161
+ """
162
+ tokens = TOKEN_REGISTRY.get(network)
163
+ if tokens is None:
164
+ return None
165
+ return tokens.get(symbol)
166
+
167
+
168
+ def get_token_by_denom(network: str, denom: str) -> Optional[TokenInfo]:
169
+ """Get token info by denomination.
170
+
171
+ Args:
172
+ network: The CAIP-2 network identifier.
173
+ denom: The token denomination (e.g., "uusdc").
174
+
175
+ Returns:
176
+ TokenInfo if found, None otherwise.
177
+ """
178
+ tokens = TOKEN_REGISTRY.get(network)
179
+ if tokens is None:
180
+ return None
181
+ for token in tokens.values():
182
+ if token.denom == denom:
183
+ return token
184
+ return None
185
+
186
+
187
+ def is_valid_address(address: str, expected_prefix: str) -> bool:
188
+ """Check if an address has the correct bech32 prefix.
189
+
190
+ Validates that the address starts with the expected prefix followed by "1"
191
+ (the bech32 separator) and has sufficient length.
192
+
193
+ Args:
194
+ address: The bech32 address to validate.
195
+ expected_prefix: The expected prefix (e.g., "noble").
196
+
197
+ Returns:
198
+ True if the address has the correct prefix format.
199
+ """
200
+ if not address or len(address) < len(expected_prefix) + 1:
201
+ return False
202
+ return address[:len(expected_prefix)] == expected_prefix
203
+
204
+
205
+ def get_supported_networks() -> list:
206
+ """Get a list of supported Cosmos network identifiers.
207
+
208
+ Returns:
209
+ List of CAIP-2 network identifier strings.
210
+ """
211
+ return [COSMOS_NOBLE_MAINNET, COSMOS_NOBLE_TESTNET]
@@ -0,0 +1,21 @@
1
+ """Cosmos/Noble Exact-Direct Payment Scheme.
2
+
3
+ This package provides the exact-direct payment scheme implementation for Cosmos/Noble.
4
+ In this scheme, the client executes a bank MsgSend on-chain directly,
5
+ and the transaction hash is used as proof of payment.
6
+
7
+ Components:
8
+ - ExactDirectCosmosClientScheme: Client-side (executes transfer, returns tx hash)
9
+ - ExactDirectCosmosServerScheme: Server-side (parses prices, enhances requirements)
10
+ - ExactDirectCosmosFacilitatorScheme: Facilitator-side (verifies tx, marks settled)
11
+ """
12
+
13
+ from t402.schemes.cosmos.exact_direct.client import ExactDirectCosmosClientScheme
14
+ from t402.schemes.cosmos.exact_direct.server import ExactDirectCosmosServerScheme
15
+ from t402.schemes.cosmos.exact_direct.facilitator import ExactDirectCosmosFacilitatorScheme
16
+
17
+ __all__ = [
18
+ "ExactDirectCosmosClientScheme",
19
+ "ExactDirectCosmosServerScheme",
20
+ "ExactDirectCosmosFacilitatorScheme",
21
+ ]
@@ -0,0 +1,198 @@
1
+ """Cosmos/Noble Exact-Direct Scheme - Client Implementation.
2
+
3
+ This module provides the client-side implementation of the exact-direct payment
4
+ scheme for Cosmos/Noble networks using bank MsgSend.
5
+
6
+ Unlike other schemes where the client creates a signed message for the facilitator
7
+ to execute, the exact-direct scheme has the client execute the transfer directly.
8
+ The transaction hash is then used as proof of payment.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from typing import Any, Dict, Optional, Union
15
+
16
+ from t402.types import PaymentRequirementsV2
17
+ from t402.schemes.cosmos.constants import (
18
+ SCHEME_EXACT_DIRECT,
19
+ CAIP_FAMILY,
20
+ USDC_DENOM,
21
+ is_valid_network,
22
+ is_valid_address,
23
+ get_network_config,
24
+ get_token_info,
25
+ )
26
+ from t402.schemes.cosmos.types import (
27
+ ClientCosmosSigner,
28
+ ExactDirectPayload,
29
+ )
30
+
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class ExactDirectCosmosClientConfig:
36
+ """Configuration for the ExactDirectCosmosClientScheme.
37
+
38
+ Attributes:
39
+ memo: Optional memo to include in the transaction.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ memo: Optional[str] = None,
45
+ ) -> None:
46
+ self.memo = memo
47
+
48
+
49
+ class ExactDirectCosmosClientScheme:
50
+ """Client scheme for Cosmos/Noble exact-direct payments using bank MsgSend.
51
+
52
+ This scheme executes the token transfer on-chain directly and returns the
53
+ transaction hash as proof of payment. The facilitator then verifies the
54
+ transaction was successful.
55
+
56
+ Example:
57
+ ```python
58
+ class MyCosmosSigner:
59
+ def address(self) -> str:
60
+ return "noble1abc..."
61
+
62
+ async def send_tokens(
63
+ self, network, to, amount, denom
64
+ ) -> str:
65
+ # Execute the transaction
66
+ return "AABB1122..."
67
+
68
+ signer = MyCosmosSigner()
69
+ scheme = ExactDirectCosmosClientScheme(signer)
70
+
71
+ payload = await scheme.create_payment_payload(
72
+ t402_version=2,
73
+ requirements=requirements,
74
+ )
75
+ ```
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ signer: ClientCosmosSigner,
81
+ config: Optional[ExactDirectCosmosClientConfig] = None,
82
+ ) -> None:
83
+ """Initialize with a Cosmos signer.
84
+
85
+ Args:
86
+ signer: Any object implementing the ClientCosmosSigner protocol.
87
+ config: Optional configuration for the client scheme.
88
+ """
89
+ self._signer = signer
90
+ self._config = config or ExactDirectCosmosClientConfig()
91
+
92
+ @property
93
+ def scheme(self) -> str:
94
+ """The scheme identifier."""
95
+ return SCHEME_EXACT_DIRECT
96
+
97
+ @property
98
+ def caip_family(self) -> str:
99
+ """The CAIP-2 family pattern for Cosmos networks."""
100
+ return CAIP_FAMILY
101
+
102
+ @property
103
+ def signer_address(self) -> str:
104
+ """Get the signer's Cosmos address."""
105
+ return self._signer.address()
106
+
107
+ async def create_payment_payload(
108
+ self,
109
+ t402_version: int,
110
+ requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
111
+ ) -> Dict[str, Any]:
112
+ """Create a payment payload by executing a bank send on-chain.
113
+
114
+ Executes a Cosmos bank MsgSend to the specified recipient and returns
115
+ the transaction hash as proof of payment.
116
+
117
+ Args:
118
+ t402_version: The T402 protocol version.
119
+ requirements: Payment requirements with amount, asset, payTo, network.
120
+
121
+ Returns:
122
+ Dict with t402Version and payload containing txHash, from, to, amount.
123
+
124
+ Raises:
125
+ ValueError: If requirements are invalid (bad network, missing fields,
126
+ invalid addresses).
127
+ RuntimeError: If the transaction execution fails.
128
+ """
129
+ # Extract requirements as dict
130
+ if hasattr(requirements, "model_dump"):
131
+ req = requirements.model_dump(by_alias=True)
132
+ else:
133
+ req = dict(requirements)
134
+
135
+ network = req.get("network", "")
136
+ asset = req.get("asset", "")
137
+ pay_to = req.get("payTo") or req.get("pay_to", "")
138
+ amount = req.get("amount", "")
139
+
140
+ # Validate network
141
+ if not is_valid_network(network):
142
+ raise ValueError(f"Unsupported network: {network}")
143
+
144
+ # Get network config for address validation
145
+ config = get_network_config(network)
146
+ if config is None:
147
+ raise ValueError(f"Unsupported network: {network}")
148
+
149
+ # Validate required fields
150
+ if not pay_to:
151
+ raise ValueError("payTo address is required")
152
+ if not amount:
153
+ raise ValueError("Amount is required")
154
+
155
+ # Determine denom
156
+ denom = USDC_DENOM
157
+ if asset:
158
+ # Check if asset is a symbol or denom
159
+ token = get_token_info(network, asset)
160
+ if token:
161
+ denom = token.denom
162
+ else:
163
+ # Assume it's already a denom
164
+ denom = asset
165
+
166
+ # Validate recipient address
167
+ if not is_valid_address(pay_to, config.bech32_prefix):
168
+ raise ValueError(f"Invalid recipient address: {pay_to}")
169
+
170
+ # Validate sender address
171
+ sender = self._signer.address()
172
+ if not is_valid_address(sender, config.bech32_prefix):
173
+ raise ValueError(f"Invalid sender address: {sender}")
174
+
175
+ # Execute the transfer via the signer
176
+ try:
177
+ tx_hash = await self._signer.send_tokens(
178
+ network=network,
179
+ to=pay_to,
180
+ amount=amount,
181
+ denom=denom,
182
+ )
183
+ except Exception as e:
184
+ raise RuntimeError(f"Failed to send tokens: {e}") from e
185
+
186
+ # Build the payload
187
+ payload = ExactDirectPayload(
188
+ tx_hash=tx_hash,
189
+ from_address=sender,
190
+ to_address=pay_to,
191
+ amount=amount,
192
+ denom=denom,
193
+ )
194
+
195
+ return {
196
+ "t402Version": t402_version,
197
+ "payload": payload.to_map(),
198
+ }