t402 1.4.0__py3-none-any.whl → 1.6.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.
- t402/__init__.py +98 -0
- t402/bridge/client.py +0 -1
- t402/bridge/types.py +0 -1
- t402/cli.py +324 -0
- t402/erc4337/accounts.py +1 -3
- t402/erc4337/paymasters.py +1 -3
- t402/erc4337/types.py +1 -2
- t402/fastapi/middleware.py +1 -1
- t402/flask/middleware.py +1 -1
- t402/mcp/__init__.py +109 -0
- t402/mcp/constants.py +213 -0
- t402/mcp/server.py +527 -0
- t402/mcp/tools.py +169 -0
- t402/mcp/types.py +241 -0
- t402/networks.py +47 -3
- t402/svm.py +566 -0
- t402/wdk/__init__.py +140 -0
- t402/wdk/chains.py +245 -0
- t402/wdk/errors.py +211 -0
- t402/wdk/signer.py +657 -0
- t402/wdk/types.py +134 -0
- {t402-1.4.0.dist-info → t402-1.6.0.dist-info}/METADATA +241 -10
- {t402-1.4.0.dist-info → t402-1.6.0.dist-info}/RECORD +25 -12
- t402-1.6.0.dist-info/entry_points.txt +2 -0
- {t402-1.4.0.dist-info → t402-1.6.0.dist-info}/WHEEL +0 -0
t402/__init__.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# Package version
|
|
2
|
+
__version__ = "1.5.3"
|
|
3
|
+
|
|
1
4
|
# Re-export commonly used items for convenience
|
|
2
5
|
from t402.common import (
|
|
3
6
|
parse_money,
|
|
@@ -9,6 +12,7 @@ from t402.networks import (
|
|
|
9
12
|
is_ton_network,
|
|
10
13
|
is_tron_network,
|
|
11
14
|
is_evm_network,
|
|
15
|
+
is_svm_network,
|
|
12
16
|
get_network_type,
|
|
13
17
|
)
|
|
14
18
|
from t402.types import (
|
|
@@ -59,6 +63,24 @@ from t402.tron import (
|
|
|
59
63
|
format_amount as format_tron_amount,
|
|
60
64
|
is_testnet as is_tron_testnet,
|
|
61
65
|
)
|
|
66
|
+
from t402.svm import (
|
|
67
|
+
SOLANA_MAINNET,
|
|
68
|
+
SOLANA_DEVNET,
|
|
69
|
+
SOLANA_TESTNET,
|
|
70
|
+
USDC_MAINNET_ADDRESS as SVM_USDC_MAINNET_ADDRESS,
|
|
71
|
+
USDC_DEVNET_ADDRESS as SVM_USDC_DEVNET_ADDRESS,
|
|
72
|
+
validate_svm_address,
|
|
73
|
+
get_usdc_address as get_svm_usdc_address,
|
|
74
|
+
get_network_config as get_svm_network_config,
|
|
75
|
+
get_default_asset as get_svm_default_asset,
|
|
76
|
+
prepare_svm_payment_header,
|
|
77
|
+
parse_amount as parse_svm_amount,
|
|
78
|
+
format_amount as format_svm_amount,
|
|
79
|
+
is_testnet as is_svm_testnet,
|
|
80
|
+
validate_transaction as validate_svm_transaction,
|
|
81
|
+
normalize_network as normalize_svm_network,
|
|
82
|
+
get_rpc_url as get_svm_rpc_url,
|
|
83
|
+
)
|
|
62
84
|
from t402.paywall import (
|
|
63
85
|
get_paywall_html,
|
|
64
86
|
get_paywall_template,
|
|
@@ -117,12 +139,43 @@ from t402.bridge import (
|
|
|
117
139
|
CrossChainPaymentParams,
|
|
118
140
|
CrossChainPaymentResult,
|
|
119
141
|
)
|
|
142
|
+
from t402.wdk import (
|
|
143
|
+
# Signer
|
|
144
|
+
WDKSigner,
|
|
145
|
+
generate_seed_phrase,
|
|
146
|
+
validate_seed_phrase,
|
|
147
|
+
# Types
|
|
148
|
+
WDKConfig,
|
|
149
|
+
ChainConfig as WDKChainConfig,
|
|
150
|
+
NetworkType,
|
|
151
|
+
TokenInfo as WDKTokenInfo,
|
|
152
|
+
TokenBalance,
|
|
153
|
+
ChainBalance,
|
|
154
|
+
AggregatedBalance,
|
|
155
|
+
PaymentParams,
|
|
156
|
+
PaymentResult,
|
|
157
|
+
SignedTypedData,
|
|
158
|
+
# Chain utilities
|
|
159
|
+
DEFAULT_CHAINS as WDK_DEFAULT_CHAINS,
|
|
160
|
+
USDT0_ADDRESSES as WDK_USDT0_ADDRESSES,
|
|
161
|
+
get_chain_config as get_wdk_chain_config,
|
|
162
|
+
get_usdt0_chains as get_wdk_usdt0_chains,
|
|
163
|
+
# Errors
|
|
164
|
+
WDKError,
|
|
165
|
+
WDKInitializationError,
|
|
166
|
+
SignerError,
|
|
167
|
+
SigningError,
|
|
168
|
+
BalanceError as WDKBalanceError,
|
|
169
|
+
WDKErrorCode,
|
|
170
|
+
)
|
|
120
171
|
|
|
121
172
|
def hello() -> str:
|
|
122
173
|
return "Hello from t402!"
|
|
123
174
|
|
|
124
175
|
|
|
125
176
|
__all__ = [
|
|
177
|
+
# Version
|
|
178
|
+
"__version__",
|
|
126
179
|
# Core
|
|
127
180
|
"hello",
|
|
128
181
|
"t402_VERSION",
|
|
@@ -134,6 +187,7 @@ __all__ = [
|
|
|
134
187
|
"is_ton_network",
|
|
135
188
|
"is_tron_network",
|
|
136
189
|
"is_evm_network",
|
|
190
|
+
"is_svm_network",
|
|
137
191
|
"get_network_type",
|
|
138
192
|
# Types
|
|
139
193
|
"PaymentRequirements",
|
|
@@ -181,6 +235,23 @@ __all__ = [
|
|
|
181
235
|
"parse_tron_amount",
|
|
182
236
|
"format_tron_amount",
|
|
183
237
|
"is_tron_testnet",
|
|
238
|
+
# SVM (Solana) utilities
|
|
239
|
+
"SOLANA_MAINNET",
|
|
240
|
+
"SOLANA_DEVNET",
|
|
241
|
+
"SOLANA_TESTNET",
|
|
242
|
+
"SVM_USDC_MAINNET_ADDRESS",
|
|
243
|
+
"SVM_USDC_DEVNET_ADDRESS",
|
|
244
|
+
"validate_svm_address",
|
|
245
|
+
"get_svm_usdc_address",
|
|
246
|
+
"get_svm_network_config",
|
|
247
|
+
"get_svm_default_asset",
|
|
248
|
+
"prepare_svm_payment_header",
|
|
249
|
+
"parse_svm_amount",
|
|
250
|
+
"format_svm_amount",
|
|
251
|
+
"is_svm_testnet",
|
|
252
|
+
"validate_svm_transaction",
|
|
253
|
+
"normalize_svm_network",
|
|
254
|
+
"get_svm_rpc_url",
|
|
184
255
|
# Paywall
|
|
185
256
|
"get_paywall_html",
|
|
186
257
|
"get_paywall_template",
|
|
@@ -226,4 +297,31 @@ __all__ = [
|
|
|
226
297
|
"LayerZeroMessageStatus",
|
|
227
298
|
"CrossChainPaymentParams",
|
|
228
299
|
"CrossChainPaymentResult",
|
|
300
|
+
# WDK - Signer
|
|
301
|
+
"WDKSigner",
|
|
302
|
+
"generate_seed_phrase",
|
|
303
|
+
"validate_seed_phrase",
|
|
304
|
+
# WDK - Types
|
|
305
|
+
"WDKConfig",
|
|
306
|
+
"WDKChainConfig",
|
|
307
|
+
"NetworkType",
|
|
308
|
+
"WDKTokenInfo",
|
|
309
|
+
"TokenBalance",
|
|
310
|
+
"ChainBalance",
|
|
311
|
+
"AggregatedBalance",
|
|
312
|
+
"PaymentParams",
|
|
313
|
+
"PaymentResult",
|
|
314
|
+
"SignedTypedData",
|
|
315
|
+
# WDK - Chain utilities
|
|
316
|
+
"WDK_DEFAULT_CHAINS",
|
|
317
|
+
"WDK_USDT0_ADDRESSES",
|
|
318
|
+
"get_wdk_chain_config",
|
|
319
|
+
"get_wdk_usdt0_chains",
|
|
320
|
+
# WDK - Errors
|
|
321
|
+
"WDKError",
|
|
322
|
+
"WDKInitializationError",
|
|
323
|
+
"SignerError",
|
|
324
|
+
"SigningError",
|
|
325
|
+
"WDKBalanceError",
|
|
326
|
+
"WDKErrorCode",
|
|
229
327
|
]
|
t402/bridge/client.py
CHANGED
t402/bridge/types.py
CHANGED
t402/cli.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
T402 CLI - Command-line interface for the T402 payment protocol.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
t402 verify <payment-payload> [--facilitator URL]
|
|
7
|
+
t402 settle <payment-payload> [--facilitator URL]
|
|
8
|
+
t402 supported [--facilitator URL]
|
|
9
|
+
t402 encode <json-file>
|
|
10
|
+
t402 decode <base64-string>
|
|
11
|
+
t402 version
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import asyncio
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from . import __version__
|
|
23
|
+
from .encoding import decode_payment, encode_payment
|
|
24
|
+
from .facilitator import FacilitatorClient, FacilitatorConfig
|
|
25
|
+
from .types import PaymentPayload
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
29
|
+
"""Create the argument parser for the CLI."""
|
|
30
|
+
parser = argparse.ArgumentParser(
|
|
31
|
+
prog="t402",
|
|
32
|
+
description="T402 Payment Protocol CLI",
|
|
33
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
34
|
+
epilog="""
|
|
35
|
+
Examples:
|
|
36
|
+
# Verify a payment
|
|
37
|
+
t402 verify <base64-encoded-payload>
|
|
38
|
+
|
|
39
|
+
# Settle a payment
|
|
40
|
+
t402 settle <base64-encoded-payload>
|
|
41
|
+
|
|
42
|
+
# List supported networks and schemes
|
|
43
|
+
t402 supported
|
|
44
|
+
|
|
45
|
+
# Encode a payment payload from JSON
|
|
46
|
+
t402 encode payment.json
|
|
47
|
+
|
|
48
|
+
# Decode a base64 payment payload
|
|
49
|
+
t402 decode <base64-string>
|
|
50
|
+
""",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"-v", "--version", action="version", version=f"t402 {__version__}"
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"-f",
|
|
57
|
+
"--facilitator",
|
|
58
|
+
default="https://facilitator.t402.io",
|
|
59
|
+
help="Facilitator URL (default: https://facilitator.t402.io)",
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"-o",
|
|
63
|
+
"--output",
|
|
64
|
+
choices=["json", "text"],
|
|
65
|
+
default="text",
|
|
66
|
+
help="Output format (default: text)",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
70
|
+
|
|
71
|
+
# verify command
|
|
72
|
+
verify_parser = subparsers.add_parser("verify", help="Verify a payment payload")
|
|
73
|
+
verify_parser.add_argument("payload", help="Base64-encoded payment payload")
|
|
74
|
+
|
|
75
|
+
# settle command
|
|
76
|
+
settle_parser = subparsers.add_parser("settle", help="Settle a payment")
|
|
77
|
+
settle_parser.add_argument("payload", help="Base64-encoded payment payload")
|
|
78
|
+
|
|
79
|
+
# supported command
|
|
80
|
+
subparsers.add_parser("supported", help="List supported networks and schemes")
|
|
81
|
+
|
|
82
|
+
# encode command
|
|
83
|
+
encode_parser = subparsers.add_parser(
|
|
84
|
+
"encode", help="Encode a payment payload from JSON"
|
|
85
|
+
)
|
|
86
|
+
encode_parser.add_argument(
|
|
87
|
+
"file", type=Path, help="JSON file containing payment payload"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# decode command
|
|
91
|
+
decode_parser = subparsers.add_parser(
|
|
92
|
+
"decode", help="Decode a base64-encoded payment payload"
|
|
93
|
+
)
|
|
94
|
+
decode_parser.add_argument("payload", help="Base64-encoded payment payload")
|
|
95
|
+
|
|
96
|
+
# info command
|
|
97
|
+
info_parser = subparsers.add_parser("info", help="Show information about a network")
|
|
98
|
+
info_parser.add_argument("network", help="Network identifier (e.g., eip155:1)")
|
|
99
|
+
|
|
100
|
+
return parser
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def output_result(result: Any, output_format: str) -> None:
|
|
104
|
+
"""Output result in the specified format."""
|
|
105
|
+
if output_format == "json":
|
|
106
|
+
if hasattr(result, "model_dump"):
|
|
107
|
+
print(json.dumps(result.model_dump(), indent=2))
|
|
108
|
+
elif isinstance(result, dict):
|
|
109
|
+
print(json.dumps(result, indent=2))
|
|
110
|
+
else:
|
|
111
|
+
print(json.dumps({"result": str(result)}, indent=2))
|
|
112
|
+
else:
|
|
113
|
+
if isinstance(result, dict):
|
|
114
|
+
for key, value in result.items():
|
|
115
|
+
print(f"{key}: {value}")
|
|
116
|
+
else:
|
|
117
|
+
print(result)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def cmd_verify(args: argparse.Namespace) -> int:
|
|
121
|
+
"""Verify a payment payload."""
|
|
122
|
+
try:
|
|
123
|
+
config = FacilitatorConfig(base_url=args.facilitator)
|
|
124
|
+
client = FacilitatorClient(config)
|
|
125
|
+
|
|
126
|
+
# Decode the payload first
|
|
127
|
+
payload_dict = decode_payment(args.payload)
|
|
128
|
+
payload = PaymentPayload.model_validate(payload_dict)
|
|
129
|
+
|
|
130
|
+
result = await client.verify(payload)
|
|
131
|
+
|
|
132
|
+
if args.output == "json":
|
|
133
|
+
print(json.dumps({"valid": result.valid, "error": result.error}, indent=2))
|
|
134
|
+
else:
|
|
135
|
+
if result.valid:
|
|
136
|
+
print("Payment is VALID")
|
|
137
|
+
else:
|
|
138
|
+
print(f"Payment is INVALID: {result.error}")
|
|
139
|
+
|
|
140
|
+
return 0 if result.valid else 1
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
143
|
+
return 1
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def cmd_settle(args: argparse.Namespace) -> int:
|
|
147
|
+
"""Settle a payment."""
|
|
148
|
+
try:
|
|
149
|
+
config = FacilitatorConfig(base_url=args.facilitator)
|
|
150
|
+
client = FacilitatorClient(config)
|
|
151
|
+
|
|
152
|
+
# Decode the payload first
|
|
153
|
+
payload_dict = decode_payment(args.payload)
|
|
154
|
+
payload = PaymentPayload.model_validate(payload_dict)
|
|
155
|
+
|
|
156
|
+
result = await client.settle(payload)
|
|
157
|
+
|
|
158
|
+
if args.output == "json":
|
|
159
|
+
print(
|
|
160
|
+
json.dumps(
|
|
161
|
+
{
|
|
162
|
+
"success": result.success,
|
|
163
|
+
"transaction_hash": result.transaction_hash,
|
|
164
|
+
"error": result.error,
|
|
165
|
+
},
|
|
166
|
+
indent=2,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
if result.success:
|
|
171
|
+
print("Payment settled successfully!")
|
|
172
|
+
print(f"Transaction hash: {result.transaction_hash}")
|
|
173
|
+
else:
|
|
174
|
+
print(f"Settlement failed: {result.error}")
|
|
175
|
+
|
|
176
|
+
return 0 if result.success else 1
|
|
177
|
+
except Exception as e:
|
|
178
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
179
|
+
return 1
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def cmd_supported(args: argparse.Namespace) -> int:
|
|
183
|
+
"""List supported networks and schemes."""
|
|
184
|
+
try:
|
|
185
|
+
config = FacilitatorConfig(base_url=args.facilitator)
|
|
186
|
+
client = FacilitatorClient(config)
|
|
187
|
+
|
|
188
|
+
result = await client.list_supported()
|
|
189
|
+
|
|
190
|
+
if args.output == "json":
|
|
191
|
+
print(
|
|
192
|
+
json.dumps(
|
|
193
|
+
{
|
|
194
|
+
"kinds": [k.model_dump() for k in result.kinds],
|
|
195
|
+
"signers": result.signers,
|
|
196
|
+
"extensions": result.extensions,
|
|
197
|
+
},
|
|
198
|
+
indent=2,
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
else:
|
|
202
|
+
print("Supported Payment Kinds:")
|
|
203
|
+
print("-" * 50)
|
|
204
|
+
for kind in result.kinds:
|
|
205
|
+
print(f" Scheme: {kind.scheme}")
|
|
206
|
+
print(f" Network: {kind.network}")
|
|
207
|
+
if hasattr(kind, "token") and kind.token:
|
|
208
|
+
print(f" Token: {kind.token}")
|
|
209
|
+
print()
|
|
210
|
+
|
|
211
|
+
if result.signers:
|
|
212
|
+
print("Supported Signers:")
|
|
213
|
+
for signer in result.signers:
|
|
214
|
+
print(f" - {signer}")
|
|
215
|
+
print()
|
|
216
|
+
|
|
217
|
+
if result.extensions:
|
|
218
|
+
print("Supported Extensions:")
|
|
219
|
+
for ext in result.extensions:
|
|
220
|
+
print(f" - {ext}")
|
|
221
|
+
|
|
222
|
+
return 0
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
225
|
+
return 1
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def cmd_encode(args: argparse.Namespace) -> int:
|
|
229
|
+
"""Encode a payment payload from JSON."""
|
|
230
|
+
try:
|
|
231
|
+
with open(args.file) as f:
|
|
232
|
+
payload_dict = json.load(f)
|
|
233
|
+
|
|
234
|
+
encoded = encode_payment(payload_dict)
|
|
235
|
+
print(encoded)
|
|
236
|
+
return 0
|
|
237
|
+
except FileNotFoundError:
|
|
238
|
+
print(f"Error: File not found: {args.file}", file=sys.stderr)
|
|
239
|
+
return 1
|
|
240
|
+
except json.JSONDecodeError as e:
|
|
241
|
+
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
|
|
242
|
+
return 1
|
|
243
|
+
except Exception as e:
|
|
244
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
245
|
+
return 1
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def cmd_decode(args: argparse.Namespace) -> int:
|
|
249
|
+
"""Decode a base64-encoded payment payload."""
|
|
250
|
+
try:
|
|
251
|
+
decoded = decode_payment(args.payload)
|
|
252
|
+
|
|
253
|
+
if args.output == "json":
|
|
254
|
+
print(json.dumps(decoded, indent=2))
|
|
255
|
+
else:
|
|
256
|
+
print(json.dumps(decoded, indent=2))
|
|
257
|
+
|
|
258
|
+
return 0
|
|
259
|
+
except Exception as e:
|
|
260
|
+
print(f"Error: Failed to decode payload: {e}", file=sys.stderr)
|
|
261
|
+
return 1
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def cmd_info(args: argparse.Namespace) -> int:
|
|
265
|
+
"""Show information about a network."""
|
|
266
|
+
from .networks import is_evm_network, is_ton_network, is_tron_network
|
|
267
|
+
|
|
268
|
+
network = args.network
|
|
269
|
+
|
|
270
|
+
info = {
|
|
271
|
+
"network": network,
|
|
272
|
+
"is_evm": is_evm_network(network),
|
|
273
|
+
"is_ton": is_ton_network(network),
|
|
274
|
+
"is_tron": is_tron_network(network),
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Add chain-specific info
|
|
278
|
+
if is_evm_network(network):
|
|
279
|
+
from .chains import EVM_CHAINS
|
|
280
|
+
|
|
281
|
+
chain_id = network.split(":")[1] if ":" in network else network
|
|
282
|
+
if chain_id in EVM_CHAINS:
|
|
283
|
+
chain = EVM_CHAINS[chain_id]
|
|
284
|
+
info["chain_name"] = chain.get("name", "Unknown")
|
|
285
|
+
info["currency"] = chain.get("currency", "Unknown")
|
|
286
|
+
|
|
287
|
+
if args.output == "json":
|
|
288
|
+
print(json.dumps(info, indent=2))
|
|
289
|
+
else:
|
|
290
|
+
for key, value in info.items():
|
|
291
|
+
print(f"{key}: {value}")
|
|
292
|
+
|
|
293
|
+
return 0
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def main() -> int:
|
|
297
|
+
"""Main entry point for the CLI."""
|
|
298
|
+
parser = create_parser()
|
|
299
|
+
args = parser.parse_args()
|
|
300
|
+
|
|
301
|
+
if not args.command:
|
|
302
|
+
parser.print_help()
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
# Route to the appropriate command handler
|
|
306
|
+
if args.command == "verify":
|
|
307
|
+
return asyncio.run(cmd_verify(args))
|
|
308
|
+
elif args.command == "settle":
|
|
309
|
+
return asyncio.run(cmd_settle(args))
|
|
310
|
+
elif args.command == "supported":
|
|
311
|
+
return asyncio.run(cmd_supported(args))
|
|
312
|
+
elif args.command == "encode":
|
|
313
|
+
return cmd_encode(args)
|
|
314
|
+
elif args.command == "decode":
|
|
315
|
+
return cmd_decode(args)
|
|
316
|
+
elif args.command == "info":
|
|
317
|
+
return cmd_info(args)
|
|
318
|
+
else:
|
|
319
|
+
parser.print_help()
|
|
320
|
+
return 0
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if __name__ == "__main__":
|
|
324
|
+
sys.exit(main())
|
t402/erc4337/accounts.py
CHANGED
|
@@ -6,7 +6,7 @@ including Safe smart account with 4337 module support.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
-
from typing import Optional, List
|
|
9
|
+
from typing import Optional, List
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
11
|
from eth_account import Account
|
|
12
12
|
from eth_account.messages import encode_defunct
|
|
@@ -95,8 +95,6 @@ class SafeSmartAccount(SmartAccountSigner):
|
|
|
95
95
|
return self._cached_address
|
|
96
96
|
|
|
97
97
|
# Calculate counterfactual address via CREATE2
|
|
98
|
-
init_code = self.get_init_code()
|
|
99
|
-
|
|
100
98
|
factory_address = bytes.fromhex(SAFE_4337_ADDRESSES["proxy_factory"][2:])
|
|
101
99
|
salt_hash = self._calculate_salt()
|
|
102
100
|
proxy_init_code = self._get_proxy_creation_code()
|
t402/erc4337/paymasters.py
CHANGED
|
@@ -6,15 +6,13 @@ gas sponsorship, including Pimlico, Biconomy, and Stackup clients.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
|
-
from
|
|
10
|
-
from typing import Optional, List, Dict, Any, Union
|
|
9
|
+
from typing import Optional, List, Dict, Any
|
|
11
10
|
from abc import ABC, abstractmethod
|
|
12
11
|
|
|
13
12
|
from .types import (
|
|
14
13
|
UserOperation,
|
|
15
14
|
PaymasterData,
|
|
16
15
|
TokenQuote,
|
|
17
|
-
GasEstimate,
|
|
18
16
|
ENTRYPOINT_V07_ADDRESS,
|
|
19
17
|
PIMLICO_NETWORKS,
|
|
20
18
|
DEFAULT_GAS_LIMITS,
|
t402/erc4337/types.py
CHANGED
|
@@ -7,8 +7,7 @@ bundler interactions, and paymaster integration.
|
|
|
7
7
|
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
from enum import Enum
|
|
10
|
-
from typing import Optional, List
|
|
11
|
-
from eth_typing import HexStr, Address
|
|
10
|
+
from typing import Optional, List
|
|
12
11
|
|
|
13
12
|
# EntryPoint addresses (canonical deployments)
|
|
14
13
|
ENTRYPOINT_V07_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
|
t402/fastapi/middleware.py
CHANGED
|
@@ -108,7 +108,7 @@ def require_payment(
|
|
|
108
108
|
mime_type=mime_type,
|
|
109
109
|
pay_to=pay_to_address,
|
|
110
110
|
max_timeout_seconds=max_deadline_seconds,
|
|
111
|
-
#
|
|
111
|
+
# Contains both input and output schema (field name kept for backwards compatibility)
|
|
112
112
|
output_schema={
|
|
113
113
|
"input": {
|
|
114
114
|
"type": "http",
|
t402/flask/middleware.py
CHANGED
|
@@ -192,7 +192,7 @@ class PaymentMiddleware:
|
|
|
192
192
|
mime_type=config["mime_type"],
|
|
193
193
|
pay_to=config["pay_to_address"],
|
|
194
194
|
max_timeout_seconds=config["max_deadline_seconds"],
|
|
195
|
-
#
|
|
195
|
+
# Contains both input and output schema (field name kept for backwards compatibility)
|
|
196
196
|
output_schema={
|
|
197
197
|
"input": {
|
|
198
198
|
"type": "http",
|
t402/mcp/__init__.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""T402 MCP Server - Model Context Protocol server for AI agent integration.
|
|
2
|
+
|
|
3
|
+
This module provides an MCP server that enables AI agents to interact with
|
|
4
|
+
blockchain payments using the T402 protocol.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
from t402.mcp import T402McpServer, ServerConfig
|
|
9
|
+
|
|
10
|
+
config = ServerConfig(demo_mode=True)
|
|
11
|
+
server = T402McpServer(config)
|
|
12
|
+
await server.run()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Available Tools:
|
|
16
|
+
- t402/getBalance: Get token balances for a wallet on specific network
|
|
17
|
+
- t402/getAllBalances: Get balances across all supported networks
|
|
18
|
+
- t402/pay: Execute stablecoin payments (USDC, USDT, USDT0)
|
|
19
|
+
- t402/payGasless: ERC-4337 gasless payments
|
|
20
|
+
- t402/getBridgeFee: Get LayerZero bridge fee quotes
|
|
21
|
+
- t402/bridge: Bridge USDT0 between chains via LayerZero
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .server import T402McpServer, run_server
|
|
25
|
+
from .types import (
|
|
26
|
+
ServerConfig,
|
|
27
|
+
SupportedNetwork,
|
|
28
|
+
SupportedToken,
|
|
29
|
+
Tool,
|
|
30
|
+
ToolResult,
|
|
31
|
+
GetBalanceInput,
|
|
32
|
+
GetAllBalancesInput,
|
|
33
|
+
PayInput,
|
|
34
|
+
PayGaslessInput,
|
|
35
|
+
GetBridgeFeeInput,
|
|
36
|
+
BridgeInput,
|
|
37
|
+
BalanceInfo,
|
|
38
|
+
NetworkBalance,
|
|
39
|
+
PaymentResult,
|
|
40
|
+
BridgeFeeResult,
|
|
41
|
+
BridgeResultData,
|
|
42
|
+
)
|
|
43
|
+
from .constants import (
|
|
44
|
+
CHAIN_IDS,
|
|
45
|
+
NATIVE_SYMBOLS,
|
|
46
|
+
EXPLORER_URLS,
|
|
47
|
+
DEFAULT_RPC_URLS,
|
|
48
|
+
USDC_ADDRESSES,
|
|
49
|
+
USDT_ADDRESSES,
|
|
50
|
+
USDT0_ADDRESSES,
|
|
51
|
+
BRIDGEABLE_CHAINS,
|
|
52
|
+
GASLESS_NETWORKS,
|
|
53
|
+
ALL_NETWORKS,
|
|
54
|
+
is_valid_network,
|
|
55
|
+
is_bridgeable_chain,
|
|
56
|
+
is_gasless_network,
|
|
57
|
+
get_token_address,
|
|
58
|
+
get_explorer_tx_url,
|
|
59
|
+
get_rpc_url,
|
|
60
|
+
format_token_amount,
|
|
61
|
+
parse_token_amount,
|
|
62
|
+
)
|
|
63
|
+
from .tools import get_tool_definitions
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
# Server
|
|
67
|
+
"T402McpServer",
|
|
68
|
+
"run_server",
|
|
69
|
+
# Config and types
|
|
70
|
+
"ServerConfig",
|
|
71
|
+
"SupportedNetwork",
|
|
72
|
+
"SupportedToken",
|
|
73
|
+
"Tool",
|
|
74
|
+
"ToolResult",
|
|
75
|
+
# Input types
|
|
76
|
+
"GetBalanceInput",
|
|
77
|
+
"GetAllBalancesInput",
|
|
78
|
+
"PayInput",
|
|
79
|
+
"PayGaslessInput",
|
|
80
|
+
"GetBridgeFeeInput",
|
|
81
|
+
"BridgeInput",
|
|
82
|
+
# Result types
|
|
83
|
+
"BalanceInfo",
|
|
84
|
+
"NetworkBalance",
|
|
85
|
+
"PaymentResult",
|
|
86
|
+
"BridgeFeeResult",
|
|
87
|
+
"BridgeResultData",
|
|
88
|
+
# Constants
|
|
89
|
+
"CHAIN_IDS",
|
|
90
|
+
"NATIVE_SYMBOLS",
|
|
91
|
+
"EXPLORER_URLS",
|
|
92
|
+
"DEFAULT_RPC_URLS",
|
|
93
|
+
"USDC_ADDRESSES",
|
|
94
|
+
"USDT_ADDRESSES",
|
|
95
|
+
"USDT0_ADDRESSES",
|
|
96
|
+
"BRIDGEABLE_CHAINS",
|
|
97
|
+
"GASLESS_NETWORKS",
|
|
98
|
+
"ALL_NETWORKS",
|
|
99
|
+
# Functions
|
|
100
|
+
"is_valid_network",
|
|
101
|
+
"is_bridgeable_chain",
|
|
102
|
+
"is_gasless_network",
|
|
103
|
+
"get_token_address",
|
|
104
|
+
"get_explorer_tx_url",
|
|
105
|
+
"get_rpc_url",
|
|
106
|
+
"format_token_amount",
|
|
107
|
+
"parse_token_amount",
|
|
108
|
+
"get_tool_definitions",
|
|
109
|
+
]
|