SEAMAN Public Sell Trigger
Exploit Transactions
0x6f1af27d08b10caa7e96ec3d580bf39e29fd5ece00abda7d8955715403bf34a8Victim Addresses
0x6bc9b4976ba6f8c9574326375204ee469993d038BSCLoss Breakdown
Similar Incidents
Public Liquidity Trigger Drain
43%AiWGPT Public Sell LP Drain
39%BUBU2 Permissionless LP-Balance Burn Trigger Exploit
36%Keep Rising Public Sell LP Drain
36%STV Sell Accounting Drain
35%SOF Sell-Hook Reserve Manipulation Drains PancakeSwap V2 USDT Liquidity
35%Root Cause Analysis
SEAMAN Public Sell Trigger
1. Incident Overview TL;DR
On BSC block 23467516, transaction 0x6f1af27d08b10caa7e96ec3d580bf39e29fd5ece00abda7d8955715403bf34a8 used a public DODO flash loan to buy GVC, then repeatedly sold 1 wei of SEAMAN into the SEAMAN/USDT pair to force SEAMAN’s token contract to dump its own fee inventory through SEAMAN -> USDT -> GVC. Those victim-funded buys pumped the GVC price, letting the attacker unwind the pre-bought GVC position for net USDT profit after loan repayment.
The root cause is that SEAMAN embeds treasury-spending swap logic directly inside permissionless sell flow. Any non-excluded seller sending SEAMAN to the pair can reach swapAndLiquifyV3() and swapAndLiquifyV1() before their own sell is processed, so an unprivileged adversary can choose when the protocol spends contract-held SEAMAN inventory into external markets.
2. Key Background
SEAMAN is a token on BSC at 0x6bc9b4976ba6f8c9574326375204ee469993d038. Its verified source shows that transfer-time fee handling accumulates inventory on the token contract itself and tracks convertible balances through lpAmount and hAmount. That accumulated inventory is later swapped through PancakeSwap.
Two PancakeSwap pools matter for this incident:
SEAMAN/USDTat0x6637914482670f91f43025802b6755f27050b0a6GVC/USDTat0x84d26ad5f7d12ae783fee433fd0d2b2b1a7e9260
The adversary also used public DODO liquidity and the Pancake router. No privileged role, admin method, or private artifact was required; the opportunity depended only on public state and public contract entrypoints.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability class is an application-layer attack caused by exposing a price-impacting treasury action through a public transfer hook. In the SEAMAN override of _transfer, any qualifying sell to uniswapV2Pair checks whether the contract balance exceeds a threshold derived from pair inventory, and if so it runs swapAndLiquifyV3() and swapAndLiquifyV1(). swapAndLiquifyV1() converts contract-held SEAMAN into lpToken, which in this deployment is GVC, through the path SEAMAN -> USDT -> GVC.
That means a seller does not merely sell their own token balance. They can also force the protocol to spend treasury-held SEAMAN inventory into the market immediately before their sell completes. Because this path predictably removes USDT from the SEAMAN/USDT pool and buys GVC in the GVC/USDT pool, an attacker can first accumulate GVC, then trigger the treasury spend many times, then sell GVC into the induced price increase. The violated invariant is that unprivileged users must not control the timing and direction of protocol treasury inventory spending in external liquidity venues.
4. Detailed Root Cause Analysis
The verified SEAMAN source shows the exact permissionless breakpoint:
if (
uniswapV2Pair.totalSupply() > 0 &&
balanceOf(address(this)) > balanceOf(address(uniswapV2Pair)).div(10000) &&
to == address(uniswapV2Pair)
) {
if (!swapping && _tokenOwner != from && _tokenOwner != to && !ammPairs[from] && !(from == address(uniswapV2Router) && !ammPairs[to]) && swapAndLiquifyEnabled) {
swapping = true;
swapAndLiquifyV3();
swapAndLiquifyV1();
swapping = false;
}
}
swapAndLiquifyV1() then spends tracked fee inventory:
function swapAndLiquifyV1() public {
uint256 canlpAmount = lpAmount.sub(lpTokenAmount);
uint256 amountT = balanceOf(address(uniswapV2Pair)).div(10000);
if (balanceOf(address(this)) >= canlpAmount && canlpAmount >= amountT) {
if (canlpAmount >= amountT.mul(5)) canlpAmount = amountT.mul(5);
lpTokenAmount = lpTokenAmount.add(canlpAmount);
swapTokensFor(canlpAmount, address(lpToken), address(this));
}
}
And swapTokensFor hard-codes the market-buy route:
path[0] = address(this);
path[1] = address(usdt);
path[2] = address(token);
uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(tokenAmount, 0, path, to, block.timestamp);
The seed trace confirms the exploit sequence. First, the attacker contract bought 485907716270510929330243 raw GVC through the GVC/USDT pair. Immediately after, the trace shows repeated calls to SEAMAN::transfer(PancakePair, 1). On the first such call, the victim contract performed a router swap of 9480988980507858741121000 raw SEAMAN into the SEAMAN -> USDT -> GVC path, emitted a Transfer of 1 token into the pair from the attacker contract, and then repeated this pattern across the trigger loop.
The balance diff matches the mechanism:
- SEAMAN contract balance:
172148930281759729054127355 -> 11158620000010000000000000, delta-160990310281749729054127355 - SEAMAN/USDT pair USDT balance: delta
-8585753852461135632712 - Helper contract
0x49fac69c51a303b4597d09c18bc5e7bf38ecf89cUSDT balance: delta+7781777413037475413078
These state changes are exactly what the root cause predicts: public dust sells force treasury inventory into market buys, the SEAMAN pool loses USDT, GVC is bought, and the adversary exits with profit.
5. Adversary Flow Analysis
The adversary-controlled EOA 0x4b1f47be1f678076f447585beba025e3a046a9fa sent the seed transaction to contract 0x0e647d34c4caf61d9e377a059a01b5c85ab1d82a, which executed the exploit logic and later forwarded profit to helper 0x49fac69c51a303b4597d09c18bc5e7bf38ecf89c.
The end-to-end flow was:
- Borrow
805584574837416580124510raw USDT from the public DODO pool. - Buy a dust amount of SEAMAN to ensure the attacker contract can call
transfer(pair, 1). - Buy a large GVC position before the manipulation.
- Call
SEAMAN::transfer(SEAMAN_USDT_PAIR, 1)forty times. - Each trigger forces SEAMAN to swap contract-held SEAMAN through
SEAMAN -> USDT -> GVC, which depletes USDT from the SEAMAN pool and buys GVC. - Sell the now-appreciated GVC position back into USDT.
- Repay the flash loan and keep the remaining USDT profit.
The trace contains many consecutive entries of the form below, evidencing the public trigger loop:
SEAMAN::transfer(PancakePair: [0x6637914482670f91F43025802b6755F27050b0a6], 1)
SEAMAN::transfer(PancakePair: [0x6637914482670f91F43025802b6755F27050b0a6], 1)
SEAMAN::transfer(PancakePair: [0x6637914482670f91F43025802b6755F27050b0a6], 1)
6. Impact & Losses
The attacker cluster realized 7781777413037475413078 raw USDT profit at the helper contract after flash-loan repayment. The manipulated flow also removed 8585753852461135632712 raw USDT from the SEAMAN/USDT pool and forced the SEAMAN token contract to spend 160990310281749729054127355 raw SEAMAN from its own inventory.
The measurable loss reported for the incident is:
{
"token_symbol": "USDT",
"amount": "8585753852461135632712",
"decimal": 18
}
7. References
- Seed tx hash:
0x6f1af27d08b10caa7e96ec3d580bf39e29fd5ece00abda7d8955715403bf34a8 - Seed tx metadata:
/workspace/session/artifacts/collector/seed/56/0x6f1af27d08b10caa7e96ec3d580bf39e29fd5ece00abda7d8955715403bf34a8/metadata.json - Seed trace:
/workspace/session/artifacts/collector/seed/56/0x6f1af27d08b10caa7e96ec3d580bf39e29fd5ece00abda7d8955715403bf34a8/trace.cast.log - Seed balance diff:
/workspace/session/artifacts/collector/seed/56/0x6f1af27d08b10caa7e96ec3d580bf39e29fd5ece00abda7d8955715403bf34a8/balance_diff.json - SEAMAN verified source:
/workspace/session/artifacts/collector/seed/56/0x6bc9b4976ba6f8c9574326375204ee469993d038/src/Contract.sol - GVC verified source:
/workspace/session/artifacts/collector/seed/56/0xdb95fbc5532eeb43deed56c8dc050c930e31017e/src/Contract.sol