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,238 @@
1
+ """
2
+ Transaction builder for T402 Multi-sig (Safe) support.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from web3 import Web3
8
+
9
+ from .constants import SAFE_MULTISEND, ERC20_TRANSFER_SELECTOR, MULTISEND_SELECTOR
10
+ from .types import SafeTransaction, OperationType
11
+
12
+
13
+ class TransactionBuilder:
14
+ """Builder for Safe transactions."""
15
+
16
+ def __init__(self):
17
+ """Initialize TransactionBuilder."""
18
+ self._to: str = "0x0000000000000000000000000000000000000000"
19
+ self._value: int = 0
20
+ self._data: bytes = b""
21
+ self._operation: OperationType = OperationType.CALL
22
+ self._safe_tx_gas: int = 0
23
+ self._base_gas: int = 0
24
+ self._gas_price: int = 0
25
+ self._gas_token: str = "0x0000000000000000000000000000000000000000"
26
+ self._refund_receiver: str = "0x0000000000000000000000000000000000000000"
27
+ self._nonce: Optional[int] = None
28
+
29
+ def to(self, address: str) -> "TransactionBuilder":
30
+ """Set the target address."""
31
+ self._to = Web3.to_checksum_address(address)
32
+ return self
33
+
34
+ def value(self, amount: int) -> "TransactionBuilder":
35
+ """Set the ETH value to send."""
36
+ self._value = amount
37
+ return self
38
+
39
+ def data(self, data: bytes) -> "TransactionBuilder":
40
+ """Set the calldata."""
41
+ self._data = data
42
+ return self
43
+
44
+ def operation(self, op: OperationType) -> "TransactionBuilder":
45
+ """Set the operation type."""
46
+ self._operation = op
47
+ return self
48
+
49
+ def delegate_call(self) -> "TransactionBuilder":
50
+ """Set operation to delegate call."""
51
+ self._operation = OperationType.DELEGATE_CALL
52
+ return self
53
+
54
+ def safe_tx_gas(self, gas: int) -> "TransactionBuilder":
55
+ """Set the Safe transaction gas."""
56
+ self._safe_tx_gas = gas
57
+ return self
58
+
59
+ def base_gas(self, gas: int) -> "TransactionBuilder":
60
+ """Set the base gas."""
61
+ self._base_gas = gas
62
+ return self
63
+
64
+ def gas_price(self, price: int) -> "TransactionBuilder":
65
+ """Set the gas price for refund."""
66
+ self._gas_price = price
67
+ return self
68
+
69
+ def gas_token(self, token: str) -> "TransactionBuilder":
70
+ """Set the token for gas refund (zero address for ETH)."""
71
+ self._gas_token = Web3.to_checksum_address(token)
72
+ return self
73
+
74
+ def refund_receiver(self, receiver: str) -> "TransactionBuilder":
75
+ """Set the address to receive gas refund."""
76
+ self._refund_receiver = Web3.to_checksum_address(receiver)
77
+ return self
78
+
79
+ def nonce(self, nonce: int) -> "TransactionBuilder":
80
+ """Set the Safe nonce."""
81
+ self._nonce = nonce
82
+ return self
83
+
84
+ def build(self) -> SafeTransaction:
85
+ """Build the SafeTransaction."""
86
+ return SafeTransaction(
87
+ to=self._to,
88
+ value=self._value,
89
+ data=self._data,
90
+ operation=self._operation,
91
+ safe_tx_gas=self._safe_tx_gas,
92
+ base_gas=self._base_gas,
93
+ gas_price=self._gas_price,
94
+ gas_token=self._gas_token,
95
+ refund_receiver=self._refund_receiver,
96
+ nonce=self._nonce,
97
+ )
98
+
99
+
100
+ def erc20_transfer(token: str, to: str, amount: int) -> SafeTransaction:
101
+ """
102
+ Create a transaction for ERC20 token transfer.
103
+
104
+ Args:
105
+ token: Token contract address.
106
+ to: Recipient address.
107
+ amount: Amount in smallest units.
108
+
109
+ Returns:
110
+ SafeTransaction for the transfer.
111
+ """
112
+ # Build calldata: transfer(address,uint256)
113
+ to_addr = Web3.to_checksum_address(to)
114
+ data = (
115
+ ERC20_TRANSFER_SELECTOR
116
+ + bytes(12) # Padding for address
117
+ + bytes.fromhex(to_addr[2:])
118
+ + amount.to_bytes(32, "big")
119
+ )
120
+
121
+ return (
122
+ TransactionBuilder()
123
+ .to(token)
124
+ .data(data)
125
+ .build()
126
+ )
127
+
128
+
129
+ def eth_transfer(to: str, amount: int) -> SafeTransaction:
130
+ """
131
+ Create a transaction for sending ETH.
132
+
133
+ Args:
134
+ to: Recipient address.
135
+ amount: Amount in wei.
136
+
137
+ Returns:
138
+ SafeTransaction for the transfer.
139
+ """
140
+ return (
141
+ TransactionBuilder()
142
+ .to(to)
143
+ .value(amount)
144
+ .build()
145
+ )
146
+
147
+
148
+ def contract_call(target: str, data: bytes) -> SafeTransaction:
149
+ """
150
+ Create a transaction for calling a contract.
151
+
152
+ Args:
153
+ target: Target contract address.
154
+ data: Calldata.
155
+
156
+ Returns:
157
+ SafeTransaction for the call.
158
+ """
159
+ return (
160
+ TransactionBuilder()
161
+ .to(target)
162
+ .data(data)
163
+ .build()
164
+ )
165
+
166
+
167
+ class BatchTransactionBuilder:
168
+ """Builder for batch transactions via MultiSend."""
169
+
170
+ def __init__(self):
171
+ """Initialize BatchTransactionBuilder."""
172
+ self._transactions: List[SafeTransaction] = []
173
+
174
+ def add(self, tx: SafeTransaction) -> "BatchTransactionBuilder":
175
+ """Add a transaction to the batch."""
176
+ self._transactions.append(tx)
177
+ return self
178
+
179
+ def add_transfer(
180
+ self, token: str, to: str, amount: int
181
+ ) -> "BatchTransactionBuilder":
182
+ """Add an ERC20 transfer to the batch."""
183
+ return self.add(erc20_transfer(token, to, amount))
184
+
185
+ def add_eth_transfer(self, to: str, amount: int) -> "BatchTransactionBuilder":
186
+ """Add an ETH transfer to the batch."""
187
+ return self.add(eth_transfer(to, amount))
188
+
189
+ def build(self) -> List[SafeTransaction]:
190
+ """Return all transactions in the batch."""
191
+ return self._transactions.copy()
192
+
193
+ def build_multisend(self) -> SafeTransaction:
194
+ """
195
+ Create a single transaction that executes all batch transactions via MultiSend.
196
+
197
+ Returns:
198
+ SafeTransaction for MultiSend execution.
199
+ """
200
+ # Encode each transaction for MultiSend
201
+ # Format: operation (1 byte) + to (20 bytes) + value (32 bytes) +
202
+ # dataLength (32 bytes) + data (variable)
203
+ packed_txs = b""
204
+ for tx in self._transactions:
205
+ # Operation (1 byte)
206
+ packed_txs += bytes([tx.operation])
207
+
208
+ # To (20 bytes)
209
+ packed_txs += bytes.fromhex(tx.to[2:])
210
+
211
+ # Value (32 bytes)
212
+ packed_txs += tx.value.to_bytes(32, "big")
213
+
214
+ # Data length (32 bytes)
215
+ packed_txs += len(tx.data).to_bytes(32, "big")
216
+
217
+ # Data
218
+ packed_txs += tx.data
219
+
220
+ # Build MultiSend calldata
221
+ # multiSend(bytes transactions)
222
+ # Offset (32 bytes) + Length (32 bytes) + Data (padded to 32 bytes)
223
+ offset = (32).to_bytes(32, "big") # Offset to data
224
+ length = len(packed_txs).to_bytes(32, "big")
225
+
226
+ # Pad to 32-byte boundary
227
+ padded_len = ((len(packed_txs) + 31) // 32) * 32
228
+ padded_data = packed_txs + b"\x00" * (padded_len - len(packed_txs))
229
+
230
+ calldata = MULTISEND_SELECTOR + offset + length + padded_data
231
+
232
+ return (
233
+ TransactionBuilder()
234
+ .to(SAFE_MULTISEND)
235
+ .data(calldata)
236
+ .delegate_call()
237
+ .build()
238
+ )
t402/multisig/types.py ADDED
@@ -0,0 +1,108 @@
1
+ """
2
+ Type definitions for T402 Multi-sig (Safe) support.
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import IntEnum
7
+ from typing import Dict, List, Optional
8
+
9
+
10
+ class SignatureType(IntEnum):
11
+ """Type of signature for Safe transactions."""
12
+
13
+ EOA = 0 # Standard EOA signature
14
+ CONTRACT = 1 # EIP-1271 contract signature
15
+ APPROVED_HASH = 4 # Pre-approved hash
16
+
17
+
18
+ class OperationType(IntEnum):
19
+ """Operation type for Safe transactions."""
20
+
21
+ CALL = 0
22
+ DELEGATE_CALL = 1
23
+
24
+
25
+ @dataclass
26
+ class SafeConfig:
27
+ """Configuration for a Safe multi-sig account."""
28
+
29
+ address: str
30
+ rpc_url: str
31
+ chain_id: Optional[int] = None
32
+
33
+
34
+ @dataclass
35
+ class SafeOwner:
36
+ """Represents an owner of a Safe account."""
37
+
38
+ address: str
39
+ index: int
40
+
41
+
42
+ @dataclass
43
+ class SafeTransaction:
44
+ """Represents a pending Safe transaction."""
45
+
46
+ to: str
47
+ value: int = 0
48
+ data: bytes = field(default_factory=bytes)
49
+ operation: OperationType = OperationType.CALL
50
+ safe_tx_gas: int = 0
51
+ base_gas: int = 0
52
+ gas_price: int = 0
53
+ gas_token: str = "0x0000000000000000000000000000000000000000"
54
+ refund_receiver: str = "0x0000000000000000000000000000000000000000"
55
+ nonce: Optional[int] = None
56
+
57
+
58
+ @dataclass
59
+ class SafeSignature:
60
+ """Holds a signature from a Safe owner."""
61
+
62
+ signer: str
63
+ signature: bytes
64
+ signature_type: SignatureType = SignatureType.EOA
65
+
66
+
67
+ @dataclass
68
+ class TransactionRequest:
69
+ """Represents a multi-sig transaction awaiting signatures."""
70
+
71
+ id: str
72
+ safe_address: str
73
+ transaction: SafeTransaction
74
+ transaction_hash: str
75
+ signatures: Dict[str, SafeSignature] = field(default_factory=dict)
76
+ threshold: int = 0
77
+ created_at: int = 0
78
+ expires_at: int = 0
79
+
80
+ def is_ready(self) -> bool:
81
+ """Check if enough signatures have been collected."""
82
+ return len(self.signatures) >= self.threshold
83
+
84
+ def collected_count(self) -> int:
85
+ """Return the number of signatures collected."""
86
+ return len(self.signatures)
87
+
88
+
89
+ @dataclass
90
+ class SafeInfo:
91
+ """Information about a Safe account."""
92
+
93
+ address: str
94
+ owners: List[str]
95
+ threshold: int
96
+ nonce: int
97
+ version: Optional[str] = None
98
+ chain_id: Optional[int] = None
99
+
100
+
101
+ @dataclass
102
+ class ExecutionResult:
103
+ """Result of executing a Safe transaction."""
104
+
105
+ tx_hash: str
106
+ success: bool
107
+ gas_used: int = 0
108
+ block_number: int = 0
t402/multisig/utils.py ADDED
@@ -0,0 +1,77 @@
1
+ """
2
+ Utility functions for T402 Multi-sig support.
3
+ """
4
+
5
+ import secrets
6
+ import time
7
+ from typing import List
8
+
9
+
10
+ def generate_request_id() -> str:
11
+ """Generate a unique request ID."""
12
+ timestamp = int(time.time() * 1000)
13
+ random_bytes = secrets.token_hex(4)
14
+ return f"msig_{timestamp:x}_{random_bytes}"
15
+
16
+
17
+ def current_timestamp() -> int:
18
+ """Get current Unix timestamp in seconds."""
19
+ return int(time.time())
20
+
21
+
22
+ def sort_addresses(addresses: List[str]) -> List[str]:
23
+ """Sort addresses in ascending order (case-insensitive)."""
24
+ return sorted(addresses, key=lambda a: a.lower())
25
+
26
+
27
+ def is_valid_threshold(threshold: int, owner_count: int) -> bool:
28
+ """Check if a threshold is valid for the given owner count."""
29
+ from .constants import MIN_THRESHOLD
30
+
31
+ return MIN_THRESHOLD <= threshold <= owner_count
32
+
33
+
34
+ def are_addresses_unique(addresses: List[str]) -> bool:
35
+ """Check if all addresses are unique (case-insensitive)."""
36
+ lower_addresses = [a.lower() for a in addresses]
37
+ return len(set(lower_addresses)) == len(addresses)
38
+
39
+
40
+ def get_owner_index(owner: str, owners: List[str]) -> int:
41
+ """
42
+ Get the index of an owner in the list.
43
+
44
+ Returns -1 if not found.
45
+ """
46
+ owner_lower = owner.lower()
47
+ for i, o in enumerate(owners):
48
+ if o.lower() == owner_lower:
49
+ return i
50
+ return -1
51
+
52
+
53
+ def combine_signatures(signatures: dict) -> bytes:
54
+ """
55
+ Combine multiple signatures sorted by signer address.
56
+
57
+ Args:
58
+ signatures: Dict mapping signer address to SafeSignature.
59
+
60
+ Returns:
61
+ Combined signature bytes.
62
+ """
63
+ sorted_signers = sort_addresses(list(signatures.keys()))
64
+
65
+ packed = b""
66
+ for signer in sorted_signers:
67
+ sig = signatures[signer.lower()]
68
+ packed += sig.signature
69
+
70
+ return packed
71
+
72
+
73
+ def pad_to_32_bytes(data: bytes) -> bytes:
74
+ """Pad data to 32 bytes."""
75
+ if len(data) >= 32:
76
+ return data[-32:]
77
+ return b"\x00" * (32 - len(data)) + data
t402/schemes/__init__.py CHANGED
@@ -179,6 +179,17 @@ from t402.schemes.stacks import (
179
179
  SCHEME_EXACT_DIRECT as STACKS_SCHEME_EXACT_DIRECT,
180
180
  )
