Skip to main content
PulseScanner.io

Address

0xcdea894d3d4aeb3ddefedac777dd2a949d2ce4ac
Current Holdings
$0.00
TXs sent
not counted
First Active
2026-06-17
block 26,811,982
Last Active
91 days ago
block 26,812,001
Funded By
not identified

Net worth historyi

3 snapshots · to block 27,427,957coverage change 26 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Aug
exact matchPulseArbsolc 0.8.20+commit.a1b79de6runtime exact · creation exact
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title PulseArb — Low-gas atomic DEX arbitrage for PulseChain
///
/// Key design decisions:
///   - Contract-funded: WPLS is held inside the contract and reused across arb cycles.
///     No per-TX transferFrom wallet or final transfer back — eliminates 2 ERC-20 ops per route.
///   - Direct pair-to-pair routing: WPLS is sent once to pair[0];
///     each pair's swap() sends output directly to the next pair.
///   - zeroForOne[] is pre-computed off-chain — no token0() calls on-chain.
///   - WPLS is the exclusive start/end token; hardcoded to save calldata.
///   - executeMultiArb isolates each route in a self-call. A stale/FOT route rolls
///     back only its own swaps while other routes in the batch can still execute.

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IUniswapV2Pair {
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}

contract PulseArb {
    address public immutable owner;

    // WPLS is always the start and end token of every arb cycle.
    IERC20 internal constant WPLS = IERC20(0xA1077a294dDE1B09bB078844df40758a5D0f9a27);

    error NotOwner();
    error OnlySelf();
    error InvalidPath();
    error QuoteInsufficientProfit(uint256 got, uint256 need);
    error InsufficientProfit(uint256 got, uint256 need);
    error TransferFailed();
    error NoRoutesExecuted();

    /// Emitted when an isolated batch route reverts.
    event RouteFailed(uint256 indexed routeIdx, bytes4 selector, bytes32 reasonHash);
    /// Emitted when a route executes successfully.
    event RouteExecuted(uint256 indexed routeIdx, uint256 amountIn, uint256 amountOut, uint256 profit);

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    modifier onlySelf() {
        if (msg.sender != address(this)) revert OnlySelf();
        _;
    }

    constructor() { owner = msg.sender; }

    // =========================================================================
    // Capital management — deposit / withdraw WPLS trading capital
    // =========================================================================

    /// @notice Deposit WPLS from owner wallet into contract as trading capital.
    ///         Requires prior WPLS.approve(address(this), amount) from the wallet.
    function depositWPLS(uint256 amount) external onlyOwner {
        if (!WPLS.transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
    }

    /// @notice Withdraw WPLS from contract back to owner.
    function withdrawWPLS(uint256 amount) external onlyOwner {
        if (!WPLS.transfer(owner, amount)) revert TransferFailed();
    }

    // =========================================================================
    // executeArb — single funded route, fast path
    // =========================================================================

    /// @notice Execute one WPLS arb cycle using contract-held WPLS.
    ///
    /// Gas profile vs wallet-funded design (3-hop example):
    ///   Old: transferFrom(wallet→pair) + 3×swap + transfer(contract→wallet) = 5 ERC-20 ops
    ///   New: transfer(contract→pair)   + 3×swap                              = 4 ERC-20 ops
    ///   Profit stays inside the contract — no gas for the final return transfer.
    ///
    /// @param pairs      UniswapV2 pairs in swap order.
    /// @param zeroForOne true if tokenIn == token0 of that pair (pre-computed off-chain).
    /// @param feeBps     Swap fee in basis points per pair (e.g. 29 for PulseX).
    /// @param amountIn   WPLS amount to arb (must already be in contract balance).
    /// @param minProfit  Minimum WPLS profit required (reverts if not met).
    function executeArb(
        address[] calldata pairs,
        bool[]    calldata zeroForOne,
        uint256[] calldata feeBps,
        uint256   amountIn,
        uint256   minProfit
    ) external onlyOwner {
        uint256 amount = _executeRoute(pairs, zeroForOne, feeBps, amountIn, minProfit);
        emit RouteExecuted(0, amountIn, amount, amount - amountIn);
    }

    // =========================================================================
    // executeMultiArb — batch multiple routes in one TX
    // =========================================================================

    /// @notice Execute multiple WPLS arb cycles atomically using contract-held WPLS.
    ///
    /// Each route executes in an external self-call so a route-level revert restores
    /// its WPLS and pair state without reverting successful sibling routes.
    function executeMultiArb(
        address[][] calldata pairs,
        bool[][]    calldata zeroForOne,
        uint256[][] calldata feeBps,
        uint256[]   calldata amountsIn,
        uint256[]   calldata minProfits
    ) external onlyOwner {
        uint256 n = pairs.length;
        if (
            n == 0 ||
            zeroForOne.length != n ||
            feeBps.length != n ||
            amountsIn.length != n ||
            minProfits.length != n
        ) revert InvalidPath();
        uint256 executed;

        for (uint256 i = 0; i < n; ) {
            try this.executeRouteIsolated(
                pairs[i],
                zeroForOne[i],
                feeBps[i],
                amountsIn[i],
                minProfits[i]
            ) returns (uint256 amount) {
                emit RouteExecuted(i, amountsIn[i], amount, amount - amountsIn[i]);
                unchecked { ++executed; }
            } catch (bytes memory reason) {
                bytes4 selector;
                if (reason.length >= 4) {
                    assembly {
                        selector := mload(add(reason, 32))
                    }
                }
                emit RouteFailed(i, selector, keccak256(reason));
            }
            unchecked { ++i; }
        }

        if (executed == 0) revert NoRoutesExecuted();
    }

    // =========================================================================
    // Batch isolation entry point
    // =========================================================================

    /// @dev Callable only by this contract through `try this...catch`.
    function executeRouteIsolated(
        address[] calldata pairs,
        bool[]    calldata zeroForOne,
        uint256[] calldata feeBps,
        uint256 amountIn,
        uint256 minProfit
    ) external onlySelf returns (uint256) {
        return _executeRoute(pairs, zeroForOne, feeBps, amountIn, minProfit);
    }

    // =========================================================================
    // Token rescue
    // =========================================================================

    function withdraw(address token, uint256 amount) external onlyOwner {
        if (!IERC20(token).transfer(owner, amount)) revert TransferFailed();
    }

    function withdrawAll(address token) external onlyOwner {
        uint256 bal = IERC20(token).balanceOf(address(this));
        if (bal > 0 && !IERC20(token).transfer(owner, bal)) revert TransferFailed();
    }

    // =========================================================================
    // Internal helpers
    // =========================================================================

    function _executeRoute(
        address[] memory pairs,
        bool[] memory zeroForOne,
        uint256[] memory feeBps,
        uint256 amountIn,
        uint256 minProfit
    ) internal returns (uint256 amount) {
        uint256 n = pairs.length;
        if (
            n == 0 ||
            zeroForOne.length != n ||
            feeBps.length != n ||
            WPLS.balanceOf(address(this)) < amountIn
        ) revert InvalidPath();

        uint256 quoted = _quote(pairs, zeroForOne, feeBps, amountIn);
        if (quoted < amountIn + minProfit) {
            revert QuoteInsufficientProfit(quoted > amountIn ? quoted - amountIn : 0, minProfit);
        }
        if (!WPLS.transfer(pairs[0], amountIn)) revert TransferFailed();

        amount = amountIn;
        unchecked {
            for (uint256 i = 0; i < n; ++i) {
                address to = i + 1 < n ? pairs[i + 1] : address(this);
                (uint112 r0, uint112 r1,) = IUniswapV2Pair(pairs[i]).getReserves();
                bool z1o = zeroForOne[i];
                uint256 out = _getAmountOut(
                    amount,
                    z1o ? r0 : r1,
                    z1o ? r1 : r0,
                    feeBps[i]
                );
                IUniswapV2Pair(pairs[i]).swap(
                    z1o ? 0 : out,
                    z1o ? out : 0,
                    to,
                    ""
                );
                amount = out;
            }
        }

        if (amount < amountIn + minProfit) {
            revert InsufficientProfit(amount > amountIn ? amount - amountIn : 0, minProfit);
        }
    }

    function _quote(
        address[] memory pairs,
        bool[]    memory zeroForOne,
        uint256[] memory feeBps,
        uint256   amountIn
    ) internal view returns (uint256 amount) {
        amount = amountIn;
        unchecked {
            for (uint256 i = 0; i < pairs.length; ++i) {
                (uint112 r0, uint112 r1,) = IUniswapV2Pair(pairs[i]).getReserves();
                bool z1o = zeroForOne[i];
                amount = _getAmountOut(amount, z1o ? r0 : r1, z1o ? r1 : r0, feeBps[i]);
            }
        }
    }

    /// @dev UniswapV2 constant-product formula. Returns 0 on bad inputs (no revert).
    function _getAmountOut(
        uint256 amIn,
        uint256 rIn,
        uint256 rOut,
        uint256 fee
    ) internal pure returns (uint256) {
        if (amIn == 0 || rIn == 0 || rOut == 0) return 0;
        unchecked {
            uint256 k      = 10000 - fee;
            uint256 inFee  = amIn * k;
            return (inFee * rOut) / (rIn * 10000 + inFee);
        }
    }
}