NeuyArbExecutor Developer Docs

Updated Dec 11, 2025 - Two-leg atomic arbitrage executor

NeuyArbExecutor is a lightweight smart contract that composes two swaps through NeuyRouter into a single atomic transaction. It's designed for price discovery, cross-pool arbitrage, market making, and on-chain oracle style flows where you want to simulate and then execute the exact same path with one call.

0. Why NeuyArbExecutor?

NeuyArbExecutor is a generic two-leg execution primitive built on top of NeuyRouter. Anywhere you need to test and then atomically execute a tokenIn > midToken > tokenOut loop, this contract gives you a single, predictable endpoint that works across multiple DEXs and route types (V2, V3, split V2).

NeuyArbExecutor is designed as a multipurpose building block for:

  • Price discovery - simulate round trips through different pools and paths to measure execution-level prices, not just quotes.
  • On-chain oracle style reads - use stable assets or blue-chip pairs as midToken to infer prices for new or thinly traded tokens before an official oracle exists.
  • Arbitrage & spread capture - loop between two liquidity sources (e.g. V3 on one DEX and V2 on another) and only execute when the full path is profitable.
  • Market making & inventory rebalancing - intentionally allow bounded, controlled negative PnL (via minAmountOut) to keep books balanced across pools.
  • Launch & lab tooling - for new token launches, bots and dashboards can use callStatic.executeArb as a live price probe, then flip into real execution once spreads or liquidity meet their requirements.

The core idea is simple: you discover routes and parameters off-chain, simulate the exact call via callStatic.executeArb, and then submit the same payload as a transaction only when the outcome fits your strategy. No hidden routing, no external API, just a compact, deterministic on-chain executor.

1. Overview

NeuyArbExecutor is a small contract that:

  • Pulls tokenIn from the caller
  • Executes Leg 1: tokenIn > midToken via NeuyRouter
  • Executes Leg 2: midToken > tokenOut via NeuyRouter
  • Checks that final tokenOutminAmountOut
  • Returns the result and sends tokenOut back to the caller

The contract supports three leg types (for each leg separately):

  • 0 = V2: uses dexV2Swap on NeuyRouter
  • 1 = V3: uses dexV3Swap on NeuyRouter
  • 2 = V2_SPLIT: uses dexSplitV2Swap on NeuyRouter

This lets you build arbitrage and market-making strategies that combine two different liquidity sources (for example, Uniswap V3 > Camelot V2, or split V2 > V3) in a single atomic route.

2. Networks & contract addresses

NeuyArbExecutor is deployed alongside NeuyRouter on supported chains. The executor address is chain-specific and is designed to be stable, versioned infrastructure for arbitrage bots and MM tools.

Chain Executor address Router it uses
Arbitrum 0xeF1C09c2280e4529D33E8fF8413393Bb670bC5dB 0x0BA133CbfdAE0b431c35459B0b5f9Fe627c48cac
Avalanche 0x81A585CDd262861CCe51E926161d5fBdFD2B615D 0x1252b87BAd75AEfaCE59B594239F439EFF67cEF0
Binance (BSC) 0xbbaDd9B1411434739C5c04D9c8c643919c386680 0x600df1F1d2EAb5f81B7Acfe1319AD7B283525C88

The executor is intentionally simple: it does not compute routes itself. It expects you to provide leg types and route ids that are valid for the underlying NeuyRouter on that chain.

3. Executor ABI

NeuyArbExecutor exposes a single public entry point and an owner-only function for updating the router. Only the arb endpoint is required for most integrations:

[
// Core two-leg arbitrage call
"function executeArb(
    address tokenIn,
    address midToken,
    address tokenOut,
    uint256 amountIn,
    uint256 minAmountOut,
    uint8   leg1Type,
    uint8   leg2Type,
    uint256 leg1Route,
    uint256 leg1RouteSplit,
    uint256 leg2Route,
    uint256 leg2RouteSplit
) external returns (uint256)",

// (Admin) update router address - ABI-compatible NeuyRouter only
"function setNeuyRouter(address _neuyRouter) external"
]

The leg type mapping (from the Solidity enum) is:

LegType (uint8) Meaning Router function
0 V2 dexV2Swap(route, ...)
1 V3 dexV3Swap(route, ...)
2 V2_SPLIT dexSplitV2Swap(route1, route2, ...)

4. Quick start

The core pattern is always:

  1. Approve the executor for tokenIn
  2. Use callStatic.executeArb(...) to simulate a loop
  3. Check if the result is acceptable (profit or controlled loss)
  4. Send the exact same call as a real transaction
// JavaScript (ethers v5) - simulate, then execute

