// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "./RouteTypesV2.sol"; import "./interfaces/IEnhancedSwapRouterV2.sol"; import "./interfaces/IBridgeIntentExecutor.sol"; contract IntentBridgeCoordinatorV2 is AccessControl, ReentrancyGuard { using SafeERC20 for IERC20; bytes32 public constant EXECUTOR_MANAGER_ROLE = keccak256("EXECUTOR_MANAGER_ROLE"); IEnhancedSwapRouterV2 public immutable swapRouter; mapping(bytes32 => address) public bridgeExecutors; event BridgeExecutorSet(bytes32 indexed bridgeType, address indexed executor); event IntentExecuted( bytes32 indexed intentHash, bytes32 indexed bridgeReference, bytes32 indexed destinationPlanHash, address inputToken, address bridgeToken, uint256 amountIn, uint256 bridgedAmount, address recipient ); error ZeroAddress(); error InvalidIntent(); error BridgeExecutorNotConfigured(); error BridgeValidationFailed(string reason); constructor(address _swapRouter) { if (_swapRouter == address(0)) revert ZeroAddress(); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(EXECUTOR_MANAGER_ROLE, msg.sender); swapRouter = IEnhancedSwapRouterV2(_swapRouter); } function setBridgeExecutor( bytes32 bridgeType, address executor ) external onlyRole(EXECUTOR_MANAGER_ROLE) { if (executor == address(0)) revert ZeroAddress(); bridgeExecutors[bridgeType] = executor; emit BridgeExecutorSet(bridgeType, executor); } function executeIntent( RouteTypesV2.BridgeIntentPlan calldata intent ) external payable nonReentrant returns (bytes32 bridgeReference, bytes32 destinationPlanHash) { if (intent.deadline < block.timestamp) revert InvalidIntent(); if (intent.recipient == address(0)) revert InvalidIntent(); if (intent.sourcePlan.deadline < block.timestamp) revert InvalidIntent(); if (intent.sourcePlan.recipient != address(this)) revert InvalidIntent(); address bridgeExecutor = bridgeExecutors[intent.bridgeType]; if (bridgeExecutor == address(0)) revert BridgeExecutorNotConfigured(); uint256 bridgedAmount; address bridgeToken; if (intent.sourcePlan.inputToken == address(0)) { bridgedAmount = swapRouter.executeRoute{value: intent.sourcePlan.amountIn}(intent.sourcePlan); bridgeToken = intent.sourcePlan.outputToken; } else { IERC20(intent.sourcePlan.inputToken).safeTransferFrom( msg.sender, address(this), intent.sourcePlan.amountIn ); IERC20(intent.sourcePlan.inputToken).forceApprove(address(swapRouter), 0); IERC20(intent.sourcePlan.inputToken).forceApprove(address(swapRouter), intent.sourcePlan.amountIn); bridgedAmount = swapRouter.executeRoute(intent.sourcePlan); bridgeToken = intent.sourcePlan.outputToken; } (bool ok, string memory reason) = IBridgeIntentExecutor(bridgeExecutor).validateBridge( intent.bridgeType, intent.bridgeData, bridgeToken, bridgedAmount, intent.recipient ); if (!ok) revert BridgeValidationFailed(reason); IERC20(bridgeToken).forceApprove(bridgeExecutor, 0); IERC20(bridgeToken).forceApprove(bridgeExecutor, bridgedAmount); bridgeReference = IBridgeIntentExecutor(bridgeExecutor).executeBridge( intent.bridgeType, intent.bridgeData, bridgeToken, bridgedAmount, intent.recipient ); destinationPlanHash = keccak256( abi.encode( intent.destinationPlan.chainId, intent.destinationPlan.inputToken, intent.destinationPlan.outputToken, intent.destinationPlan.amountIn, intent.destinationPlan.minAmountOut, intent.destinationPlan.recipient, intent.destinationPlan.deadline, intent.destinationPlan.legs ) ); emit IntentExecuted( keccak256(abi.encode(intent.bridgeType, intent.recipient, intent.deadline)), bridgeReference, destinationPlanHash, intent.sourcePlan.inputToken == address(0) ? address(0) : intent.sourcePlan.inputToken, bridgeToken, intent.sourcePlan.amountIn, bridgedAmount, intent.recipient ); } receive() external payable {} }