DxSale
An attacker who controls the owner key of DxSale's old liquidity locker on BNB Chain has been draining LP tokens that more than 1,400 projects locked up as far back as 2021, including SafeMoon-linked liquidity. Around $1.74 million has been pulled out so far and roughly $2.91 million more is still exposed, out of about $7.3 million of affected positions. The locker keeps working exactly as coded. The problem is that whoever holds its owner key can move the locked funds, and that key is now in hostile hands. Proceeds were swapped into BNB and moved through more than 80 wallets, so they are effectively gone.
DxSale is not one of the protocols we publish a live safety score for, so we had no prior reading on it. The weakness here is the kind our model weighs heavily wherever it does apply: a contract that custodies user funds while a single live owner key, with no renounce and no timelock, can change the rules and pull the funds out. A liquidity lock is only as safe as the key that controls the locker.
The drain runs against DxSale's liquidity locker, whose owner is now an attacker account. That account set its own code to point at a purpose-built drainer contract using a new account-delegation feature, so the locker sees every call as coming from its rightful owner. For each batch of positions the drainer drops the lock fee to one unit, creates lock records with the unlock date backdated to 1970 so they count as already expired, restores the fee, then withdraws the freed LP in a loop and routes it through wrapped BNB into BNB. The cash-out moved through a pass-through wallet and a years-dormant wallet tied to the DxSale team before fanning across 80 or more hops.
Safe. New DxSale products and current token tooling were not affected.
A single live owner key over the old locker was never renounced and not behind a timelock or multisig. With the key in hostile hands, the owner called changeFees, createLocker with unlock backdated to 1970, then unlockToken to pull the LP. Not a contract bug.
Full forensic detail
Step-by-step reconstruction, root cause, counterfactuals, remediation, and disclosure timeline.
Exploit anatomy
Root cause
DxSale's old liquidity locker is a single contract that custodies LP tokens for thousands of projects. It kept owner-only functions that can change the lock fee, create lock records, and withdraw locked tokens. That owner key was live, never renounced, not behind a timelock, and not behind a multisig. Ownership over the key moved to an attacker, who then used the normal owner functions to convert locked deposits into a withdrawable balance and pull them out. The contract did exactly what it was written to do. The failure is that a single key was allowed to keep that much authority over other people's funds. Whether the key was always the team's or was acquired by an outsider is not independently proven; the load-bearing fact either way is the live privileged key.
// Owner-only functions on the locker that move user funds
function changeFees(uint256 newFee) external onlyOwner { ... }
function createLocker(address token, uint256 amount, uint256 unlockTime, string memory logo) external { ... } // unlockTime = 68 -> 1970, already expired
function unlockToken(uint256 lockerId) external { ... } // withdraws the positionPrevention analysis
Similar incidents
A privileged role on a DeFi product was used to withdraw user-supplied liquidity, with insider or key-control attribution. Same shape: legitimate authority turned against the funds it was meant to protect.
Custodial admin authority over locked assets was exercised to drain them. Identical risk class at the architecture level: a privileged custodial role over user funds without enough operational hardening.
Same delegation primitive used here: an account sets its own code to a drainer and self-executes. Usually pointed at victim wallets; here it is pointed at a protocol locker.