const EXECUTORS = {
  // Fill in per-chain once deployed
  arbitrum: "0x........................................"
};

const executorAbi = [
  "function executeArb(address tokenIn,address midToken,address tokenOut,uint256 amountIn,uint256 minAmountOut,uint8 leg1Type,uint8 leg2Type,uint256 leg1Route,uint256 leg1RouteSplit,uint256 leg2Route,uint256 leg2RouteSplit) external returns (uint256)"
];

async function initExecutor() {
  await window.ethereum.request({ method: "eth_requestAccounts" });

  const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
  const signer   = provider.getSigner();

  const executor = new ethers.Contract(
    EXECUTORS.arbitrum,
    executorAbi,
    signer
  );

  return { provider, signer, executor };
}

async function sampleArbLoop() {
  const { executor, signer } = await initExecutor();

  const tokenIn   = "0x..."; // e.g. WETH
  const midToken  = "0x..."; // e.g. USDC
  const tokenOut  = tokenIn; // round-trip arb
  const decimals  = 18;      // tokenIn decimals

  const amountIn  = ethers.utils.parseUnits("0.10", decimals); // 0.1 WETH

  // Example: both legs V2, using route=1 (fill in valid routes for your chain)
  const leg1Type       = 0;      // V2
  const leg2Type       = 0;      // V2
  const leg1Route      = 1;
  const leg1RouteSplit = 0;      // ignored for non-split
  const leg2Route      = 1;
  const leg2RouteSplit = 0;

  // 1) Simulate full loop
  const outAmount = await executor.callStatic.executeArb(
    tokenIn,
    midToken,
    tokenOut,
    amountIn,
    0,              // minAmountOut=0 for simulation
    leg1Type,
    leg2Type,
    leg1Route,
    leg1RouteSplit,
    leg2Route,
    leg2RouteSplit
  );

  const inFloat  = Number(ethers.utils.formatUnits(amountIn,  decimals));
  const outFloat = Number(ethers.utils.formatUnits(outAmount, decimals));
  const profitPct = ((outFloat - inFloat) / inFloat) * 100.0;

  console.log("Simulated out:", outFloat, "profit %:", profitPct.toFixed(4));

  // 2) Decide if this is acceptable (e.g. > 0.2% or within a controlled negative bound)
  const minProfit = 0.2; // required profit in percent
  if (profitPct < minProfit) {
    console.log("Not profitable enough, skipping.");
    return;
  }

  // 3) Choose a safety floor - e.g. require at least 80% of simulated result
  const minAmountOut = outAmount.mul(80).div(100);

  // 4) Make sure tokenIn is approved for the executor:
  //    erc20.approve(EXECUTORS.arbitrum, amountIn);

  const tx = await executor.executeArb(
    tokenIn,
    midToken,
    tokenOut,
    amountIn,
    minAmountOut,
    leg1Type,
    leg2Type,
    leg1Route,
    leg1RouteSplit,
    leg2Route,
    leg2RouteSplit
  );
  const receipt = await tx.wait();
  console.log("Arb executed:", receipt.transactionHash);
}
# Python (web3.py) - simulate & execute a loop via NeuyArbExecutor

from web3 import Web3

EXECUTORS = {
    "arbitrum": "0x........................................"
}

executor_abi = [
    {
        "name": "executeArb",
        "type": "function",
        "stateMutability": "nonpayable",
        "inputs": [
            {"name": "tokenIn",        "type": "address"},
            {"name": "midToken",       "type": "address"},
            {"name": "tokenOut",       "type": "address"},
            {"name": "amountIn",       "type": "uint256"},
            {"name": "minAmountOut",   "type": "uint256"},
            {"name": "leg1Type",       "type": "uint8"},
            {"name": "leg2Type",       "type": "uint8"},
            {"name": "leg1Route",      "type": "uint256"},
            {"name": "leg1RouteSplit", "type": "uint256"},
            {"name": "leg2Route",      "type": "uint256"},
            {"name": "leg2RouteSplit", "type": "uint256"},
        ],
        "outputs": [
            {"name": "", "type": "uint256"}
        ]
    }
]

w3 = Web3(Web3.HTTPProvider("YOUR_ARB_RPC_URL"))
executor_addr = Web3.to_checksum_address(EXECUTORS["arbitrum"])
executor = w3.eth.contract(address=executor_addr, abi=executor_abi)

token_in   = Web3.to_checksum_address("0x...")
mid_token  = Web3.to_checksum_address("0x...")
token_out  = token_in    # round-trip
decimals   = 18
amount_in  = int(0.10 * 10**decimals)

