Files
smom-dbis-138/contracts/bridge/trustless/SwapBridgeSwapCoordinator.sol
2026-03-02 12:14:09 -08:00

113 lines
4.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../UniversalCCIPBridge.sol";
import "./EnhancedSwapRouter.sol";
/**
* @title SwapBridgeSwapCoordinator
* @notice Coordinates source-chain swap (token A -> bridgeable token) then CCIP bridge in one flow
* @dev User approves coordinator for sourceToken; coordinator swaps via EnhancedSwapRouter (Dodoex) then calls UniversalCCIPBridge
*/
contract SwapBridgeSwapCoordinator is ReentrancyGuard {
using SafeERC20 for IERC20;
EnhancedSwapRouter public immutable swapRouter;
UniversalCCIPBridge public immutable bridge;
event SwapAndBridgeExecuted(
address indexed sourceToken,
address indexed bridgeableToken,
uint256 amountIn,
uint256 amountBridged,
uint64 destinationChain,
address indexed recipient,
bytes32 messageId
);
error ZeroAddress();
error ZeroAmount();
error InsufficientOutput();
error SameToken();
constructor(address _swapRouter, address _bridge) {
if (_swapRouter == address(0) || _bridge == address(0)) revert ZeroAddress();
swapRouter = EnhancedSwapRouter(payable(_swapRouter));
bridge = UniversalCCIPBridge(payable(_bridge));
}
/**
* @notice Swap source token to bridgeable token then bridge to destination chain
* @param sourceToken Token user is sending (will be swapped if different from bridgeableToken)
* @param amountIn Amount of source token
* @param amountOutMin Minimum bridgeable token from swap (slippage protection; ignored if sourceToken == bridgeableToken)
* @param bridgeableToken Token to bridge (WETH or stablecoin); must be registered on bridge
* @param destinationChainSelector CCIP destination chain selector
* @param recipient Recipient on destination chain
* @param assetType Asset type hash for bridge (from UniversalAssetRegistry)
* @param usePMM Whether bridge should use PMM liquidity
* @param useVault Whether bridge should use vault
*/
function swapAndBridge(
address sourceToken,
uint256 amountIn,
uint256 amountOutMin,
address bridgeableToken,
uint64 destinationChainSelector,
address recipient,
bytes32 assetType,
bool usePMM,
bool useVault
) external payable nonReentrant returns (bytes32 messageId) {
if (amountIn == 0) revert ZeroAmount();
if (sourceToken == address(0) || bridgeableToken == address(0) || recipient == address(0)) revert ZeroAddress();
uint256 amountToBridge;
if (sourceToken == bridgeableToken) {
IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), amountIn);
amountToBridge = amountIn;
} else {
IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), amountIn);
IERC20(sourceToken).approve(address(swapRouter), amountIn);
amountToBridge = swapRouter.swapTokenToToken(sourceToken, bridgeableToken, amountIn, amountOutMin);
if (amountToBridge < amountOutMin) revert InsufficientOutput();
}
UniversalCCIPBridge.BridgeOperation memory op = UniversalCCIPBridge.BridgeOperation({
token: bridgeableToken,
amount: amountToBridge,
destinationChain: destinationChainSelector,
recipient: recipient,
assetType: assetType,
usePMM: usePMM,
useVault: useVault,
complianceProof: "",
vaultInstructions: ""
});
IERC20(bridgeableToken).approve(address(bridge), amountToBridge);
(bool ok, bytes memory result) = address(bridge).call{value: msg.value}(
abi.encodeWithSelector(bridge.bridge.selector, op)
);
require(ok, "SwapBridgeSwapCoordinator: bridge failed");
messageId = abi.decode(result, (bytes32));
emit SwapAndBridgeExecuted(
sourceToken,
bridgeableToken,
amountIn,
amountToBridge,
destinationChainSelector,
recipient,
messageId
);
return messageId;
}
receive() external payable {}
}