t402 1.2.0__py3-none-any.whl → 1.4.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 +94 -0
- t402/bridge/__init__.py +140 -0
- t402/bridge/client.py +328 -0
- t402/bridge/constants.py +275 -0
- t402/bridge/router.py +221 -0
- t402/bridge/scan.py +226 -0
- t402/bridge/types.py +307 -0
- t402/erc4337/__init__.py +188 -0
- t402/erc4337/accounts.py +434 -0
- t402/erc4337/bundlers.py +458 -0
- t402/erc4337/paymasters.py +532 -0
- t402/erc4337/types.py +315 -0
- {t402-1.2.0.dist-info → t402-1.4.0.dist-info}/METADATA +1 -1
- {t402-1.2.0.dist-info → t402-1.4.0.dist-info}/RECORD +15 -4
- {t402-1.2.0.dist-info → t402-1.4.0.dist-info}/WHEEL +0 -0
t402/__init__.py
CHANGED
|
@@ -64,6 +64,59 @@ from t402.paywall import (
|
|
|
64
64
|
get_paywall_template,
|
|
65
65
|
is_browser_request,
|
|
66
66
|
)
|
|
67
|
+
from t402.erc4337 import (
|
|
68
|
+
# Constants
|
|
69
|
+
ENTRYPOINT_V07_ADDRESS,
|
|
70
|
+
ENTRYPOINT_V06_ADDRESS,
|
|
71
|
+
SAFE_4337_ADDRESSES,
|
|
72
|
+
SUPPORTED_CHAINS as ERC4337_SUPPORTED_CHAINS,
|
|
73
|
+
# Types
|
|
74
|
+
UserOperation,
|
|
75
|
+
PackedUserOperation,
|
|
76
|
+
PaymasterData,
|
|
77
|
+
GasEstimate,
|
|
78
|
+
UserOperationReceipt,
|
|
79
|
+
# Bundlers
|
|
80
|
+
GenericBundlerClient,
|
|
81
|
+
PimlicoBundlerClient,
|
|
82
|
+
AlchemyBundlerClient,
|
|
83
|
+
create_bundler_client,
|
|
84
|
+
# Paymasters
|
|
85
|
+
PimlicoPaymaster,
|
|
86
|
+
BiconomyPaymaster,
|
|
87
|
+
StackupPaymaster,
|
|
88
|
+
create_paymaster,
|
|
89
|
+
# Accounts
|
|
90
|
+
SafeSmartAccount,
|
|
91
|
+
SafeAccountConfig,
|
|
92
|
+
create_smart_account,
|
|
93
|
+
)
|
|
94
|
+
from t402.bridge import (
|
|
95
|
+
# Client
|
|
96
|
+
Usdt0Bridge,
|
|
97
|
+
create_usdt0_bridge,
|
|
98
|
+
# LayerZero Scan
|
|
99
|
+
LayerZeroScanClient,
|
|
100
|
+
create_layerzero_scan_client,
|
|
101
|
+
# Router
|
|
102
|
+
CrossChainPaymentRouter,
|
|
103
|
+
create_cross_chain_payment_router,
|
|
104
|
+
# Constants
|
|
105
|
+
LAYERZERO_ENDPOINT_IDS,
|
|
106
|
+
USDT0_OFT_ADDRESSES,
|
|
107
|
+
LAYERZERO_SCAN_BASE_URL,
|
|
108
|
+
get_bridgeable_chains,
|
|
109
|
+
supports_bridging,
|
|
110
|
+
# Types
|
|
111
|
+
BridgeQuoteParams,
|
|
112
|
+
BridgeQuote,
|
|
113
|
+
BridgeExecuteParams,
|
|
114
|
+
BridgeResult,
|
|
115
|
+
LayerZeroMessage,
|
|
116
|
+
LayerZeroMessageStatus,
|
|
117
|
+
CrossChainPaymentParams,
|
|
118
|
+
CrossChainPaymentResult,
|
|
119
|
+
)
|
|
67
120
|
|
|
68
121
|
def hello() -> str:
|
|
69
122
|
return "Hello from t402!"
|
|
@@ -132,4 +185,45 @@ __all__ = [
|
|
|
132
185
|
"get_paywall_html",
|
|
133
186
|
"get_paywall_template",
|
|
134
187
|
"is_browser_request",
|
|
188
|
+
# ERC-4337 Account Abstraction
|
|
189
|
+
"ENTRYPOINT_V07_ADDRESS",
|
|
190
|
+
"ENTRYPOINT_V06_ADDRESS",
|
|
191
|
+
"SAFE_4337_ADDRESSES",
|
|
192
|
+
"ERC4337_SUPPORTED_CHAINS",
|
|
193
|
+
"UserOperation",
|
|
194
|
+
"PackedUserOperation",
|
|
195
|
+
"PaymasterData",
|
|
196
|
+
"GasEstimate",
|
|
197
|
+
"UserOperationReceipt",
|
|
198
|
+
"GenericBundlerClient",
|
|
199
|
+
"PimlicoBundlerClient",
|
|
200
|
+
"AlchemyBundlerClient",
|
|
201
|
+
"create_bundler_client",
|
|
202
|
+
"PimlicoPaymaster",
|
|
203
|
+
"BiconomyPaymaster",
|
|
204
|
+
"StackupPaymaster",
|
|
205
|
+
"create_paymaster",
|
|
206
|
+
"SafeSmartAccount",
|
|
207
|
+
"SafeAccountConfig",
|
|
208
|
+
"create_smart_account",
|
|
209
|
+
# USDT0 Bridge
|
|
210
|
+
"Usdt0Bridge",
|
|
211
|
+
"create_usdt0_bridge",
|
|
212
|
+
"LayerZeroScanClient",
|
|
213
|
+
"create_layerzero_scan_client",
|
|
214
|
+
"CrossChainPaymentRouter",
|
|
215
|
+
"create_cross_chain_payment_router",
|
|
216
|
+
"LAYERZERO_ENDPOINT_IDS",
|
|
217
|
+
"USDT0_OFT_ADDRESSES",
|
|
218
|
+
"LAYERZERO_SCAN_BASE_URL",
|
|
219
|
+
"get_bridgeable_chains",
|
|
220
|
+
"supports_bridging",
|
|
221
|
+
"BridgeQuoteParams",
|
|
222
|
+
"BridgeQuote",
|
|
223
|
+
"BridgeExecuteParams",
|
|
224
|
+
"BridgeResult",
|
|
225
|
+
"LayerZeroMessage",
|
|
226
|
+
"LayerZeroMessageStatus",
|
|
227
|
+
"CrossChainPaymentParams",
|
|
228
|
+
"CrossChainPaymentResult",
|
|
135
229
|
]
|
t402/bridge/__init__.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""USDT0 Cross-Chain Bridge Module.
|
|
2
|
+
|
|
3
|
+
Provides cross-chain USDT0 transfers using LayerZero OFT standard,
|
|
4
|
+
with message tracking via LayerZero Scan API.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
from t402.bridge import (
|
|
9
|
+
Usdt0Bridge,
|
|
10
|
+
LayerZeroScanClient,
|
|
11
|
+
CrossChainPaymentRouter,
|
|
12
|
+
get_bridgeable_chains,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Check supported chains
|
|
16
|
+
print(get_bridgeable_chains()) # ['ethereum', 'arbitrum', 'ink', ...]
|
|
17
|
+
|
|
18
|
+
# Create bridge client
|
|
19
|
+
bridge = Usdt0Bridge(signer, 'arbitrum')
|
|
20
|
+
|
|
21
|
+
# Get quote
|
|
22
|
+
quote = await bridge.quote(BridgeQuoteParams(
|
|
23
|
+
from_chain='arbitrum',
|
|
24
|
+
to_chain='ethereum',
|
|
25
|
+
amount=100_000000,
|
|
26
|
+
recipient='0x...',
|
|
27
|
+
))
|
|
28
|
+
|
|
29
|
+
print(f"Fee: {quote.native_fee} wei")
|
|
30
|
+
|
|
31
|
+
# Execute bridge
|
|
32
|
+
result = await bridge.send(BridgeExecuteParams(
|
|
33
|
+
from_chain='arbitrum',
|
|
34
|
+
to_chain='ethereum',
|
|
35
|
+
amount=100_000000,
|
|
36
|
+
recipient='0x...',
|
|
37
|
+
))
|
|
38
|
+
|
|
39
|
+
print(f"Bridge tx: {result.tx_hash}")
|
|
40
|
+
print(f"Message GUID: {result.message_guid}")
|
|
41
|
+
|
|
42
|
+
# Track message delivery
|
|
43
|
+
scan_client = LayerZeroScanClient()
|
|
44
|
+
message = await scan_client.wait_for_delivery(
|
|
45
|
+
result.message_guid,
|
|
46
|
+
on_status_change=lambda s: print(f"Status: {s}")
|
|
47
|
+
)
|
|
48
|
+
print(f"Delivered! Dest TX: {message.dst_tx_hash}")
|
|
49
|
+
```
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
# Bridge client
|
|
53
|
+
from .client import Usdt0Bridge, create_usdt0_bridge
|
|
54
|
+
|
|
55
|
+
# LayerZero Scan client
|
|
56
|
+
from .scan import LayerZeroScanClient, create_layerzero_scan_client
|
|
57
|
+
|
|
58
|
+
# Cross-chain payment router
|
|
59
|
+
from .router import CrossChainPaymentRouter, create_cross_chain_payment_router
|
|
60
|
+
|
|
61
|
+
# Constants
|
|
62
|
+
from .constants import (
|
|
63
|
+
LAYERZERO_ENDPOINT_IDS,
|
|
64
|
+
USDT0_OFT_ADDRESSES,
|
|
65
|
+
LAYERZERO_SCAN_BASE_URL,
|
|
66
|
+
NETWORK_TO_CHAIN,
|
|
67
|
+
CHAIN_TO_NETWORK,
|
|
68
|
+
get_endpoint_id,
|
|
69
|
+
get_endpoint_id_from_network,
|
|
70
|
+
get_usdt0_oft_address,
|
|
71
|
+
supports_bridging,
|
|
72
|
+
get_bridgeable_chains,
|
|
73
|
+
address_to_bytes32,
|
|
74
|
+
bytes32_to_address,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Types
|
|
78
|
+
from .types import (
|
|
79
|
+
# Bridge types
|
|
80
|
+
BridgeQuoteParams,
|
|
81
|
+
BridgeQuote,
|
|
82
|
+
BridgeExecuteParams,
|
|
83
|
+
BridgeResult,
|
|
84
|
+
BridgeStatus,
|
|
85
|
+
BridgeSigner,
|
|
86
|
+
SendParam,
|
|
87
|
+
MessagingFee,
|
|
88
|
+
TransactionLog,
|
|
89
|
+
BridgeTransactionReceipt,
|
|
90
|
+
# LayerZero Scan types
|
|
91
|
+
LayerZeroMessage,
|
|
92
|
+
LayerZeroMessageStatus,
|
|
93
|
+
WaitForDeliveryOptions,
|
|
94
|
+
# Cross-chain payment types
|
|
95
|
+
CrossChainPaymentParams,
|
|
96
|
+
CrossChainPaymentResult,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
__all__ = [
|
|
100
|
+
# Bridge client
|
|
101
|
+
"Usdt0Bridge",
|
|
102
|
+
"create_usdt0_bridge",
|
|
103
|
+
# LayerZero Scan client
|
|
104
|
+
"LayerZeroScanClient",
|
|
105
|
+
"create_layerzero_scan_client",
|
|
106
|
+
# Cross-chain payment router
|
|
107
|
+
"CrossChainPaymentRouter",
|
|
108
|
+
"create_cross_chain_payment_router",
|
|
109
|
+
# Constants
|
|
110
|
+
"LAYERZERO_ENDPOINT_IDS",
|
|
111
|
+
"USDT0_OFT_ADDRESSES",
|
|
112
|
+
"LAYERZERO_SCAN_BASE_URL",
|
|
113
|
+
"NETWORK_TO_CHAIN",
|
|
114
|
+
"CHAIN_TO_NETWORK",
|
|
115
|
+
"get_endpoint_id",
|
|
116
|
+
"get_endpoint_id_from_network",
|
|
117
|
+
"get_usdt0_oft_address",
|
|
118
|
+
"supports_bridging",
|
|
119
|
+
"get_bridgeable_chains",
|
|
120
|
+
"address_to_bytes32",
|
|
121
|
+
"bytes32_to_address",
|
|
122
|
+
# Bridge types
|
|
123
|
+
"BridgeQuoteParams",
|
|
124
|
+
"BridgeQuote",
|
|
125
|
+
"BridgeExecuteParams",
|
|
126
|
+
"BridgeResult",
|
|
127
|
+
"BridgeStatus",
|
|
128
|
+
"BridgeSigner",
|
|
129
|
+
"SendParam",
|
|
130
|
+
"MessagingFee",
|
|
131
|
+
"TransactionLog",
|
|
132
|
+
"BridgeTransactionReceipt",
|
|
133
|
+
# LayerZero Scan types
|
|
134
|
+
"LayerZeroMessage",
|
|
135
|
+
"LayerZeroMessageStatus",
|
|
136
|
+
"WaitForDeliveryOptions",
|
|
137
|
+
# Cross-chain payment types
|
|
138
|
+
"CrossChainPaymentParams",
|
|
139
|
+
"CrossChainPaymentResult",
|
|
140
|
+
]
|
t402/bridge/client.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""USDT0 Bridge Client for LayerZero OFT transfers."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .constants import (
|
|
6
|
+
DEFAULT_EXTRA_OPTIONS,
|
|
7
|
+
DEFAULT_SLIPPAGE,
|
|
8
|
+
ERC20_APPROVE_ABI,
|
|
9
|
+
ESTIMATED_BRIDGE_TIME,
|
|
10
|
+
OFT_SEND_ABI,
|
|
11
|
+
OFT_SENT_EVENT_TOPIC,
|
|
12
|
+
address_to_bytes32,
|
|
13
|
+
get_bridgeable_chains,
|
|
14
|
+
get_endpoint_id,
|
|
15
|
+
get_usdt0_oft_address,
|
|
16
|
+
supports_bridging,
|
|
17
|
+
)
|
|
18
|
+
from .types import (
|
|
19
|
+
BridgeExecuteParams,
|
|
20
|
+
BridgeQuote,
|
|
21
|
+
BridgeQuoteParams,
|
|
22
|
+
BridgeResult,
|
|
23
|
+
BridgeSigner,
|
|
24
|
+
BridgeTransactionReceipt,
|
|
25
|
+
MessagingFee,
|
|
26
|
+
SendParam,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Usdt0Bridge:
|
|
31
|
+
"""USDT0 Bridge Client for LayerZero OFT transfers.
|
|
32
|
+
|
|
33
|
+
Provides cross-chain USDT0 transfers using LayerZero OFT standard.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
```python
|
|
37
|
+
from t402.bridge import Usdt0Bridge
|
|
38
|
+
|
|
39
|
+
bridge = Usdt0Bridge(signer, 'arbitrum')
|
|
40
|
+
|
|
41
|
+
# Get quote
|
|
42
|
+
quote = await bridge.quote(BridgeQuoteParams(
|
|
43
|
+
from_chain='arbitrum',
|
|
44
|
+
to_chain='ethereum',
|
|
45
|
+
amount=100_000000, # 100 USDT0
|
|
46
|
+
recipient='0x...',
|
|
47
|
+
))
|
|
48
|
+
|
|
49
|
+
print(f"Fee: {quote.native_fee} wei")
|
|
50
|
+
|
|
51
|
+
# Execute bridge
|
|
52
|
+
result = await bridge.send(BridgeExecuteParams(
|
|
53
|
+
from_chain='arbitrum',
|
|
54
|
+
to_chain='ethereum',
|
|
55
|
+
amount=100_000000,
|
|
56
|
+
recipient='0x...',
|
|
57
|
+
))
|
|
58
|
+
|
|
59
|
+
print(f"Bridge tx: {result.tx_hash}")
|
|
60
|
+
print(f"Message GUID: {result.message_guid}")
|
|
61
|
+
```
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, signer: BridgeSigner, chain: str) -> None:
|
|
65
|
+
"""Create a new bridge client.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
signer: Wallet signer with read/write capabilities
|
|
69
|
+
chain: Source chain name (e.g., 'arbitrum', 'ethereum')
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If chain doesn't support bridging
|
|
73
|
+
"""
|
|
74
|
+
if not supports_bridging(chain):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f'Chain "{chain}" does not support USDT0 bridging. '
|
|
77
|
+
f'Supported chains: {", ".join(get_bridgeable_chains())}'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self._signer = signer
|
|
81
|
+
self._chain = chain.lower()
|
|
82
|
+
|
|
83
|
+
async def quote(self, params: BridgeQuoteParams) -> BridgeQuote:
|
|
84
|
+
"""Get a quote for bridging USDT0.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
params: Bridge parameters
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Quote with fee and amount information
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If parameters are invalid
|
|
94
|
+
"""
|
|
95
|
+
self._validate_params(params)
|
|
96
|
+
|
|
97
|
+
send_param = self._build_send_param(
|
|
98
|
+
params.to_chain, params.amount, params.recipient, DEFAULT_SLIPPAGE
|
|
99
|
+
)
|
|
100
|
+
oft_address = get_usdt0_oft_address(params.from_chain)
|
|
101
|
+
|
|
102
|
+
# Get quote from contract
|
|
103
|
+
fee = await self._signer.read_contract(
|
|
104
|
+
oft_address,
|
|
105
|
+
OFT_SEND_ABI,
|
|
106
|
+
"quoteSend",
|
|
107
|
+
self._send_param_to_tuple(send_param),
|
|
108
|
+
False,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
native_fee = fee[0] if isinstance(fee, (list, tuple)) else fee.get("nativeFee", 0)
|
|
112
|
+
|
|
113
|
+
return BridgeQuote(
|
|
114
|
+
native_fee=int(native_fee),
|
|
115
|
+
amount_to_send=params.amount,
|
|
116
|
+
min_amount_to_receive=send_param.min_amount_ld,
|
|
117
|
+
estimated_time=ESTIMATED_BRIDGE_TIME,
|
|
118
|
+
from_chain=params.from_chain,
|
|
119
|
+
to_chain=params.to_chain,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
async def send(self, params: BridgeExecuteParams) -> BridgeResult:
|
|
123
|
+
"""Execute a bridge transaction.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
params: Bridge execution parameters
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Bridge result with transaction hash and message GUID
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
ValueError: If parameters are invalid or transaction fails
|
|
133
|
+
"""
|
|
134
|
+
self._validate_params(
|
|
135
|
+
BridgeQuoteParams(
|
|
136
|
+
from_chain=params.from_chain,
|
|
137
|
+
to_chain=params.to_chain,
|
|
138
|
+
amount=params.amount,
|
|
139
|
+
recipient=params.recipient,
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
slippage = params.slippage_tolerance if params.slippage_tolerance > 0 else DEFAULT_SLIPPAGE
|
|
144
|
+
oft_address = get_usdt0_oft_address(params.from_chain)
|
|
145
|
+
send_param = self._build_send_param(
|
|
146
|
+
params.to_chain, params.amount, params.recipient, slippage
|
|
147
|
+
)
|
|
148
|
+
refund_address = params.refund_address or self._signer.address
|
|
149
|
+
|
|
150
|
+
# Get fee quote
|
|
151
|
+
fee_result = await self._signer.read_contract(
|
|
152
|
+
oft_address,
|
|
153
|
+
OFT_SEND_ABI,
|
|
154
|
+
"quoteSend",
|
|
155
|
+
self._send_param_to_tuple(send_param),
|
|
156
|
+
False,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if isinstance(fee_result, (list, tuple)):
|
|
160
|
+
native_fee = int(fee_result[0])
|
|
161
|
+
lz_token_fee = int(fee_result[1]) if len(fee_result) > 1 else 0
|
|
162
|
+
else:
|
|
163
|
+
native_fee = int(fee_result.get("nativeFee", 0))
|
|
164
|
+
lz_token_fee = int(fee_result.get("lzTokenFee", 0))
|
|
165
|
+
|
|
166
|
+
fee = MessagingFee(native_fee=native_fee, lz_token_fee=lz_token_fee)
|
|
167
|
+
|
|
168
|
+
# Check and approve allowance if needed
|
|
169
|
+
await self._ensure_allowance(oft_address, params.amount)
|
|
170
|
+
|
|
171
|
+
# Execute bridge transaction
|
|
172
|
+
tx_hash = await self._signer.write_contract(
|
|
173
|
+
oft_address,
|
|
174
|
+
OFT_SEND_ABI,
|
|
175
|
+
"send",
|
|
176
|
+
self._send_param_to_tuple(send_param),
|
|
177
|
+
(fee.native_fee, fee.lz_token_fee),
|
|
178
|
+
refund_address,
|
|
179
|
+
value=fee.native_fee,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Wait for transaction confirmation
|
|
183
|
+
receipt = await self._signer.wait_for_transaction_receipt(tx_hash)
|
|
184
|
+
|
|
185
|
+
if receipt.status != 1:
|
|
186
|
+
raise ValueError(f"Bridge transaction failed: {tx_hash}")
|
|
187
|
+
|
|
188
|
+
# Extract message GUID from OFTSent event logs
|
|
189
|
+
message_guid = self._extract_message_guid(receipt)
|
|
190
|
+
|
|
191
|
+
return BridgeResult(
|
|
192
|
+
tx_hash=tx_hash,
|
|
193
|
+
message_guid=message_guid,
|
|
194
|
+
amount_sent=params.amount,
|
|
195
|
+
amount_to_receive=send_param.min_amount_ld,
|
|
196
|
+
from_chain=params.from_chain,
|
|
197
|
+
to_chain=params.to_chain,
|
|
198
|
+
estimated_time=ESTIMATED_BRIDGE_TIME,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def get_supported_destinations(self) -> list[str]:
|
|
202
|
+
"""Get all supported destination chains from current chain.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
List of supported destination chain names
|
|
206
|
+
"""
|
|
207
|
+
return [c for c in get_bridgeable_chains() if c != self._chain]
|
|
208
|
+
|
|
209
|
+
def supports_destination(self, to_chain: str) -> bool:
|
|
210
|
+
"""Check if a destination chain is supported.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
to_chain: Destination chain name
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
True if supported
|
|
217
|
+
"""
|
|
218
|
+
return to_chain.lower() != self._chain and supports_bridging(to_chain)
|
|
219
|
+
|
|
220
|
+
def _validate_params(self, params: BridgeQuoteParams) -> None:
|
|
221
|
+
"""Validate bridge parameters."""
|
|
222
|
+
if params.from_chain.lower() != self._chain:
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f'Source chain mismatch: bridge initialized for "{self._chain}" '
|
|
225
|
+
f'but got "{params.from_chain}"'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if not supports_bridging(params.from_chain):
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f'Source chain "{params.from_chain}" does not support USDT0 bridging'
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if not supports_bridging(params.to_chain):
|
|
234
|
+
raise ValueError(
|
|
235
|
+
f'Destination chain "{params.to_chain}" does not support USDT0 bridging'
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if params.from_chain.lower() == params.to_chain.lower():
|
|
239
|
+
raise ValueError("Source and destination chains must be different")
|
|
240
|
+
|
|
241
|
+
if params.amount <= 0:
|
|
242
|
+
raise ValueError("Amount must be greater than 0")
|
|
243
|
+
|
|
244
|
+
def _build_send_param(
|
|
245
|
+
self, to_chain: str, amount: int, recipient: str, slippage: float
|
|
246
|
+
) -> SendParam:
|
|
247
|
+
"""Build the LayerZero SendParam struct."""
|
|
248
|
+
dst_eid = get_endpoint_id(to_chain)
|
|
249
|
+
if dst_eid is None:
|
|
250
|
+
raise ValueError(f"Unknown destination chain: {to_chain}")
|
|
251
|
+
|
|
252
|
+
to_bytes = address_to_bytes32(recipient)
|
|
253
|
+
|
|
254
|
+
# Calculate minimum amount with slippage
|
|
255
|
+
slippage_bps = int(slippage * 100)
|
|
256
|
+
min_amount = amount - (amount * slippage_bps) // 10000
|
|
257
|
+
|
|
258
|
+
return SendParam(
|
|
259
|
+
dst_eid=dst_eid,
|
|
260
|
+
to=to_bytes,
|
|
261
|
+
amount_ld=amount,
|
|
262
|
+
min_amount_ld=min_amount,
|
|
263
|
+
extra_options=DEFAULT_EXTRA_OPTIONS,
|
|
264
|
+
compose_msg=b"",
|
|
265
|
+
oft_cmd=b"",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def _send_param_to_tuple(self, param: SendParam) -> tuple:
|
|
269
|
+
"""Convert SendParam to tuple for contract call."""
|
|
270
|
+
return (
|
|
271
|
+
param.dst_eid,
|
|
272
|
+
param.to,
|
|
273
|
+
param.amount_ld,
|
|
274
|
+
param.min_amount_ld,
|
|
275
|
+
param.extra_options,
|
|
276
|
+
param.compose_msg,
|
|
277
|
+
param.oft_cmd,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
async def _ensure_allowance(self, oft_address: str, amount: int) -> None:
|
|
281
|
+
"""Check and approve token allowance if needed."""
|
|
282
|
+
signer_address = self._signer.address
|
|
283
|
+
|
|
284
|
+
# Check current allowance
|
|
285
|
+
allowance = await self._signer.read_contract(
|
|
286
|
+
oft_address,
|
|
287
|
+
ERC20_APPROVE_ABI,
|
|
288
|
+
"allowance",
|
|
289
|
+
signer_address,
|
|
290
|
+
oft_address,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
allowance_int = int(allowance) if not isinstance(allowance, int) else allowance
|
|
294
|
+
|
|
295
|
+
# Approve if needed
|
|
296
|
+
if allowance_int < amount:
|
|
297
|
+
await self._signer.write_contract(
|
|
298
|
+
oft_address,
|
|
299
|
+
ERC20_APPROVE_ABI,
|
|
300
|
+
"approve",
|
|
301
|
+
oft_address,
|
|
302
|
+
amount,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def _extract_message_guid(self, receipt: BridgeTransactionReceipt) -> str:
|
|
306
|
+
"""Extract LayerZero message GUID from OFTSent event logs."""
|
|
307
|
+
for log in receipt.logs:
|
|
308
|
+
if len(log.topics) >= 2 and log.topics[0].lower() == OFT_SENT_EVENT_TOPIC.lower():
|
|
309
|
+
# GUID is the first indexed parameter (topics[1])
|
|
310
|
+
return log.topics[1]
|
|
311
|
+
|
|
312
|
+
raise ValueError(
|
|
313
|
+
"Failed to extract message GUID from transaction logs. "
|
|
314
|
+
"The OFTSent event was not found in the transaction receipt."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def create_usdt0_bridge(signer: BridgeSigner, chain: str) -> Usdt0Bridge:
|
|
319
|
+
"""Create a bridge client for a specific chain.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
signer: Wallet signer
|
|
323
|
+
chain: Source chain name
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
New bridge client instance
|
|
327
|
+
"""
|
|
328
|
+
return Usdt0Bridge(signer, chain)
|