@hyperlane-xyz/multicollateral 0.1.0 → 1.0.0
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.
- package/contracts/{MultiCollateral.sol → CrossCollateralRouter.sol} +131 -46
- package/contracts/{MultiCollateralRoutingFee.sol → CrossCollateralRoutingFee.sol} +9 -6
- package/contracts/TokenBridgeOft.sol +338 -0
- package/contracts/interfaces/{IMultiCollateralFee.sol → ICrossCollateralFee.sol} +1 -1
- package/contracts/interfaces/layerzero/IOFT.sol +85 -0
- package/dist/typechain/{MultiCollateral.d.ts → CrossCollateralRouter.d.ts} +89 -79
- package/dist/typechain/CrossCollateralRouter.d.ts.map +1 -0
- package/dist/typechain/CrossCollateralRouter.js +2 -0
- package/dist/typechain/CrossCollateralRouter.js.map +1 -0
- package/dist/typechain/{MultiCollateralRoutingFee.d.ts → CrossCollateralRoutingFee.d.ts} +13 -13
- package/dist/typechain/{MultiCollateralRoutingFee.d.ts.map → CrossCollateralRoutingFee.d.ts.map} +1 -1
- package/dist/typechain/CrossCollateralRoutingFee.js +2 -0
- package/dist/typechain/CrossCollateralRoutingFee.js.map +1 -0
- package/dist/typechain/TokenBridgeOft.d.ts +293 -0
- package/dist/typechain/TokenBridgeOft.d.ts.map +1 -0
- package/dist/typechain/TokenBridgeOft.js +2 -0
- package/dist/typechain/TokenBridgeOft.js.map +1 -0
- package/dist/typechain/factories/{MultiCollateral__factory.d.ts → CrossCollateralRouter__factory.d.ts} +97 -87
- package/dist/typechain/factories/{MultiCollateral__factory.d.ts.map → CrossCollateralRouter__factory.d.ts.map} +1 -1
- package/dist/typechain/factories/CrossCollateralRouter__factory.js +1868 -0
- package/dist/typechain/factories/CrossCollateralRouter__factory.js.map +1 -0
- package/dist/typechain/factories/{MultiCollateralRoutingFee__factory.d.ts → CrossCollateralRoutingFee__factory.d.ts} +12 -12
- package/dist/typechain/factories/{MultiCollateralRoutingFee__factory.d.ts.map → CrossCollateralRoutingFee__factory.d.ts.map} +1 -1
- package/dist/typechain/factories/{MultiCollateralRoutingFee__factory.js → CrossCollateralRoutingFee__factory.js} +6 -6
- package/dist/typechain/factories/{MultiCollateralRoutingFee__factory.js.map → CrossCollateralRoutingFee__factory.js.map} +1 -1
- package/dist/typechain/factories/TokenBridgeOft__factory.d.ts +312 -0
- package/dist/typechain/factories/TokenBridgeOft__factory.d.ts.map +1 -0
- package/dist/typechain/factories/TokenBridgeOft__factory.js +417 -0
- package/dist/typechain/factories/TokenBridgeOft__factory.js.map +1 -0
- package/dist/typechain/factories/index.d.ts +3 -2
- package/dist/typechain/factories/index.d.ts.map +1 -1
- package/dist/typechain/factories/index.js +3 -2
- package/dist/typechain/factories/index.js.map +1 -1
- package/dist/typechain/index.d.ts +6 -4
- package/dist/typechain/index.d.ts.map +1 -1
- package/dist/typechain/index.js +3 -2
- package/dist/typechain/index.js.map +1 -1
- package/package.json +7 -6
- package/dist/typechain/MultiCollateral.d.ts.map +0 -1
- package/dist/typechain/MultiCollateral.js +0 -2
- package/dist/typechain/MultiCollateral.js.map +0 -1
- package/dist/typechain/MultiCollateralRoutingFee.js +0 -2
- package/dist/typechain/MultiCollateralRoutingFee.js.map +0 -1
- package/dist/typechain/factories/MultiCollateral__factory.js +0 -1855
- package/dist/typechain/factories/MultiCollateral__factory.js.map +0 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
2
|
+
pragma solidity >=0.8.0;
|
|
3
|
+
|
|
4
|
+
import {ITokenBridge, ITokenFee, Quote} from "@hyperlane-xyz/core/interfaces/ITokenBridge.sol";
|
|
5
|
+
import {IOFT, SendParam, MessagingFee, MessagingReceipt, OFTReceipt} from "./interfaces/layerzero/IOFT.sol";
|
|
6
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
8
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
9
|
+
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
|
|
10
|
+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
11
|
+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
12
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
13
|
+
import {PackageVersioned} from "@hyperlane-xyz/core/PackageVersioned.sol";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @title TokenBridgeOft
|
|
17
|
+
* @notice Warp route adapter for LayerZero OFT (Omnichain Fungible Token) contracts.
|
|
18
|
+
*
|
|
19
|
+
* @dev This contract implements ITokenBridge directly with Ownable for admin.
|
|
20
|
+
* It does NOT use Hyperlane messaging for transfers. Instead, transferRemote bridges
|
|
21
|
+
* via OFT.send() and there is no inbound message handling.
|
|
22
|
+
*
|
|
23
|
+
* @dev Dust and decimal handling:
|
|
24
|
+
* OFTs use "sharedDecimals" (typically 6) as a wire format, regardless of the
|
|
25
|
+
* token's local decimals. When localDecimals > sharedDecimals, the OFT truncates
|
|
26
|
+
* sub-sharedDecimals precision ("dust") via _removeDust() before sending. This
|
|
27
|
+
* contract stores the decimalConversionRate (10^(localDecimals - sharedDecimals))
|
|
28
|
+
* as an immutable and uses it to:
|
|
29
|
+
* 1. Round grossAmount UP to the next dust-free boundary after fee inversion,
|
|
30
|
+
* preventing SlippageExceeded reverts from the OFT.
|
|
31
|
+
* 2. Round minAmountLD DOWN to a dust-free value, since the OFT cannot deliver
|
|
32
|
+
* sub-dust precision anyway.
|
|
33
|
+
*
|
|
34
|
+
* Supports all OFT patterns:
|
|
35
|
+
* - Native OFT (burn/mint, approvalRequired=false)
|
|
36
|
+
* - OFTAdapter (lock/unlock, approvalRequired=true)
|
|
37
|
+
* - OFTWrapper (Paxos-style burn/mint, approvalRequired=false)
|
|
38
|
+
*
|
|
39
|
+
* Token support:
|
|
40
|
+
* - Fee-on-transfer tokens: NOT supported — amount mismatches between
|
|
41
|
+
* safeTransferFrom and OFT.send will cause failures or loss.
|
|
42
|
+
* - Rebasing tokens: NOT supported — amounts may diverge across chains.
|
|
43
|
+
* - ERC-777: NOT explicitly supported — hook reentrancy not guarded.
|
|
44
|
+
*/
|
|
45
|
+
contract TokenBridgeOft is ITokenBridge, Ownable, PackageVersioned {
|
|
46
|
+
using SafeERC20 for IERC20;
|
|
47
|
+
using EnumerableMap for EnumerableMap.UintToUintMap;
|
|
48
|
+
|
|
49
|
+
// ============ Errors ============
|
|
50
|
+
|
|
51
|
+
error LzEidNotConfigured(uint32 hyperlaneDomain);
|
|
52
|
+
|
|
53
|
+
// ============ Events ============
|
|
54
|
+
|
|
55
|
+
event SentTransferRemote(
|
|
56
|
+
uint32 indexed destination,
|
|
57
|
+
bytes32 indexed recipient,
|
|
58
|
+
uint256 amount
|
|
59
|
+
);
|
|
60
|
+
event DomainAdded(uint32 indexed hyperlaneDomain, uint32 lzEid);
|
|
61
|
+
event DomainRemoved(uint32 indexed hyperlaneDomain);
|
|
62
|
+
event ExtraOptionsSet(bytes extraOptions);
|
|
63
|
+
|
|
64
|
+
// ============ Storage ============
|
|
65
|
+
|
|
66
|
+
/// @notice The LayerZero OFT contract to bridge through
|
|
67
|
+
IOFT public immutable oft;
|
|
68
|
+
|
|
69
|
+
/// @notice The underlying ERC20 token
|
|
70
|
+
IERC20 public immutable wrappedToken;
|
|
71
|
+
|
|
72
|
+
/// @notice 10^(localDecimals - sharedDecimals). Amounts are only meaningful
|
|
73
|
+
/// at multiples of this value; sub-dust precision is truncated by the OFT.
|
|
74
|
+
/// Equals 1 when localDecimals == sharedDecimals (no dust).
|
|
75
|
+
uint256 public immutable decimalConversionRate;
|
|
76
|
+
|
|
77
|
+
/// @notice Enumerable mapping from Hyperlane domain ID to LayerZero endpoint ID
|
|
78
|
+
EnumerableMap.UintToUintMap private _domainToLzEid;
|
|
79
|
+
|
|
80
|
+
/// @notice Configurable LayerZero extra options (e.g., destination gas limits)
|
|
81
|
+
bytes public extraOptions;
|
|
82
|
+
|
|
83
|
+
// ============ Constructor ============
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param _oft Address of the OFT / OFTAdapter / OFTWrapper contract
|
|
87
|
+
* @param _owner Initial owner of the contract
|
|
88
|
+
*/
|
|
89
|
+
constructor(address _oft, address _owner) {
|
|
90
|
+
require(_oft != address(0), "TokenBridgeOft: zero OFT address");
|
|
91
|
+
|
|
92
|
+
oft = IOFT(_oft);
|
|
93
|
+
address _token = IOFT(_oft).token();
|
|
94
|
+
wrappedToken = IERC20(_token);
|
|
95
|
+
|
|
96
|
+
uint8 localDecimals = IERC20Metadata(_token).decimals();
|
|
97
|
+
uint8 sharedDecimals = IOFT(_oft).sharedDecimals();
|
|
98
|
+
require(
|
|
99
|
+
localDecimals >= sharedDecimals,
|
|
100
|
+
"TokenBridgeOft: localDecimals < sharedDecimals"
|
|
101
|
+
);
|
|
102
|
+
decimalConversionRate = 10 ** (localDecimals - sharedDecimals);
|
|
103
|
+
|
|
104
|
+
_transferOwnership(_owner);
|
|
105
|
+
wrappedToken.safeApprove(address(oft), type(uint256).max);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============ ITokenBridge ============
|
|
109
|
+
|
|
110
|
+
/// @notice Returns the address of the underlying ERC20 token.
|
|
111
|
+
function token() public view returns (address) {
|
|
112
|
+
return address(wrappedToken);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// @inheritdoc ITokenFee
|
|
116
|
+
function quoteTransferRemote(
|
|
117
|
+
uint32 _destination,
|
|
118
|
+
bytes32 _recipient,
|
|
119
|
+
uint256 _amount
|
|
120
|
+
) external view override returns (Quote[] memory quotes) {
|
|
121
|
+
uint256 nativeFee = _quoteGasPayment(_destination, _recipient, _amount);
|
|
122
|
+
uint256 externalFee = _externalFeeAmount(
|
|
123
|
+
_destination,
|
|
124
|
+
_recipient,
|
|
125
|
+
_amount
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
quotes = new Quote[](2);
|
|
129
|
+
quotes[0] = Quote({token: address(0), amount: nativeFee});
|
|
130
|
+
quotes[1] = Quote({
|
|
131
|
+
token: address(wrappedToken),
|
|
132
|
+
amount: _amount + externalFee
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// @inheritdoc ITokenBridge
|
|
137
|
+
function transferRemote(
|
|
138
|
+
uint32 _destination,
|
|
139
|
+
bytes32 _recipient,
|
|
140
|
+
uint256 _amount
|
|
141
|
+
) external payable override returns (bytes32 messageId) {
|
|
142
|
+
// 1. Calculate OFT fee and pull total from sender
|
|
143
|
+
uint256 externalFee = _externalFeeAmount(
|
|
144
|
+
_destination,
|
|
145
|
+
_recipient,
|
|
146
|
+
_amount
|
|
147
|
+
);
|
|
148
|
+
uint256 grossAmount = _amount + externalFee;
|
|
149
|
+
wrappedToken.safeTransferFrom(msg.sender, address(this), grossAmount);
|
|
150
|
+
|
|
151
|
+
// 2. Build OFT send params: send grossAmount, enforce dust-free _amount as minimum received.
|
|
152
|
+
SendParam memory sendParam = _buildSendParam(
|
|
153
|
+
_destination,
|
|
154
|
+
_recipient,
|
|
155
|
+
grossAmount,
|
|
156
|
+
_removeDust(_amount)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// 3. Quote native gas fee and send via OFT
|
|
160
|
+
uint256 nativeFee = _quoteGasPayment(_destination, _recipient, _amount);
|
|
161
|
+
|
|
162
|
+
MessagingFee memory msgFee = MessagingFee({
|
|
163
|
+
nativeFee: nativeFee,
|
|
164
|
+
lzTokenFee: 0
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
emit SentTransferRemote(_destination, _recipient, _amount);
|
|
168
|
+
|
|
169
|
+
(MessagingReceipt memory msgReceipt, ) = oft.send{value: nativeFee}(
|
|
170
|
+
sendParam,
|
|
171
|
+
msgFee,
|
|
172
|
+
msg.sender
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// 4. Refund excess native value back to caller
|
|
176
|
+
uint256 excessNative = msg.value - nativeFee;
|
|
177
|
+
if (excessNative > 0) {
|
|
178
|
+
Address.sendValue(payable(msg.sender), excessNative);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return msgReceipt.guid;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ============ Admin ============
|
|
185
|
+
|
|
186
|
+
function addDomain(
|
|
187
|
+
uint32 _hyperlaneDomain,
|
|
188
|
+
uint32 _lzEid
|
|
189
|
+
) external onlyOwner {
|
|
190
|
+
require(_lzEid != 0, "TokenBridgeOft: zero LZ EID");
|
|
191
|
+
_domainToLzEid.set(uint256(_hyperlaneDomain), uint256(_lzEid));
|
|
192
|
+
emit DomainAdded(_hyperlaneDomain, _lzEid);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function removeDomain(uint32 _hyperlaneDomain) external onlyOwner {
|
|
196
|
+
bool removed = _domainToLzEid.remove(uint256(_hyperlaneDomain));
|
|
197
|
+
require(removed, "TokenBridgeOft: domain not configured");
|
|
198
|
+
emit DomainRemoved(_hyperlaneDomain);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function setExtraOptions(bytes calldata _options) external onlyOwner {
|
|
202
|
+
extraOptions = _options;
|
|
203
|
+
emit ExtraOptionsSet(_options);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============ Views ============
|
|
207
|
+
|
|
208
|
+
/// @notice Look up the LZ endpoint ID for a given Hyperlane domain.
|
|
209
|
+
function hyperlaneDomainToLzEid(
|
|
210
|
+
uint32 _domain
|
|
211
|
+
) external view returns (uint32) {
|
|
212
|
+
return _getLzEid(_domain);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// @notice Returns all configured domain mappings as parallel arrays.
|
|
216
|
+
function getDomainMappings()
|
|
217
|
+
external
|
|
218
|
+
view
|
|
219
|
+
returns (uint32[] memory domains, uint32[] memory lzEids)
|
|
220
|
+
{
|
|
221
|
+
uint256 len = _domainToLzEid.length();
|
|
222
|
+
domains = new uint32[](len);
|
|
223
|
+
lzEids = new uint32[](len);
|
|
224
|
+
for (uint256 i = 0; i < len; i++) {
|
|
225
|
+
(uint256 domain, uint256 eid) = _domainToLzEid.at(i);
|
|
226
|
+
domains[i] = uint32(domain);
|
|
227
|
+
lzEids[i] = uint32(eid);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ============ Internal ============
|
|
232
|
+
|
|
233
|
+
function _getLzEid(uint32 _domain) internal view returns (uint32) {
|
|
234
|
+
(bool exists, uint256 eid) = _domainToLzEid.tryGet(uint256(_domain));
|
|
235
|
+
if (!exists) revert LzEidNotConfigured(_domain);
|
|
236
|
+
return uint32(eid);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function _buildSendParam(
|
|
240
|
+
uint32 _destination,
|
|
241
|
+
bytes32 _recipient,
|
|
242
|
+
uint256 _amount,
|
|
243
|
+
uint256 _minAmountLD
|
|
244
|
+
) internal view returns (SendParam memory) {
|
|
245
|
+
return
|
|
246
|
+
SendParam({
|
|
247
|
+
dstEid: _getLzEid(_destination),
|
|
248
|
+
to: _recipient,
|
|
249
|
+
amountLD: _amount,
|
|
250
|
+
minAmountLD: _minAmountLD,
|
|
251
|
+
extraOptions: extraOptions,
|
|
252
|
+
composeMsg: "",
|
|
253
|
+
oftCmd: ""
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// @dev Return the LZ native fee for sending via OFT.
|
|
258
|
+
function _quoteGasPayment(
|
|
259
|
+
uint32 _destination,
|
|
260
|
+
bytes32 _recipient,
|
|
261
|
+
uint256 _amount
|
|
262
|
+
) internal view returns (uint256) {
|
|
263
|
+
SendParam memory sendParam = _buildSendParam(
|
|
264
|
+
_destination,
|
|
265
|
+
_recipient,
|
|
266
|
+
_amount,
|
|
267
|
+
0
|
|
268
|
+
);
|
|
269
|
+
MessagingFee memory msgFee = oft.quoteSend(sendParam, false);
|
|
270
|
+
return msgFee.nativeFee;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// @dev Return the OFT token fee (difference between gross input and net output).
|
|
274
|
+
function _externalFeeAmount(
|
|
275
|
+
uint32 _destination,
|
|
276
|
+
bytes32 _recipient,
|
|
277
|
+
uint256 _amount
|
|
278
|
+
) internal view returns (uint256) {
|
|
279
|
+
return _grossOftAmount(_destination, _recipient, _amount) - _amount;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @dev Analytically invert the OFT fee to compute the gross input amount
|
|
284
|
+
* such that the recipient receives at least _amount after OFT deductions.
|
|
285
|
+
*
|
|
286
|
+
* OFT fees are linear (percentage-based), so the inversion is:
|
|
287
|
+
* grossAmount = ceil(_amount * amountSentLD / amountReceivedLD)
|
|
288
|
+
*
|
|
289
|
+
* After inversion, grossAmount is rounded UP to the next dust-free boundary.
|
|
290
|
+
* This is necessary because OFTs internally call _removeDust() which truncates
|
|
291
|
+
* sub-sharedDecimals precision. Without this rounding, the truncated gross
|
|
292
|
+
* amount after fee deduction can fall below _amount, causing SlippageExceeded.
|
|
293
|
+
*
|
|
294
|
+
* Example with 18 local / 6 shared decimals and 1% fee:
|
|
295
|
+
* _amount = 1e18, probe gives sent=1e18, received=0.99e18
|
|
296
|
+
* ceilDiv → 1010101010101010102 (has dust in last 12 digits)
|
|
297
|
+
* OFT would truncate to 1010101000000000000, then deduct 1% → 0.99999999e18 < 1e18
|
|
298
|
+
* Rounding up to 1010102000000000000 ensures post-fee amount >= 1e18
|
|
299
|
+
*
|
|
300
|
+
* If the OFT charges no fee (amountSentLD == amountReceivedLD), returns _amount
|
|
301
|
+
* rounded up to the nearest dust-free value (to handle dusty input amounts).
|
|
302
|
+
*/
|
|
303
|
+
function _grossOftAmount(
|
|
304
|
+
uint32 _destination,
|
|
305
|
+
bytes32 _recipient,
|
|
306
|
+
uint256 _amount
|
|
307
|
+
) internal view returns (uint256) {
|
|
308
|
+
SendParam memory probeParam = _buildSendParam(
|
|
309
|
+
_destination,
|
|
310
|
+
_recipient,
|
|
311
|
+
_amount,
|
|
312
|
+
0
|
|
313
|
+
);
|
|
314
|
+
(, , OFTReceipt memory receipt) = oft.quoteOFT(probeParam);
|
|
315
|
+
|
|
316
|
+
uint256 sent = receipt.amountSentLD;
|
|
317
|
+
uint256 received = receipt.amountReceivedLD;
|
|
318
|
+
|
|
319
|
+
// No fee (or zero amount): return _amount rounded up to dust-free boundary
|
|
320
|
+
if (sent == received) return _roundUpDust(_amount);
|
|
321
|
+
|
|
322
|
+
// Analytical inversion, then round up to dust-free boundary
|
|
323
|
+
// If received == 0 (100% fee), Math.mulDiv reverts with division by zero
|
|
324
|
+
uint256 gross = Math.mulDiv(_amount, sent, received, Math.Rounding.Up);
|
|
325
|
+
return _roundUpDust(gross);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// @dev Round `_amount` DOWN to the nearest dust-free value (mirrors OFT._removeDust).
|
|
329
|
+
function _removeDust(uint256 _amount) internal view returns (uint256) {
|
|
330
|
+
return (_amount / decimalConversionRate) * decimalConversionRate;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// @dev Round `_amount` UP to the nearest dust-free value.
|
|
334
|
+
function _roundUpDust(uint256 _amount) internal view returns (uint256) {
|
|
335
|
+
uint256 rate = decimalConversionRate;
|
|
336
|
+
return ((_amount + rate - 1) / rate) * rate;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
2
|
+
pragma solidity >=0.8.0;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title IOFT
|
|
6
|
+
* @notice Minimal interface for LayerZero OFT (Omnichain Fungible Token) contracts.
|
|
7
|
+
* @dev See https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/interfaces/IOFT.sol
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
struct SendParam {
|
|
11
|
+
uint32 dstEid; // Destination LayerZero endpoint ID
|
|
12
|
+
bytes32 to; // Recipient address
|
|
13
|
+
uint256 amountLD; // Amount in local decimals
|
|
14
|
+
uint256 minAmountLD; // Minimum amount accepted (slippage protection)
|
|
15
|
+
bytes extraOptions; // Additional LayerZero messaging options
|
|
16
|
+
bytes composeMsg; // Optional composed message
|
|
17
|
+
bytes oftCmd; // OFT command (unused in default implementations)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct MessagingFee {
|
|
21
|
+
uint256 nativeFee; // Gas fee in native token
|
|
22
|
+
uint256 lzTokenFee; // Optional fee in ZRO token
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
struct MessagingReceipt {
|
|
26
|
+
bytes32 guid; // Globally unique message identifier
|
|
27
|
+
uint64 nonce; // Message nonce
|
|
28
|
+
MessagingFee fee; // Actual fee paid
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
struct OFTLimit {
|
|
32
|
+
uint256 minAmountLD;
|
|
33
|
+
uint256 maxAmountLD;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
struct OFTFeeDetail {
|
|
37
|
+
int256 feeAmountLD; // Fee in local decimals (token-denominated)
|
|
38
|
+
string description;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
struct OFTReceipt {
|
|
42
|
+
uint256 amountSentLD; // Amount debited from sender
|
|
43
|
+
uint256 amountReceivedLD; // Amount credited on destination
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface IOFT {
|
|
47
|
+
function oftVersion()
|
|
48
|
+
external
|
|
49
|
+
view
|
|
50
|
+
returns (bytes4 interfaceId, uint64 version);
|
|
51
|
+
|
|
52
|
+
function token() external view returns (address);
|
|
53
|
+
|
|
54
|
+
function approvalRequired() external view returns (bool);
|
|
55
|
+
|
|
56
|
+
function sharedDecimals() external view returns (uint8);
|
|
57
|
+
|
|
58
|
+
function quoteOFT(
|
|
59
|
+
SendParam calldata _sendParam
|
|
60
|
+
)
|
|
61
|
+
external
|
|
62
|
+
view
|
|
63
|
+
returns (
|
|
64
|
+
OFTLimit memory limit,
|
|
65
|
+
OFTFeeDetail[] memory oftFeeDetails,
|
|
66
|
+
OFTReceipt memory receipt
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
function quoteSend(
|
|
70
|
+
SendParam calldata _sendParam,
|
|
71
|
+
bool _payInLzToken
|
|
72
|
+
) external view returns (MessagingFee memory msgFee);
|
|
73
|
+
|
|
74
|
+
function send(
|
|
75
|
+
SendParam calldata _sendParam,
|
|
76
|
+
MessagingFee calldata _fee,
|
|
77
|
+
address _refundAddress
|
|
78
|
+
)
|
|
79
|
+
external
|
|
80
|
+
payable
|
|
81
|
+
returns (
|
|
82
|
+
MessagingReceipt memory msgReceipt,
|
|
83
|
+
OFTReceipt memory oftReceipt
|
|
84
|
+
);
|
|
85
|
+
}
|