How bots exploited my ImageGen contract - and how I fixed it
Loading...
Early last week, I discovered that my image generation smart contract had been exploited by a sophisticated bot network. The bots found a vulnerability that allowed them to steal the rewards meant for my backend service. In this post, I'll explain the exploit, the fix, and share some of the surprising findings from my forensic analysis of the bot network.
The Exploit: Exploit-2025-11-26
The vulnerability was embarrassingly simple. In my GenImNFT contract, I had a function called requestImageUpdate() that was supposed to:
- Update the NFT with a generated image URL
- Pay the caller (my backend service) the mint price as compensation
The problem? Anyone could call this function, not just my authorized backend service.
// VULNERABLE CODE (v3)
function requestImageUpdate(uint256 tokenId, string memory imageUrl) public {
require(_exists(tokenId), "Token does not exist");
require(!_imageUpdated[tokenId], "Image already updated");
// NO AUTHORIZATION CHECK! ❌
_imageUpdated[tokenId] = true;
_setTokenURI(tokenId, imageUrl);
// Pay the caller - but anyone can be the caller!
(bool success, ) = payable(msg.sender).call{value: mintPrice}("");
}Some bots discovered this and started front-running legitimate image update requests. When my backend tried to update an NFT, the bots would:
- See the pending transaction in the mempool
- Submit their own
requestImageUpdate()with higher gas - Claim the payment before my transaction executed
- Leave the NFT with an empty image URL
The Fix: Agent Whitelist (v4)
The fix was straightforward - implement a whitelist of authorized agent wallets that can call requestImageUpdate(). This follows the EIP-8004 pattern for trustless agents:
// FIXED CODE (v4)
mapping(address => bool) private _whitelistedAgentWallets;
function authorizeAgentWallet(address agentWallet) public onlyOwner {
_whitelistedAgentWallets[agentWallet] = true;
}
function requestImageUpdate(uint256 tokenId, string memory imageUrl) public {
require(_exists(tokenId), "Token does not exist");
require(!_imageUpdated[tokenId], "Image already updated");
require(_whitelistedAgentWallets[msg.sender], "Not authorized agent"); // ✅
// ... rest of the function
}After upgrading to v4, I called authorizeAgentWallet() with my backend service's address. Now only whitelisted addresses can claim the rewards.
The Bot Network: A Forensic Analysis
What started as fixing a bug turned into a fascinating forensic investigation. Using Etherscan data and on-chain analysis, I traced the bot network's structure:
Scale of the Operation
- 67 potential bot wallets identified on Optimism
- Roughly 100,679 USDC extracted (80cents just from my contract)
- Active since May 2023 - over 2.5 years of operation
Network Hierarchy
The bots operate in a clear hierarchy:
Central Treasury
│
├── Wallet Farm Funder 1
│ └── 67 bot wallets funded
│
└── Wallet Farm Funder 2
└── roughly 100 bot wallets fundedThe central wallet has been active for 893 days with 4,800+ transactions. It funds the "Wallet Farm Funders" who then distribute small amounts of ETH to a lot of individual bot wallets.
How the Bots Operate
- Funding: The central treasury sends ETH to wallet farm funders on Mainnet
- Distribution: Farm funders send roughly 0.2 ETH to bot wallets
- Bridging: Bots bridge funds to target chains (Optimism, Arbitrum, etc.)
- Exploitation: Bots monitor mempools and front-run vulnerable transactions
- Extraction: Profits are bridged back via Stargate, Orbiter, deBridge, LiFi
Professionalism
This clearly looks like a professional operation:
- Uses multiple parallel bot infrastructures
- Rotates wallets regularly
- Leverages multiple bridges to obscure fund flows
- Has been operating undetected for years
My Lessons Learned
- Always implement access control - Even "internal" functions that seem safe can be exploited. And even tiny amounts are exploited by bots.
- Use established patterns - EIP-8004 agent authorization patterns exist and are important for everyone, not just large protocols.
- Bots are everywhere - If there's value to extract, bots will find it
What's Next
I've filed reports on Chainabuse to help warn others about these wallet addresses.
The image generator is back online with the fixed contract. Feel free to try it at /imagegen - now with proper security!
Comments
Loading comments...