leg1_type       = 0  # V2
leg2_type       = 0  # V2
leg1_route      = 1
leg1_route_split = 0
leg2_route      = 1
leg2_route_split = 0

# 1) Simulate
out_amount = executor.functions.executeArb(
    token_in,
    mid_token,
    token_out,
    amount_in,
    0,  # minAmountOut=0 for call
    leg1_type,
    leg2_type,
    leg1_route,
    leg1_route_split,
    leg2_route,
    leg2_route_split
).call()

in_float  = amount_in / 10**decimals
out_float = out_amount / 10**decimals
profit_pct = (out_float - in_float) / in_float * 100.0
print("Sim out:", out_float, "profit%:", profit_pct)

min_profit = 0.2
if profit_pct < min_profit:
    print("Not profitable enough, aborting.")
    raise SystemExit

min_amount_out = out_amount * 80 // 100  # 80% floor

from_addr = Web3.to_checksum_address("0xYourEOA...")
nonce = w3.eth.get_transaction_count(from_addr)

tx = executor.functions.executeArb(
    token_in,
    mid_token,
    token_out,
    amount_in,
    min_amount_out,
    leg1_type,
    leg2_type,
    leg1_route,
    leg1_route_split,
    leg2_route,
    leg2_route_split
).build_transaction({
    "from": from_addr,
    "nonce": nonce,
    "gas": 900000,
    "gasPrice": w3.to_wei("0.1", "gwei"),
})

# Sign & send as usual

5. Key use cases

5.1 Price discovery across DEXs

You can loop tokenIn > midToken > tokenIn across different leg types and routes to measure real, execution-level prices instead of relying only on quotes. For example:

  • Leg 1: V3 (route = 0..2 fee tiers)
  • Leg 2: V2 (route = 1/2/3 across Pancake/Sushi/Camelot)

By scanning these combinations with callStatic.executeArb, you can build your own micro price oracle for pairs that may not have a reliable Chainlink feed yet.

5.2 Oracle-style read for new token launches

For brand new tokens, on-chain oracles often lag behind. NeuyArbExecutor lets you:

  • Simulate looping through a stable asset (e.g. USDC) as midToken
  • Measure implied price of the new token across multiple pools
  • Feed that into your internal pricing engine or off-chain risk logic

5.3 Arbitrage & MEV-lite flows

A traditional arbitrage bot might choose:

  • Leg 1: buy midToken cheaply on DEX A
  • Leg 2: sell midToken on DEX B back into tokenIn

With NeuyArbExecutor, you simulate and execute that logic via a single call. If any leg reverts, the whole transaction reverts. This makes it safer to run bots that operate close to break-even or across volatile mempool conditions.

5.4 Market making with bounded loss

Market makers sometimes accept small, controlled negative trades to maintain liquidity and inventory balance. Because minAmountOut is fully under your control, you can:

  • Allow trades down to e.g. -0.5% PnL for inventory rebalancing
  • Use callStatic.executeArb to measure % PnL precisely
  • Execute only when the loss is within your configured band

6. Integration patterns & best practices

6.1 Separate read & write RPC endpoints

Arbitrage bots can generate a lot of callStatic traffic. It's best to:

  • Use a public / high-throughput RPC for simulation
  • Use a reliable, low-latency RPC for sending actual transactions

6.2 Pre-validating routes with NeuyRouter

Before calling executeArb, you can use NeuyRouter's quote functions to pre-select promising routes:

  • Use getV2Quote, getV3Quote, getSplitV2Quote to filter
  • Then feed those route ids into executeArb for full-loop simulation

6.3 Allowance and per-trade sizing

For safety, it's common to:

  • Approve a fixed "budget" to NeuyArbExecutor (e.g. 3-10x per-trade size)
  • Track remaining allowance off-chain and stop when you're near the limit
  • Use smaller per-trade sizes when testing new routes or new tokens

6.4 Handling reverts

Some combinations will revert due to low liquidity or unexpected router-level reverts. Always:

  • Wrap callStatic.executeArb calls in try/catch
  • Treat a revert as "no valid path" for that combination
  • Log and skip; do not treat reverts as critical errors for the whole bot

7. Risk Disclosure

The NeuyArbExecutor and NeuyRouter contracts operate autonomously on public blockchain networks and are not monitored, controlled, or upgraded by NeuyAI once deployed. By choosing to interact with these contracts, you assume full responsibility for all associated risks, including but not limited to smart-contract bugs, market volatility, failed transactions, slippage, MEV, and loss of funds. NeuyAI provides no warranties and disclaims all liability for any damages arising from the use of this protocol.