import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
import { IOFT, SendParam, MessagingFee } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
contract Asset0Sender {
using OptionsBuilder for bytes;
// The Asset0 token contract on this chain
IOFT public asset0Token;
// The address of the MultiHopComposer on the Hub Chain
address public composerOnHub;
// The Endpoint ID of the Hub Chain
uint32 public hubEid;
constructor(address _token, address _composer, uint32 _hubEid) {
asset0Token = IOFT(_token);
composerOnHub = _composer;
hubEid = _hubEid;
}
function sendCrossMesh(
uint32 _finalDstEid,
address _finalRecipient,
uint256 _amount,
uint128 _nextHopNativeFee
) external payable {
// 1. PREPARE SECOND HOP (Hub -> Destination)
// Options for the final delivery (Executor gas on destination)
bytes memory nextHopOptions = OptionsBuilder.newOptions()
.addExecutorLzReceiveOption(200000, 0);
SendParam memory nextHopParam = SendParam({
dstEid: _finalDstEid,
to: bytes32(uint256(uint160(_finalRecipient))),
amountLD: _amount,
minAmountLD: 0, // Composer handles amount adjustment
extraOptions: nextHopOptions,
composeMsg: "",
oftCmd: ""
});
// 2. PREPARE FIRST HOP (Source -> Hub)
// Encode the second hop instructions as the composeMsg
// The OFT will wrap this with (nonce, srcEid, amountLD, composeFrom) before delivery
// The MultiHopComposer extracts it via OFTComposeMsgCodec.composeMsg()
bytes memory composeMsg = abi.encode(nextHopParam);
// Options for the Hub: BOTH lzReceive AND lzCompose gas are required
// - lzReceive: Hub OFT credits tokens and calls endpoint.sendCompose()
// - lzCompose: MultiHopComposer executes and calls OFT.send() for second hop
bytes memory firstHopOptions = OptionsBuilder.newOptions()
.addExecutorLzReceiveOption(65_000, 0)
.addExecutorLzComposeOption(0, 500_000, _nextHopNativeFee);
SendParam memory sendParam = SendParam({
dstEid: hubEid,
to: bytes32(uint256(uint160(composerOnHub))),
amountLD: _amount,
minAmountLD: _amount, // Normal slippage for first hop
extraOptions: firstHopOptions,
composeMsg: composeMsg,
oftCmd: ""
});
// 3. QUOTE AND SEND
MessagingFee memory fee = asset0Token.quoteSend(sendParam, false);
// Ensure enough value was sent
require(msg.value >= fee.nativeFee, "Insufficient fee");
// Send
asset0Token.send{value: fee.nativeFee}(
sendParam,
fee,
msg.sender // Refund address
);
}
}