Address
0x1e5df975bf9eeec856bbe7aa96aafd9cdd83de6dCurrent Holdings
$0.00
TXs sent
not counted
First Active
2026-06-02
block 26,685,608
Last Active
104 days ago
block 26,685,608
Funded By
not identified
Net worth historyi
No net-worth snapshots recorded yet
exact matchPulseArbsolc 0.8.20+commit.a1b79de6runtime exact · creation exact
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title PulseArb — Atomic multi-hop DEX arbitrage for PulseChain
///
/// @notice Two execution modes:
/// 1. `executeArb` — funded arb: bot pre-holds the input capital.
/// 2. `executeArbFlash` — flash-swap arb: borrow from a UniswapV2 pair,
/// execute the cycle, repay loan + fee, keep profit.
/// Zero capital required; TX reverts with no loss if
/// profit < minProfit.
///
/// @dev Security model:
/// - `onlyOwner` on both entry points.
/// - `uniswapV2Call` validates `msg.sender == pairs[0]` (the flash provider)
/// and `sender == address(this)` (initiated by us, not an external caller).
/// - Re-entrancy guard via `_flashActive` flag.
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 token0() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}
contract PulseArb {
address public immutable owner;
/// @dev Re-entrancy guard: set to true during a flash-swap callback.
bool private _flashActive;
error NotOwner();
error InvalidPath();
error NotACycle();
error InsufficientProfit(uint256 got, uint256 need);
error TransferFailed();
error ReentrantCall();
error InvalidFlashCaller();
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner();
_;
}
modifier noReentrant() {
if (_flashActive) revert ReentrantCall();
_flashActive = true;
_;
_flashActive = false;
}
constructor() {
owner = msg.sender;
}
// =========================================================================
// MODE 1 — FUNDED ARBITRAGE
// =========================================================================
/// @notice Execute a multi-hop arbitrage cycle using pre-held capital.
///
/// @param pairs UniswapV2-compatible pair addresses (one per hop).
/// @param tokens Token path — length must be pairs.length + 1.
/// tokens[0] == tokens[last] (cycle invariant).
/// @param feeBps Fee in basis points per pair (e.g. 30 = 0.3%).
/// @param amountIn Amount of tokens[0] to seed the cycle (pre-approved).
/// @param minProfit Minimum net profit in tokens[0] units — reverts if not met.
function executeArb(
address[] calldata pairs,
address[] calldata tokens,
uint256[] calldata feeBps,
uint256 amountIn,
uint256 minProfit
) external onlyOwner {
uint256 n = pairs.length;
if (n == 0 || tokens.length != n + 1 || feeBps.length != n) revert InvalidPath();
if (tokens[0] != tokens[n]) revert NotACycle();
if (!IERC20(tokens[0]).transferFrom(msg.sender, address(this), amountIn)) revert TransferFailed();
uint256 amount = _executeHops(pairs, tokens, feeBps, 0, n, amountIn);
if (amount < amountIn + minProfit) {
revert InsufficientProfit(amount > amountIn ? amount - amountIn : 0, minProfit);
}
if (!IERC20(tokens[0]).transfer(msg.sender, amount)) revert TransferFailed();
}
// =========================================================================
// MODE 2 — FLASH-SWAP ARBITRAGE
// =========================================================================
/// @notice Initiate a capital-free arbitrage via UniswapV2 flash swap.
///
/// @dev Flow:
/// 1. Borrow `flashAmount` of `tokens[0]` from `pairs[0]` (flash provider).
/// 2. `pairs[0]` calls back `uniswapV2Call` with the encoded route.
/// 3. Callback executes hops 1..n-1, repays `pairs[0]`, keeps profit.
///
/// @param pairs Full route including the flash provider as `pairs[0]`.
/// `pairs[0]` must hold a reserve in `tokens[0]`.
/// `pairs[0]` should NOT also appear in hops 1..n-1
/// (otherwise the reserve math will be off).
/// @param tokens Token path. tokens[0] == tokens[last] == borrowed token.
/// @param feeBps Fee per hop. feeBps[0] = fee of flash provider pair
/// (used to compute repayment amount).
/// @param flashAmount Amount of tokens[0] to borrow.
/// @param minProfit Minimum profit kept after repayment or revert.
function executeArbFlash(
address[] calldata pairs,
address[] calldata tokens,
uint256[] calldata feeBps,
uint256 flashAmount,
uint256 minProfit
) external onlyOwner noReentrant {
uint256 n = pairs.length;
if (n < 2 || tokens.length != n + 1 || feeBps.length != n) revert InvalidPath();
if (tokens[0] != tokens[n]) revert NotACycle();
bytes memory data = abi.encode(pairs, tokens, feeBps, flashAmount, minProfit);
// Determine which token slot to request from the flash pair
address t0 = IUniswapV2Pair(pairs[0]).token0();
bool tokenIsZero = (tokens[0] == t0);
uint256 out0 = tokenIsZero ? flashAmount : 0;
uint256 out1 = tokenIsZero ? 0 : flashAmount;
// Triggers the flash swap → pairs[0] sends tokens → calls uniswapV2Call
IUniswapV2Pair(pairs[0]).swap(out0, out1, address(this), data);
}
/// @notice UniswapV2 flash-swap callback.
///
/// @dev Called by `pairs[0]` after it sends us `flashAmount` of `tokens[0]`.
/// We execute the arb (hops 1..n-1), then repay pairs[0].
///
/// Security:
/// - `msg.sender` must equal `pairs[0]` encoded in `data`.
/// - `sender` must be `address(this)` (we initiated the flash swap).
/// - `_flashActive` re-entrancy guard is active.
function uniswapV2Call(
address sender,
uint256 /*amount0*/,
uint256 /*amount1*/,
bytes calldata data
) external {
// Must have been triggered by our own executeArbFlash
if (sender != address(this)) revert InvalidFlashCaller();
if (!_flashActive) revert ReentrantCall();
(
address[] memory pairs,
address[] memory tokens,
uint256[] memory feeBps,
uint256 amountBorrowed,
uint256 minProfit
) = abi.decode(data, (address[], address[], uint256[], uint256, uint256));
uint256 n = pairs.length;
// Caller must be the flash-provider pair encoded in the route
if (msg.sender != pairs[0]) revert InvalidFlashCaller();
// Execute hops 1..n-1 with the borrowed tokens
uint256 amount = _executeHops(pairs, tokens, feeBps, 1, n, amountBorrowed);
// Repay flash loan: amountBorrowed * 10000 / (10000 - fee) + 1 (rounds up)
uint256 fee = feeBps[0];
uint256 repayAmount = (amountBorrowed * 10000) / (10000 - fee) + 1;
if (amount < repayAmount + minProfit) {
revert InsufficientProfit(
amount > repayAmount ? amount - repayAmount : 0,
minProfit
);
}
// Repay the flash provider (must transfer directly to the pair contract)
if (!IERC20(tokens[0]).transfer(pairs[0], repayAmount)) revert TransferFailed();
// Residual profit stays in this contract — owner withdraws via withdraw()
}
// =========================================================================
// TOKEN RESCUE
// =========================================================================
/// @notice Withdraw a specific amount of a token to the owner.
function withdraw(address token, uint256 amount) external onlyOwner {
if (!IERC20(token).transfer(owner, amount)) revert TransferFailed();
}
/// @notice Withdraw the entire balance of a token to the owner.
function withdrawAll(address token) external onlyOwner {
uint256 bal = IERC20(token).balanceOf(address(this));
if (bal > 0) {
if (!IERC20(token).transfer(owner, bal)) revert TransferFailed();
}
}
// =========================================================================
// Internal helpers
// =========================================================================
/// @dev Execute swap hops from index `start` to `end-1` (exclusive).
/// Returns the final output amount.
function _executeHops(
address[] memory pairs,
address[] memory tokens,
uint256[] memory feeBps,
uint256 start,
uint256 end,
uint256 amountIn
) internal returns (uint256 amount) {
amount = amountIn;
for (uint256 i = start; i < end; i++) {
address pair = pairs[i];
address tokenIn = tokens[i];
address tokenOut = tokens[i + 1];
(uint112 r0, uint112 r1,) = IUniswapV2Pair(pair).getReserves();
address token0 = IUniswapV2Pair(pair).token0();
bool zeroForOne = (tokenIn == token0);
uint256 reserveIn = zeroForOne ? uint256(r0) : uint256(r1);
uint256 reserveOut = zeroForOne ? uint256(r1) : uint256(r0);
uint256 amountOut = _getAmountOut(amount, reserveIn, reserveOut, feeBps[i]);
if (!IERC20(tokenIn).transfer(pair, amount)) revert TransferFailed();
(uint256 out0, uint256 out1) = zeroForOne
? (uint256(0), amountOut)
: (amountOut, uint256(0));
IUniswapV2Pair(pair).swap(out0, out1, address(this), "");
amount = amountOut;
}
}
/// @dev UniswapV2 constant-product output formula.
/// amountOut = (amountIn * (10000 - fee) * reserveOut)
/// / (reserveIn * 10000 + amountIn * (10000 - fee))
function _getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut,
uint256 fee
) internal pure returns (uint256) {
require(amountIn > 0 && reserveIn > 0 && reserveOut > 0, "Bad reserves");
uint256 k = 10000 - fee;
uint256 amountInWithFee = amountIn * k;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 10000 + amountInWithFee;
return numerator / denominator;
}
}