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
}
}}
Submitted on: 2025-10-30 17:18:52
Comments
Log in to comment.
No comments yet.