181
181
 
182
+ # Cosmos Schemes
183
+ from t402.schemes.cosmos import (
184
+ ExactDirectCosmosClientScheme,
185
+ ExactDirectCosmosServerScheme,
186
+ ExactDirectCosmosFacilitatorScheme,
187
+ ExactDirectCosmosFacilitatorConfig,
188
+ ClientCosmosSigner,
189
+ FacilitatorCosmosSigner,
190
+ SCHEME_EXACT_DIRECT as COSMOS_SCHEME_EXACT_DIRECT,
191
+ )
192
+
182
193
  __all__ = [
183
194
  # Type aliases
184
195
  "Price",
@@ -285,4 +296,12 @@ __all__ = [
285
296
  "ClientStacksSigner",
286
297
  "FacilitatorStacksSigner",
287
298
  "STACKS_SCHEME_EXACT_DIRECT",
299
+ # Cosmos Schemes
300
+ "ExactDirectCosmosClientScheme",
301
+ "ExactDirectCosmosServerScheme",
302
+ "ExactDirectCosmosFacilitatorScheme",
303
+ "ExactDirectCosmosFacilitatorConfig",
304
+ "ClientCosmosSigner",
305
+ "FacilitatorCosmosSigner",
306
+ "COSMOS_SCHEME_EXACT_DIRECT",
288
307
  ]
@@ -0,0 +1,114 @@
1
+ """Cosmos/Noble Blockchain Payment Schemes.
2
+
3
+ This package provides payment scheme implementations for the Cosmos/Noble blockchain.
4
+
5
+ Supported schemes:
6
+ - exact-direct: Client executes bank MsgSend, tx hash used as proof.
7
+
8
+ Usage:
9
+ ```python
10
+ from t402.schemes.cosmos import (
11
+ # Client
12
+ ExactDirectCosmosClientScheme,
13
+ ExactDirectCosmosClientConfig,
14
+ # Server
15
+ ExactDirectCosmosServerScheme,
16
+ ExactDirectCosmosServerConfig,
17
+ # Facilitator
18
+ ExactDirectCosmosFacilitatorScheme,
19
+ ExactDirectCosmosFacilitatorConfig,
20
+ # Signer protocols
21
+ ClientCosmosSigner,
22
+ FacilitatorCosmosSigner,
23
+ # Constants
24
+ SCHEME_EXACT_DIRECT,
25
+ COSMOS_NOBLE_MAINNET,
26
+ COSMOS_NOBLE_TESTNET,
27
+ )
28
+ ```
29
+ """
30
+
31
+ from t402.schemes.cosmos.exact_direct import (
32
+ ExactDirectCosmosClientScheme,
33
+ ExactDirectCosmosServerScheme,
34
+ ExactDirectCosmosFacilitatorScheme,
35
+ )
36
+ from t402.schemes.cosmos.exact_direct.client import ExactDirectCosmosClientConfig
37
+ from t402.schemes.cosmos.exact_direct.server import ExactDirectCosmosServerConfig
38
+ from t402.schemes.cosmos.exact_direct.facilitator import ExactDirectCosmosFacilitatorConfig
39
+ from t402.schemes.cosmos.types import (
40
+ ClientCosmosSigner,
41
+ FacilitatorCosmosSigner,
42
+ ExactDirectPayload,
43
+ TransactionResult,
44
+ MsgSend,
45
+ Coin,
46
+ )
47
+ from t402.schemes.cosmos.constants import (
48
+ SCHEME_EXACT_DIRECT,
49
+ COSMOS_NOBLE_MAINNET,
50
+ COSMOS_NOBLE_TESTNET,
51
+ NOBLE_MAINNET_RPC,
52
+ NOBLE_TESTNET_RPC,
53
+ NOBLE_MAINNET_REST,
54
+ NOBLE_TESTNET_REST,
55
+ NOBLE_BECH32_PREFIX,
56
+ USDC_DENOM,
57
+ DEFAULT_GAS_LIMIT,
58
+ MSG_TYPE_SEND,
59
+ CAIP_FAMILY,
60
+ USDC_TOKEN,
61
+ TokenInfo,
62
+ NetworkConfig,
63
+ get_network_config,
64
+ get_token_info,
65
+ get_token_by_denom,
66
+ is_valid_network,
67
+ is_valid_address,
68
+ get_supported_networks,
69
+ )
70
+
71
+ __all__ = [
72
+ # Scheme implementations
73
+ "ExactDirectCosmosClientScheme",
74
+ "ExactDirectCosmosServerScheme",
75
+ "ExactDirectCosmosFacilitatorScheme",
76
+ # Configurations
77
+ "ExactDirectCosmosClientConfig",
78
+ "ExactDirectCosmosServerConfig",
79
+ "ExactDirectCosmosFacilitatorConfig",
80
+ # Signer protocols
81
+ "ClientCosmosSigner",
82
+ "FacilitatorCosmosSigner",
83
+ # Payload types
84
+ "ExactDirectPayload",
85
+ "TransactionResult",
86
+ "MsgSend",
87
+ "Coin",
88
+ # Validation
89
+ "is_valid_network",
90
+ "is_valid_address",
91
+ # Constants
92
+ "SCHEME_EXACT_DIRECT",
93
+ "COSMOS_NOBLE_MAINNET",
94
+ "COSMOS_NOBLE_TESTNET",
95
+ "NOBLE_MAINNET_RPC",
96
+ "NOBLE_TESTNET_RPC",
97
+ "NOBLE_MAINNET_REST",
98
+ "NOBLE_TESTNET_REST",
99
+ "NOBLE_BECH32_PREFIX",
100
+ "USDC_DENOM",
101
+ "DEFAULT_GAS_LIMIT",
102
+ "MSG_TYPE_SEND",
103
+ "CAIP_FAMILY",
104
+ # Token definitions
105
+ "USDC_TOKEN",
106
+ # Data classes
107
+ "TokenInfo",
108
+ "NetworkConfig",
109
+ # Lookup functions
110
+ "get_network_config",
111
+ "get_token_info",
112
+ "get_token_by_denom",
113
+ "get_supported_networks",
114
+ ]