// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; interface IMintable { function mint(address to, uint256 amount) external; } interface ICWMultiTokenBridgeL1 { function lockAndSend( address canonicalToken, uint64 destinationChainSelector, address recipient, uint256 amount ) external payable returns (bytes32); function calculateFee( address canonicalToken, uint64 destinationChainSelector, address recipient, uint256 amount ) external view returns (uint256); } /** * @notice Batch mint + bridge for c* mirror mesh prep on Chain 138. * @dev Called by the operator EOA. Grant IMintable.MINTER_ROLE to this contract on each c* token. * Multicall3 cannot replace this: subcalls would see msg.sender = Multicall3, not the operator. */ contract CWMirrorMeshBatch { using SafeERC20 for IERC20; address public immutable bridge; address public immutable link; event MintMany(address indexed operator, uint256 callCount); event BridgeMany(address indexed operator, address indexed token, uint256 chunkCount, uint256 totalAmount); constructor(address bridge_, address link_) { require(bridge_ != address(0), "zero bridge"); require(link_ != address(0), "zero link"); bridge = bridge_; link = link_; } /// @dev Mint each amount to msg.sender (operator) in one transaction. function mintMany(address[] calldata tokens, uint256[] calldata amounts) external { uint256 n = tokens.length; require(n == amounts.length, "length mismatch"); for (uint256 i; i < n; ) { IMintable(tokens[i]).mint(msg.sender, amounts[i]); unchecked { ++i; } } emit MintMany(msg.sender, n); } /// @dev Mint one token repeatedly to msg.sender (same token, many chunks) in one transaction. function mintChunks(address token, uint256[] calldata amounts) external { uint256 n = amounts.length; for (uint256 i; i < n; ) { IMintable(token).mint(msg.sender, amounts[i]); unchecked { ++i; } } emit MintMany(msg.sender, n); } /** * @dev Bridge one canonical token in multiple lockAndSend chunks from msg.sender. * Pulls total token + total LINK fee upfront, then loops lockAndSend. */ function bridgeTokenChunks( address canonicalToken, uint64 destinationChainSelector, address recipient, uint256[] calldata amounts ) external payable { uint256 n = amounts.length; require(n > 0, "empty amounts"); uint256 totalAmount; uint256 totalFee; for (uint256 i; i < n; ) { totalAmount += amounts[i]; totalFee += ICWMultiTokenBridgeL1(bridge).calculateFee( canonicalToken, destinationChainSelector, recipient, amounts[i] ); unchecked { ++i; } } IERC20(canonicalToken).safeTransferFrom(msg.sender, address(this), totalAmount); IERC20(link).safeTransferFrom(msg.sender, address(this), totalFee); for (uint256 i; i < n; ) { uint256 amount = amounts[i]; uint256 fee = ICWMultiTokenBridgeL1(bridge).calculateFee( canonicalToken, destinationChainSelector, recipient, amount ); IERC20(canonicalToken).forceApprove(bridge, amount); IERC20(link).forceApprove(bridge, fee); ICWMultiTokenBridgeL1(bridge).lockAndSend( canonicalToken, destinationChainSelector, recipient, amount ); unchecked { ++i; } } emit BridgeMany(msg.sender, canonicalToken, n, totalAmount); } }