TrustedVolumes' $1.1M Loss and How Olympix Would Have Prevented It
Date: May 7, 2026
Chain: Ethereum
Loss: about $5.9M drained, about $4.76M returned under a bounty settlement, about $1.1M net
Class: signer authorization checked against one address while tokens are pulled from another
The market maker TrustedVolumes kept its trading inventory in a wallet on Ethereum and gave a single contract unlimited permission to spend it. That contract was the firm's own request-for-quote settlement proxy, deployed in 2024 to let traders fill TrustedVolumes' signed price quotes, and the standing approvals were how it paid out. On May 7, 2026, at 00:47:35 UTC, one transaction from a brand-new wallet used that permission four times in a row and walked away with 1,291 WETH, 206,282 USDT, 16.94 WBTC and 1.27 million USDC, about $5.9 million.
The attacker didn't steal a key or break a signature. The proxy let anyone register their own signing key, checked that key against an address the caller controlled, and then pulled tokens from a different address named in the order, which happened to be TrustedVolumes' inventory. Two months later, after a long negotiation carried out partly in on-chain messages, the attacker sent back 1,169.96 ETH and then 1,122.12 ETH to an address TrustedVolumes named as its own, and the firm said the exploiter had received a bug bounty. At CoinGecko's ETH prices on the two return dates, those 2,292.08 ETH were worth about $4.76 million, which leaves TrustedVolumes about $1.1 million short of the $5.86 million drained.
A Market Maker's Private Settlement Desk
TrustedVolumes is a market maker and resolver in the 1inch ecosystem, according to QuillAudits' analysis, and it supplied on-chain liquidity through a custom RFQ proxy. RFQ stands for request for quote. A maker signs a quote off-chain saying it'll sell one token for another at a set price, a taker submits that signed quote to a settlement contract, and the contract checks the signature and swaps the tokens in one atomic step. QuillAudits sums up what the model depends on in three guarantees: the maker decides who can sign for it, each order fills only once, and the tokens come out of the maker's own inventory.
TrustedVolumes' version had three pieces. The proxy at 0xeEeEEe53033F7227d488ae83a27Bc9A9D5051756 was deployed on August 20, 2024, and the implementation it delegated to, 0x88eb28009351Fb414A5746F5d8CA91cdc02760d8, followed on September 24, 2024, both from the same deployer. The inventory wallet at 0x9bA0CF1588E1DFA905eC948F7FE5104dD40EDa31 held the tokens and had approved the proxy to spend them without limit. At the block before the attack, QuillAudits lists that inventory holding 1,304.20 WETH, 208,366 USDT, 17.11 WBTC and 1,281,587 USDC, each with an unlimited allowance to the proxy.
Neither the proxy nor the implementation was ever verified on Etherscan, so there's no public source code to quote, and every description of the logic below comes from researchers who worked from the bytecode. Rekt notes that no audit of the RFQ proxy had been publicly disclosed. 1inch said after the attack that TrustedVolumes operates independently and that 1inch itself wasn't affected, according to QuillAudits.
Three Checks That Each Looked at the Wrong Thing
The first problem was the front door. The implementation exposes registerAllowedOrderSigner(address,bool), and according to banteg's reconstruction of the bytecode it simply records that the caller allows a given signer, keyed on msg.sender, with no owner check or role behind it. CertiK's alert described it as a public function anyone could use to register as an allowed order signer. On its own, that's harmless, because a signer you authorize for yourself should only be able to sign for your own money.
The second problem turned that harmless function into a key to the vault. The fill function, selector 0x4112e1c2, recovers the signer from the order signature and then checks that signer against an allowlist. Researchers describe the lookup slightly differently. SlowMist's alert says it's keyed on the caller, the taker, instead of the order's maker, and banteg's reconstruction says it's keyed on the order's receiver. In this attack the attacker's own contract was both the caller and the receiver, so in either reading the contract asked the attacker whether the attacker's signer was allowed, and the answer was always yes.
Meanwhile, the tokens came from somewhere else entirely. In banteg's reconstruction, the contract transfers the sell token from the order's inventory field to the receiver, and that inventory field is ordinary order data the caller writes. Rekt describes the same split in terms of the taker field being used as the source of funds. Either way, the address that got authenticated and the address that got debited were different fields, and nothing in the fill path checked that the signer had any authority over the address paying out. QuillAudits puts the gap plainly: authorization to sign for a receiver isn't authorization to spend from an arbitrary inventory.
The third problem was replay protection. An RFQ order is meant to fill once, and the contract kept a status record for that. Both Rekt and QuillAudits report that the status was written under one storage key and read back under another, so the check that should have rejected a filled order never saw the fill. According to QuillAudits, that's what let the same attack run four times in one transaction.
The lesson for builders sits between the second and third problems. When a function moves tokens out of an address, the authorization it checks has to be about that exact address. A valid signature from a valid signer proves something about whoever the signer was registered for, and if that isn't the account being charged, the signature proves nothing about the payment.
Four Orders in One Constructor
The attacker's wallet, 0xC3EBDdEa4f69df717a8f5c89e7cF20C1c0389100, sent a single contract-creation transaction at 00:47:35 UTC in block 25039670, and the whole attack ran inside the new contract's constructor. According to QuillAudits, the constructor first registered the attacker's own address as an allowed signer for the contract. The wallet had also sent the new contract a few millionths of a dollar in USDC, and that was everything the attack cost apart from gas.
The contract then filled four orders it had written and signed itself, each naming TrustedVolumes' inventory as the source of the tokens and the attack contract as the receiver. Each one paid the inventory a token amount of USDC as its side of the trade, and the token transfers on Blockscout show what came back the other way: 1,291.16 WETH, 206,282.45 USDT, 16.939 WBTC and 1,268,771.49 USDC, all pulled from 0x9bA0CF1588E1DFA905eC948F7FE5104dD40EDa31 under its existing approvals. Comparing those amounts with QuillAudits' snapshot of the inventory, the four orders took nearly everything in it.
Before the transaction ended, the contract unwrapped the WETH into ETH and forwarded the proceeds to the attacker's wallet. banteg's Foundry reproduction values the four assets at $5,864,441.85 using Chainlink prices from the parent block, and CertiK's alert put the figure at about $5.87 million. TrustedVolumes' own post later listed roughly $6.7 million across the wallets holding the stolen funds.
Wallets, Tornado Cash and Messages on Chain
The attacker moved fast. Eighteen minutes after the exploit, the main wallet sent 0.1 ETH to a second wallet, 0x61e6301614178A2cA21Bfa0FBB30ABa06ACC2D1c, which started swapping through Uniswap's Universal Router within minutes. CertiK flagged the exploit first and Blockaid followed about a minute later, according to Rekt, and CertiK's alert told anyone with approvals to the proxy to revoke them. SlowMist's alert followed with the root cause and the addresses involved.
At 03:53:51 UTC, TrustedVolumes posted its first message on X in more than a year. It named three addresses holding the stolen funds, two with about $3 million each and a third, 0xfA4F52DF02E5f3563bC4569A6B43bB6AcA617e07, with about $700,000, and said it was open to talking about a bug bounty and a mutually acceptable resolution. It didn't explain the bug. Over the following week, Blockscout shows the main wallet depositing 10.1 ETH into the Tornado Cash router on May 9, and the second wallet depositing 100 ETH on May 15.
Much of the negotiation then played out in transaction data. On May 10, the main wallet sent TrustedVolumes' messaging address 0x1ef9BFb1e7480c01D3d00E9bCA5F29625c6C4806 a note saying too many people were trying to contact it, along with an email address. On May 15 at 08:47:59 UTC, that wallet sent 1,169.96 ETH to 0xb6F28eD0f919A12822fE78F6d610e5e09A6Fe450, the address TrustedVolumes would go on to name as its own in every later message. The same day, TrustedVolumes wrote to the $700,000 wallet that it was prepared to write off the funds and file a criminal complaint, and offered a bug bounty settlement instead. On May 28, it told the attacker's wallets it had detected funds moving from the attacker's Bitcoin wallet and asked again for the rest.
On July 16, TrustedVolumes asked the attacker's wallets to return 1,122 ETH to its address to finalize the settlement. At 00:14:23 UTC on July 17, the second wallet sent 1,122.12 ETH there. Valued at CoinGecko's daily ETH price, the May 15 return was worth about $2.67 million at $2,282.60 and the July 17 return about $2.09 million at $1,863.01, so together they cut the loss from $5.86 million to about $1.10 million. Later that day, TrustedVolumes broadcast an on-chain message saying it had finalized negotiations with the original exploiter, who had returned the funds and received a bug bounty, and inviting others holding funds from the incident to get in touch. Cryptopolitan, citing Defimon Alerts, valued the July return at about $2.07 million and reported that the exploiter kept a similar amount as the bounty. As of October 23, 2026, TrustedVolumes hadn't published a post-mortem or a full accounting of what was recovered.
Why the Approvals Were the Blast Radius
RFQ market makers need their settlement contracts to move inventory quickly, and standing approvals are the usual way to do that. The design choice that made this attack so large was keeping the whole inventory approved to a contract whose authorization logic could be satisfied by anyone. The implementation was never published, and the researchers who eventually explained its three checks did so by reading bytecode after the money was gone.
Audits are necessary for code like this, and they cover the scope and time they're given. The flaw here lived in the relationship between functions: a permissionless setter, a signer check keyed on one field and a transfer that read another. Each of those lines can look reasonable alone, and the break only appears when someone follows a single order from the signature to the token transfer and asks whose money is moving.
How Olympix Follows the Signature to the Debit
Olympix reads a settlement contract the way an attacker does, as states, transitions and entry points, and it works on the code a team ships, whether or not that code is ever verified on a block explorer. In this proxy, the state that matters is who can spend the inventory's approved balances, and the fill function is the entry point that spends them. The engine maps every path into that function and uses signals to decide which paths are worth attacking. Olympix's static analysis surfaces two here: a transferFrom whose source address comes straight from order data the caller writes, while the only authorization in the path is keyed on a different address, and a public function with no access control that writes the very allowlist that authorization reads.
The invariant this system depends on fits in one sentence: tokens should only leave the inventory on an order signed by a signer the inventory's owner authorized. BugPoCer takes that rule and tries to break it with Counterexample-Guided Invariant Refutation, working outward from the contract as a whole. It narrows from every path in the proxy, to the feasible ones a fresh account can reach, to the ones that end in a transfer out of an address that never approved the signer, and the path through registerAllowedOrderSigner and the fill function lands on the invariant with nothing in between.
The counterexample BugPoCer returns is that path made concrete: a new account registers a signer for itself, fills an order it signed that names the inventory as the source, and receives inventory tokens it was never entitled to, along with the minimal conditions that make it work, chiefly an inventory with a standing allowance to the proxy, and a generated Forge test that replays the sequence. That's the TrustedVolumes exploit, produced in development rather than on mainnet.
Olympix has flagged this class before in other codebases, where a function debited an address that had never authorized the action being paid for. Because the engine runs automatically in development, at every merge and before release, a check like this one would have surfaced at the merge that introduced the fill function in 2024, long before the approvals behind it grew to millions.
Authorize the Account That Pays
TrustedVolumes' proxy checked a signature, checked an allowlist and kept a record of filled orders, and each of those checks was answering a slightly different question from the one that mattered. When a contract spends someone's approval, its authorization has to be about the address being charged, and that's a property a team can test on every merge. To see how Olympix traces every path where your contracts pull funds from one address on the strength of a check about another, book a technical walkthrough.
On-Chain References
Protocol: TrustedVolumes RFQ proxy
Chain: Ethereum
Date: May 7, 2026, 00:47:35 UTC
Loss: 1,291.16 WETH, 206,282.45 USDT, 16.939 WBTC and 1,268,771.49 USDC drained ($5,864,442 at Chainlink prices per banteg), minus 2,292.08 ETH returned ($4,761,055 at CoinGecko prices), about $1.1M net
Attacker EOA: 0xC3EB...9100
Second attacker wallet: 0x61e6...2D1c
Third wallet named by TrustedVolumes: 0xfA4F...7e07
Attack contract: 0xD4D5...1E95
RFQ proxy (unverified): 0xeEeE...1756
RFQ implementation (unverified): 0x88eb...60d8
TrustedVolumes inventory (victim): 0x9bA0...Da31
Exploit tx: 0xc5c6...0513
Block: 25039670
TrustedVolumes messaging address: 0x1ef9...4806
TrustedVolumes recovery address: 0xb6F2...e450
First return (1,169.96 ETH): 0x1916...ec96
Settlement request: 0x06a4...08b9
Second return (1,122.12 ETH): 0x2406...3f0a
Settlement message: 0x610c...0339
Sources
QuillAudits: TrustedVolumes RFQ Hack Analysis
banteg: Foundry reproduction of the TrustedVolumes RFQ drain
SlowMist: TI Alert on the TrustedVolumes RFQ exploit
TrustedVolumes: Statement on the exploit and stolen-fund addresses
More post-mortems
See every post-mortem →See what Olympix finds on your own code
Send us a repo that's already been audited, and we'll run Olympix at the same commit and show you what comes back.




