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
tokenOut ≥ minAmountOut
- 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.
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:
4. Quick start
The core pattern is always:
- Approve the executor for
tokenIn
- Use
callStatic.executeArb(...) to simulate a loop
- Check if the result is acceptable (profit or controlled loss)
- 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.