BackrunExecutor

Description:

Decentralized Finance (DeFi) protocol contract providing Swap, Factory functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/BackrunExecutor.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title BackrunExecutor — sell-all executor supporting V2/V3/Universal Router & major aggregators (1inch, 0x, Paraswap, Odos, OpenOcean, Kyber, CoW, OKX)
/// @notice 사용법: 프런트런 수신자를 이 컨트랙트로 지정하고, 백런 시 아래 sellAll* 함수들 중 라우팅에 맞는 것을 호출합니다.
///         컨트랙트는 보유 잔액만큼만 approve 후 스왑하고, 호출 뒤 승인 0으로 회수합니다.

/* ========================= Interfaces ========================= */
interface IERC20 {
    function balanceOf(address) external view returns (uint256);
    function approve(address, uint256) external returns (bool);
    function transfer(address, uint256) external returns (bool);
}

interface IWETH is IERC20 { function deposit() external payable; function withdraw(uint256) external; }

// Uniswap V2 Router (Supporting FeeOnTransfer)
interface IUniV2Router {
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}

// Uniswap V3 SwapRouter02
interface ISwapRouter02 {
    struct ExactInputParams { bytes path; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; }
    function exactInput(ExactInputParams calldata) external payable returns (uint256);
}

// Universal Router (오버로드 분리용 인터페이스)
interface IURWithDeadline { function execute(bytes calldata, bytes[] calldata, uint256) external payable; }
interface IURNoDeadline { function execute(bytes calldata, bytes[] calldata) external payable; }

