t402 1.7.1__py3-none-any.whl → 1.9.1__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.
- t402/__init__.py +2 -1
- t402/bridge/client.py +13 -5
- t402/bridge/constants.py +3 -1
- t402/bridge/router.py +1 -1
- t402/bridge/scan.py +3 -1
- t402/chains.py +268 -1
- t402/cli.py +31 -9
- t402/common.py +2 -0
- t402/cosmos_paywall_template.py +2 -0
- t402/encoding.py +9 -3
- t402/erc4337/accounts.py +56 -51
- t402/erc4337/bundlers.py +105 -99
- t402/erc4337/paymasters.py +100 -109
- t402/erc4337/types.py +39 -26
- t402/evm_paywall_template.py +1 -1
- t402/fastapi/middleware.py +1 -3
- t402/mcp/server.py +79 -46
- t402/near_paywall_template.py +2 -0
- t402/networks.py +34 -1
- t402/paywall.py +1 -3
- t402/schemes/__init__.py +164 -1
- t402/schemes/aptos/__init__.py +70 -0
- t402/schemes/aptos/constants.py +349 -0
- t402/schemes/aptos/exact_direct/__init__.py +44 -0
- t402/schemes/aptos/exact_direct/client.py +202 -0
- t402/schemes/aptos/exact_direct/facilitator.py +426 -0
- t402/schemes/aptos/exact_direct/server.py +272 -0
- t402/schemes/aptos/types.py +237 -0
- t402/schemes/evm/__init__.py +67 -1
- t402/schemes/evm/exact/__init__.py +11 -0
- t402/schemes/evm/exact/client.py +3 -1
- t402/schemes/evm/exact/facilitator.py +894 -0
- t402/schemes/evm/exact/server.py +1 -1
- t402/schemes/evm/exact_legacy/__init__.py +38 -0
- t402/schemes/evm/exact_legacy/client.py +291 -0
- t402/schemes/evm/exact_legacy/facilitator.py +777 -0
- t402/schemes/evm/exact_legacy/server.py +231 -0
- t402/schemes/evm/upto/__init__.py +70 -0
- t402/schemes/evm/upto/client.py +244 -0
- t402/schemes/evm/upto/facilitator.py +625 -0
- t402/schemes/evm/upto/server.py +243 -0
- t402/schemes/evm/upto/types.py +307 -0
- t402/schemes/interfaces.py +6 -2
- t402/schemes/near/__init__.py +112 -0
- t402/schemes/near/constants.py +189 -0
- t402/schemes/near/exact_direct/__init__.py +21 -0
- t402/schemes/near/exact_direct/client.py +204 -0
- t402/schemes/near/exact_direct/facilitator.py +455 -0
- t402/schemes/near/exact_direct/server.py +303 -0
- t402/schemes/near/types.py +419 -0
- t402/schemes/polkadot/__init__.py +72 -0
- t402/schemes/polkadot/constants.py +155 -0
- t402/schemes/polkadot/exact_direct/__init__.py +43 -0
- t402/schemes/polkadot/exact_direct/client.py +235 -0
- t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
- t402/schemes/polkadot/exact_direct/server.py +292 -0
- t402/schemes/polkadot/types.py +385 -0
- t402/schemes/registry.py +6 -2
- t402/schemes/stacks/__init__.py +68 -0
- t402/schemes/stacks/constants.py +122 -0
- t402/schemes/stacks/exact_direct/__init__.py +43 -0
- t402/schemes/stacks/exact_direct/client.py +222 -0
- t402/schemes/stacks/exact_direct/facilitator.py +424 -0
- t402/schemes/stacks/exact_direct/server.py +292 -0
- t402/schemes/stacks/types.py +380 -0
- t402/schemes/svm/__init__.py +29 -0
- t402/schemes/svm/exact/__init__.py +35 -0
- t402/schemes/svm/exact/client.py +23 -0
- t402/schemes/svm/exact/facilitator.py +24 -0
- t402/schemes/svm/exact/server.py +20 -0
- t402/schemes/tezos/__init__.py +84 -0
- t402/schemes/tezos/constants.py +372 -0
- t402/schemes/tezos/exact_direct/__init__.py +22 -0
- t402/schemes/tezos/exact_direct/client.py +226 -0
- t402/schemes/tezos/exact_direct/facilitator.py +491 -0
- t402/schemes/tezos/exact_direct/server.py +277 -0
- t402/schemes/tezos/types.py +220 -0
- t402/schemes/ton/__init__.py +9 -2
- t402/schemes/ton/exact/__init__.py +7 -0
- t402/schemes/ton/exact/facilitator.py +730 -0
- t402/schemes/ton/exact/server.py +1 -1
- t402/schemes/tron/__init__.py +11 -2
- t402/schemes/tron/exact/__init__.py +9 -0
- t402/schemes/tron/exact/facilitator.py +673 -0
- t402/schemes/tron/exact/server.py +1 -1
- t402/schemes/upto/__init__.py +80 -0
- t402/schemes/upto/types.py +376 -0
- t402/stacks_paywall_template.py +2 -0
- t402/svm.py +45 -11
- t402/svm_paywall_template.py +1 -1
- t402/ton.py +5 -1
- t402/ton_paywall_template.py +1 -192
- t402/tron.py +2 -0
- t402/tron_paywall_template.py +2 -0
- t402/types.py +4 -2
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
- t402-1.9.1.dist-info/RECORD +125 -0
- t402-1.7.1.dist-info/RECORD +0 -67
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
- {t402-1.7.1.dist-info → t402-1.9.1.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,72 @@
|
|
|
1
|
+
"""Polkadot Blockchain Payment Schemes.
|
|
2
|
+
|
|
3
|
+
This package provides payment scheme implementations for Polkadot Asset Hub networks.
|
|
4
|
+
|
|
5
|
+
Supported schemes:
|
|
6
|
+
- exact-direct: On-chain asset transfer with extrinsic proof
|
|
7
|
+
|
|
8
|
+
Supported networks:
|
|
9
|
+
- polkadot:68d56f15f85d3136970ec16946040bc1 (Polkadot Asset Hub)
|
|
10
|
+
- polkadot:e143f23803ac50e8f6f8e62695d1ce9e (Westend Asset Hub / Testnet)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from t402.schemes.polkadot.exact_direct import (
|
|
14
|
+
ExactDirectPolkadotClientScheme,
|
|
15
|
+
ExactDirectPolkadotServerScheme,
|
|
16
|
+
ExactDirectPolkadotFacilitatorScheme,
|
|
17
|
+
ClientPolkadotSigner,
|
|
18
|
+
FacilitatorPolkadotSigner,
|
|
19
|
+
SCHEME_EXACT_DIRECT,
|
|
20
|
+
)
|
|
21
|
+
from t402.schemes.polkadot.constants import (
|
|
22
|
+
POLKADOT_ASSET_HUB_CAIP2,
|
|
23
|
+
WESTEND_ASSET_HUB_CAIP2,
|
|
24
|
+
KUSAMA_ASSET_HUB_CAIP2,
|
|
25
|
+
USDT_ASSET_ID,
|
|
26
|
+
USDT_DECIMALS,
|
|
27
|
+
NETWORKS,
|
|
28
|
+
get_network_config,
|
|
29
|
+
get_supported_networks,
|
|
30
|
+
is_polkadot_network,
|
|
31
|
+
)
|
|
32
|
+
from t402.schemes.polkadot.types import (
|
|
33
|
+
ExactDirectPayload,
|
|
34
|
+
ExtrinsicResult,
|
|
35
|
+
ParsedAssetTransfer,
|
|
36
|
+
is_valid_ss58_address,
|
|
37
|
+
is_valid_hash,
|
|
38
|
+
parse_asset_identifier,
|
|
39
|
+
create_asset_identifier,
|
|
40
|
+
extract_asset_transfer,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
# Schemes
|
|
45
|
+
"ExactDirectPolkadotClientScheme",
|
|
46
|
+
"ExactDirectPolkadotServerScheme",
|
|
47
|
+
"ExactDirectPolkadotFacilitatorScheme",
|
|
48
|
+
# Signer protocols
|
|
49
|
+
"ClientPolkadotSigner",
|
|
50
|
+
"FacilitatorPolkadotSigner",
|
|
51
|
+
# Constants
|
|
52
|
+
"SCHEME_EXACT_DIRECT",
|
|
53
|
+
"POLKADOT_ASSET_HUB_CAIP2",
|
|
54
|
+
"WESTEND_ASSET_HUB_CAIP2",
|
|
55
|
+
"KUSAMA_ASSET_HUB_CAIP2",
|
|
56
|
+
"USDT_ASSET_ID",
|
|
57
|
+
"USDT_DECIMALS",
|
|
58
|
+
"NETWORKS",
|
|
59
|
+
# Functions
|
|
60
|
+
"get_network_config",
|
|
61
|
+
"get_supported_networks",
|
|
62
|
+
"is_polkadot_network",
|
|
63
|
+
# Types
|
|
64
|
+
"ExactDirectPayload",
|
|
65
|
+
"ExtrinsicResult",
|
|
66
|
+
"ParsedAssetTransfer",
|
|
67
|
+
"is_valid_ss58_address",
|
|
68
|
+
"is_valid_hash",
|
|
69
|
+
"parse_asset_identifier",
|
|
70
|
+
"create_asset_identifier",
|
|
71
|
+
"extract_asset_transfer",
|
|
72
|
+
]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Polkadot Scheme Constants.
|
|
2
|
+
|
|
3
|
+
This module defines constants for the Polkadot exact-direct payment scheme,
|
|
4
|
+
including network identifiers, token configurations, and default endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Scheme identifier
|
|
14
|
+
SCHEME_EXACT_DIRECT = "exact-direct"
|
|
15
|
+
|
|
16
|
+
# CAIP-2 network identifiers for Polkadot parachains
|
|
17
|
+
POLKADOT_ASSET_HUB_CAIP2 = "polkadot:68d56f15f85d3136970ec16946040bc1"
|
|
18
|
+
KUSAMA_ASSET_HUB_CAIP2 = "polkadot:48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a"
|
|
19
|
+
WESTEND_ASSET_HUB_CAIP2 = "polkadot:e143f23803ac50e8f6f8e62695d1ce9e"
|
|
20
|
+
|
|
21
|
+
# Default indexer endpoints (Subscan)
|
|
22
|
+
POLKADOT_ASSET_HUB_INDEXER = "https://assethub-polkadot.api.subscan.io"
|
|
23
|
+
KUSAMA_ASSET_HUB_INDEXER = "https://assethub-kusama.api.subscan.io"
|
|
24
|
+
WESTEND_ASSET_HUB_INDEXER = "https://assethub-westend.api.subscan.io"
|
|
25
|
+
|
|
26
|
+
# Default RPC endpoints
|
|
27
|
+
POLKADOT_ASSET_HUB_RPC = "wss://polkadot-asset-hub-rpc.polkadot.io"
|
|
28
|
+
KUSAMA_ASSET_HUB_RPC = "wss://kusama-asset-hub-rpc.polkadot.io"
|
|
29
|
+
WESTEND_ASSET_HUB_RPC = "wss://westend-asset-hub-rpc.polkadot.io"
|
|
30
|
+
|
|
31
|
+
# USDT Asset ID on Asset Hub parachains
|
|
32
|
+
USDT_ASSET_ID = 1984
|
|
33
|
+
|
|
34
|
+
# Default decimals for USDT
|
|
35
|
+
USDT_DECIMALS = 6
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class TokenInfo:
|
|
40
|
+
"""Token configuration for a Polkadot asset."""
|
|
41
|
+
|
|
42
|
+
asset_id: int
|
|
43
|
+
symbol: str
|
|
44
|
+
name: str
|
|
45
|
+
decimals: int
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class NetworkConfig:
|
|
50
|
+
"""Configuration for a Polkadot network."""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
caip2: str
|
|
54
|
+
indexer_url: str
|
|
55
|
+
rpc_url: str
|
|
56
|
+
genesis_hash: str
|
|
57
|
+
ss58_prefix: int
|
|
58
|
+
is_testnet: bool
|
|
59
|
+
default_token: TokenInfo
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Default token configurations
|
|
63
|
+
USDT_POLKADOT = TokenInfo(
|
|
64
|
+
asset_id=USDT_ASSET_ID,
|
|
65
|
+
symbol="USDT",
|
|
66
|
+
name="Tether USD",
|
|
67
|
+
decimals=USDT_DECIMALS,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
USDT_KUSAMA = TokenInfo(
|
|
71
|
+
asset_id=USDT_ASSET_ID,
|
|
72
|
+
symbol="USDT",
|
|
73
|
+
name="Tether USD",
|
|
74
|
+
decimals=USDT_DECIMALS,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
USDT_WESTEND = TokenInfo(
|
|
78
|
+
asset_id=USDT_ASSET_ID,
|
|
79
|
+
symbol="USDT",
|
|
80
|
+
name="Test Tether USD",
|
|
81
|
+
decimals=USDT_DECIMALS,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Network configurations indexed by CAIP-2 identifier
|
|
85
|
+
NETWORKS: Dict[str, NetworkConfig] = {
|
|
86
|
+
POLKADOT_ASSET_HUB_CAIP2: NetworkConfig(
|
|
87
|
+
name="Polkadot Asset Hub",
|
|
88
|
+
caip2=POLKADOT_ASSET_HUB_CAIP2,
|
|
89
|
+
indexer_url=POLKADOT_ASSET_HUB_INDEXER,
|
|
90
|
+
rpc_url=POLKADOT_ASSET_HUB_RPC,
|
|
91
|
+
genesis_hash="0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f",
|
|
92
|
+
ss58_prefix=0,
|
|
93
|
+
is_testnet=False,
|
|
94
|
+
default_token=USDT_POLKADOT,
|
|
95
|
+
),
|
|
96
|
+
KUSAMA_ASSET_HUB_CAIP2: NetworkConfig(
|
|
97
|
+
name="Kusama Asset Hub",
|
|
98
|
+
caip2=KUSAMA_ASSET_HUB_CAIP2,
|
|
99
|
+
indexer_url=KUSAMA_ASSET_HUB_INDEXER,
|
|
100
|
+
rpc_url=KUSAMA_ASSET_HUB_RPC,
|
|
101
|
+
genesis_hash="0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a",
|
|
102
|
+
ss58_prefix=2,
|
|
103
|
+
is_testnet=False,
|
|
104
|
+
default_token=USDT_KUSAMA,
|
|
105
|
+
),
|
|
106
|
+
WESTEND_ASSET_HUB_CAIP2: NetworkConfig(
|
|
107
|
+
name="Westend Asset Hub",
|
|
108
|
+
caip2=WESTEND_ASSET_HUB_CAIP2,
|
|
109
|
+
indexer_url=WESTEND_ASSET_HUB_INDEXER,
|
|
110
|
+
rpc_url=WESTEND_ASSET_HUB_RPC,
|
|
111
|
+
genesis_hash="0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e",
|
|
112
|
+
ss58_prefix=42,
|
|
113
|
+
is_testnet=True,
|
|
114
|
+
default_token=USDT_WESTEND,
|
|
115
|
+
),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_network_config(network: str) -> NetworkConfig:
|
|
120
|
+
"""Get the network configuration for a CAIP-2 identifier.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
network: CAIP-2 network identifier (e.g., "polkadot:68d56f15f85d3136970ec16946040bc1")
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
NetworkConfig for the given network
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ValueError: If the network is not supported
|
|
130
|
+
"""
|
|
131
|
+
config = NETWORKS.get(network)
|
|
132
|
+
if config is None:
|
|
133
|
+
raise ValueError(f"Unsupported Polkadot network: {network}")
|
|
134
|
+
return config
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def is_polkadot_network(network: str) -> bool:
|
|
138
|
+
"""Check if a network identifier is a Polkadot network.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
network: Network identifier to check
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if the network starts with "polkadot:"
|
|
145
|
+
"""
|
|
146
|
+
return network.startswith("polkadot:")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_supported_networks() -> list:
|
|
150
|
+
"""Get all supported Polkadot network identifiers.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List of CAIP-2 network identifiers
|
|
154
|
+
"""
|
|
155
|
+
return list(NETWORKS.keys())
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Polkadot Exact-Direct Payment Scheme.
|
|
2
|
+
|
|
3
|
+
This package provides the exact-direct payment scheme implementation for
|
|
4
|
+
Polkadot Asset Hub networks using on-chain asset transfers.
|
|
5
|
+
|
|
6
|
+
The exact-direct scheme works by:
|
|
7
|
+
1. Client executes assets.transfer_keep_alive on-chain
|
|
8
|
+
2. Client returns the extrinsic hash, block hash, and index as proof
|
|
9
|
+
3. Facilitator verifies the transfer on-chain via indexer/RPC
|
|
10
|
+
|
|
11
|
+
This is a "direct" scheme because the client pays on-chain before
|
|
12
|
+
submitting the payment proof, rather than providing an off-chain signature
|
|
13
|
+
for later settlement.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from t402.schemes.polkadot.exact_direct.client import (
|
|
17
|
+
ExactDirectPolkadotClientScheme,
|
|
18
|
+
)
|
|
19
|
+
from t402.schemes.polkadot.exact_direct.server import (
|
|
20
|
+
ExactDirectPolkadotServerScheme,
|
|
21
|
+
)
|
|
22
|
+
from t402.schemes.polkadot.exact_direct.facilitator import (
|
|
23
|
+
ExactDirectPolkadotFacilitatorScheme,
|
|
24
|
+
)
|
|
25
|
+
from t402.schemes.polkadot.constants import SCHEME_EXACT_DIRECT
|
|
26
|
+
from t402.schemes.polkadot.types import (
|
|
27
|
+
ClientPolkadotSigner,
|
|
28
|
+
FacilitatorPolkadotSigner,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
# Client
|
|
33
|
+
"ExactDirectPolkadotClientScheme",
|
|
34
|
+
# Server
|
|
35
|
+
"ExactDirectPolkadotServerScheme",
|
|
36
|
+
# Facilitator
|
|
37
|
+
"ExactDirectPolkadotFacilitatorScheme",
|
|
38
|
+
# Signer protocols
|
|
39
|
+
"ClientPolkadotSigner",
|
|
40
|
+
"FacilitatorPolkadotSigner",
|
|
41
|
+
# Constants
|
|
42
|
+
"SCHEME_EXACT_DIRECT",
|
|
43
|
+
]
|