/* ========================= Contract ========================= */
contract BackrunExecutor {
    /* ---- addresses ---- */
    address public immutable WETH;
    address public immutable uniV2Router;        // e.g. 0x7a25...
    address public immutable uniV3Router02;      // e.g. 0x68b3...
    address public immutable universalRouter;    // e.g. 0xEF1c...
    // aggregators
    address public immutable oneInchRouter;
    address public immutable oneInchSpender;
    address public immutable zeroExProxy;
    address public immutable zeroExAllowanceTarget; // optional
    address public immutable paraswapAugustus;
    address public immutable paraswapTokenTransferProxy; // optional
    address public immutable odosRouter;
    address public immutable kyberMetaAgg;
    address public immutable openOceanRouter;
    address public immutable cowSettlement;
    address public immutable okxDexRouter;
    address public immutable okxApproveProxy; // optional

    /* ---- access control / guards ---- */
    address public owner;
    address public bundler; // 봇 주소(선택)
    uint256 private _locked; // reentrancy guard

    modifier onlyAuthorized() {
        require(msg.sender == owner || msg.sender == bundler, "auth"); _;
    }
    modifier nonReentrant() {
        require(_locked == 0, "re"); _locked = 1; _; _locked = 0;
    }

    /* ---- events ---- */
    event SoldV2(address indexed tokenIn, uint256 amountIn, address indexed to, uint256 amountOutMin, bool unwrapped);
    event SoldV3(address indexed tokenIn, uint256 amountIn, address indexed to, uint256 amountOutMin, bool unwrapped);
    event SoldUR(address indexed tokenIn, uint256 amountIn);
    event SoldVia(address indexed tokenIn, address indexed spender, address indexed target, uint256 amountIn);
    event Swept(address indexed to, address[] tokens, uint256 ethAmount);
    event SetBundler(address indexed bundler);
    event TransferRecovered(address indexed token, uint256 amount, address indexed to);

    constructor(
        address _weth,
        address _uniV2Router,
        address _uniV3Router02,
        address _universalRouter,
        address _oneInchRouter,
        address _oneInchSpender,
        address _zeroExProxy,
        address _zeroExAllowanceTarget,
        address _paraswapAugustus,
        address _paraswapTokenTransferProxy,
        address _odosRouter,
        address _kyberMetaAgg,
        address _openOceanRouter,
        address _cowSettlement,
        address _okxDexRouter,
        address _okxApproveProxy
    ) {
        owner = msg.sender;
        WETH = _weth;
        uniV2Router = _uniV2Router;
        uniV3Router02 = _uniV3Router02;
        universalRouter = _universalRouter;
        oneInchRouter = _oneInchRouter;
        oneInchSpender = _oneInchSpender;
        zeroExProxy = _zeroExProxy;
        zeroExAllowanceTarget = _zeroExAllowanceTarget;
        paraswapAugustus = _paraswapAugustus;
        paraswapTokenTransferProxy = _paraswapTokenTransferProxy;
        odosRouter = _odosRouter;
        kyberMetaAgg = _kyberMetaAgg;
        openOceanRouter = _openOceanRouter;
        cowSettlement = _cowSettlement;
        okxDexRouter = _okxDexRouter;
        okxApproveProxy = _okxApproveProxy;
    }

    /* ========================= owner ops ========================= */
    function setBundler(address _bundler) external {
        require(msg.sender == owner, "onlyOwner");
        bundler = _bundler; emit SetBundler(_bundler);
    }
    function transferOwnership(address newOwner) external {
        require(msg.sender == owner, "onlyOwner"); owner = newOwner;
    }

    /* ========================= utils ========================= */
    receive() external payable {}

    function _balanceOf(address token) internal view returns (uint256) { return IERC20(token).balanceOf(address(this)); }

    function _safeApprove(address token, address spender, uint256 amount) internal {
        // reset to 0 then set amount (USDT형 대응)
        (bool s1, bytes memory r1) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, 0));
        require(s1 && (r1.length == 0 || abi.decode(r1, (bool))), "approve0");
        (bool s2, bytes memory r2) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, amount));
        require(s2 && (r2.length == 0 || abi.decode(r2, (bool))), "approve");
    }

    function _revokeApprove(address token, address spender) internal {
        (bool s, bytes memory r) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, 0));
        require(s && (r.length == 0 || abi.decode(r, (bool))), "revoke");
    }

    function _transferAll(address token, address to) internal {
        uint256 bal = _balanceOf(token);
        if (bal > 0) {
            (bool s, bytes memory r) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, bal));
            require(s && (r.length == 0 || abi.decode(r, (bool))), "transfer");
        }
    }

    function _unwrapAllWETHTo(address to) internal {
        uint256 bal = _balanceOf(WETH);
        if (bal > 0) { IWETH(WETH).withdraw(bal); (bool ok,) = to.call{value: bal}(""); require(ok, "sendETH"); }
    }

    /// @notice 잔여 자산을 정리(sweep)
    function sweep(address[] calldata tokens, address to, bool unwrapWeth) external onlyAuthorized nonReentrant {
        if (unwrapWeth) _unwrapAllWETHTo(to);
        uint256 n = tokens.length; for (uint256 i; i < n; ++i) _transferAll(tokens[i], to);
        uint256 v = address(this).balance; if (v > 0) { (bool ok,) = to.call{value: v}(""); require(ok, "sweepETH"); }
        emit Swept(to, tokens, v);
    }

    /* ========================= V2 ========================= */
    function sellAllV2(
        address tokenIn,
        address[] calldata path,
        uint256 amountOutMin,
        address to,
        uint256 deadline,
        bool unwrap
    ) external onlyAuthorized nonReentrant {
        uint256 amt = _balanceOf(tokenIn);
        if (amt == 0) return;
        _safeApprove(tokenIn, uniV2Router, amt);
        if (path[path.length - 1] == WETH && unwrap) {
            IUniV2Router(uniV2Router).swapExactTokensForTokensSupportingFeeOnTransferTokens(amt, amountOutMin, path, address(this), deadline);
            _revokeApprove(tokenIn, uniV2Router);
            _unwrapAllWETHTo(to);
        } else if (path[path.length - 1] == WETH) {
            IUniV2Router(uniV2Router).swapExactTokensForETHSupportingFeeOnTransferTokens(amt, amountOutMin, path, to, deadline);
            _revokeApprove(tokenIn, uniV2Router);
        } else {
            IUniV2Router(uniV2Router).swapExactTokensForTokensSupportingFeeOnTransferTokens(amt, amountOutMin, path, to, deadline);
            _revokeApprove(tokenIn, uniV2Router);
        }
        emit SoldV2(tokenIn, amt, to, amountOutMin, unwrap);
    }

    /* ========================= V3 ========================= */
    function sellAllV3ExactInput(
        address tokenIn,
        bytes calldata path,
        uint256 amountOutMin,
        address to,
        uint256 deadline,
        bool unwrap
    ) external onlyAuthorized nonReentrant returns (uint256 outAmt) {
        uint256 amt = _balanceOf(tokenIn);
        if (amt == 0) return 0;
        _safeApprove(tokenIn, uniV3Router02, amt);
        address recipient = unwrap ? address(this) : to;
        outAmt = ISwapRouter02(uniV3Router02).exactInput(ISwapRouter02.ExactInputParams({
            path: path, recipient: recipient, deadline: deadline, amountIn: amt, amountOutMinimum: amountOutMin
        }));
        _revokeApprove(tokenIn, uniV3Router02);
        if (unwrap) _unwrapAllWETHTo(to);
        emit SoldV3(tokenIn, amt, to, amountOutMin, unwrap);
    }

    /* ========================= Universal Router ========================= */
    function sellAllUR(
        address tokenIn,
        bytes calldata commands,
        bytes[] calldata inputs,
        uint256 deadline
    ) external payable onlyAuthorized nonReentrant {
        uint256 amt = _balanceOf(tokenIn);
        if (amt > 0) _safeApprove(tokenIn, universalRouter, amt);
        // deadline 버전 먼저 시도, 실패 시 non-deadline 호출
        try IURWithDeadline(universalRouter).execute{value: msg.value}(commands, inputs, deadline) {
        } catch {
            IURNoDeadline(universalRouter).execute{value: msg.value}(commands, inputs);
        }
        if (amt > 0) _revokeApprove(tokenIn, universalRouter);
        emit SoldUR(tokenIn, amt);
    }

    /* ========================= Generic Aggregators ========================= */
    function sellAllVia(
        address tokenIn,
        address spender,
        address target,
        bytes calldata data
    ) public payable onlyAuthorized nonReentrant returns (bytes memory ret) {
        uint256 amt = _balanceOf(tokenIn);
        if (amt > 0 && spender != address(0)) _safeApprove(tokenIn, spender, amt);
        (bool ok, bytes memory out) = target.call{value: msg.value}(data);
        require(ok, "router-call");
        if (amt > 0 && spender != address(0)) _revokeApprove(tokenIn, spender);
        emit SoldVia(tokenIn, spender, target, amt);
        return out;
    }

    // 편의 함수: 호출 후 즉시 잔여 sweep
    function sellAllViaAndSweep(
        address tokenIn,
        address spender,
        address target,
        bytes calldata data,
        address to,
        address[] calldata extraTokensToSweep,
        bool unwrapWeth
    ) external payable onlyAuthorized {
        sellAllVia(tokenIn, spender, target, data);
        // tokenIn 잔여 + 추가 토큰 sweep
        uint256 n = extraTokensToSweep.length;
        address[] memory arr = new address[](n + 1);
        arr[0] = tokenIn; for (uint256 i; i < n; ++i) arr[i+1] = extraTokensToSweep[i];
        this.sweep(arr, to, unwrapWeth);
    }

    /* ========================= Pre-wired wrappers ========================= */
    function sellAll1inch(address tokenIn, bytes calldata data) external payable { sellAllVia(tokenIn, oneInchSpender, oneInchRouter, data); }
    function sellAll0x(address tokenIn, bytes calldata data) external payable {
        address spender = (zeroExAllowanceTarget == address(0)) ? zeroExProxy : zeroExAllowanceTarget; sellAllVia(tokenIn, spender, zeroExProxy, data);
    }
    function sellAllParaswap(address tokenIn, bytes calldata data) external payable {
        address spender = (paraswapTokenTransferProxy == address(0)) ? paraswapAugustus : paraswapTokenTransferProxy; sellAllVia(tokenIn, spender, paraswapAugustus, data);
    }
    function sellAllOdos(address tokenIn, bytes calldata data) external payable { sellAllVia(tokenIn, odosRouter, odosRouter, data); }
    function sellAllKyber(address tokenIn, bytes calldata data) external payable { sellAllVia(tokenIn, kyberMetaAgg, kyberMetaAgg, data); }
    function sellAllOpenOcean(address tokenIn, bytes calldata data) external payable { sellAllVia(tokenIn, openOceanRouter, openOceanRouter, data); }
    function sellAllCOW(address tokenIn, bytes calldata data) external payable { sellAllVia(tokenIn, cowSettlement, cowSettlement, data); }
    function sellAllOKX(address tokenIn, bytes calldata data) external payable {
        address spender = (okxApproveProxy == address(0)) ? okxDexRouter : okxApproveProxy; sellAllVia(tokenIn, spender, okxDexRouter, data);
    }

    /* ========================= Rescue ========================= */
    function rescueTokens(address token, address to) external { require(msg.sender == owner, "onlyOwner"); _transferAll(token, to); emit TransferRecovered(token, IERC20(token).balanceOf(to), to); }
    function rescueETH(address to) external { require(msg.sender == owner, "onlyOwner"); uint256 v = address(this).balance; (bool ok,) = to.call{value: v}(""); require(ok, "sendETH"); emit Swept(to, new address[](0), v); }
}
"
    }
  },
  "settings": {
    "remappings": [
      "forge-std/=lib/forge-std/src/"
    ],
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "prague",
    "viaIR": true
  }
}}

Tags:
DeFi, Swap, Factory|addr:0xfccf48a600e1461f97c0fa2c643d72de284c3501|verified:true|block:23691048|tx:0x849939a0c4c8b247df4ae60e73659cdf72f7b797aa2e1e3852c606c9fe60932b|first_check:1761841130

Submitted on: 2025-10-30 17:18:52

Comments

Log in to comment.

No comments yet.